Completed
Branch master (404a73)
by
unknown
17:42 queued 11:18
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.43');
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.43');
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/db_classes/EE_Base_Class.class.php 2 patches
Indentation   +3362 added lines, -3362 removed lines patch added patch discarded remove patch
@@ -15,3377 +15,3377 @@
 block discarded – undo
15 15
  */
16 16
 abstract class EE_Base_Class
17 17
 {
18
-    /**
19
-     * @var EEM_Base|null
20
-     */
21
-    protected $_model = null;
22
-
23
-    /**
24
-     * This is an array of the original properties and values provided during construction
25
-     * of this model object. (keys are model field names, values are their values).
26
-     * This list is important to remember so that when we are merging data from the db, we know
27
-     * which values to override and which to not override.
28
-     */
29
-    protected ?array $_props_n_values_provided_in_constructor = null;
30
-
31
-    /**
32
-     * Timezone
33
-     * This gets set by the "set_timezone()" method so that we know what timezone incoming strings|timestamps are in.
34
-     * This can also be used before a get to set what timezone you want strings coming out of the object to be in.  NOT
35
-     * all EE_Base_Class child classes use this property but any that use a EE_Datetime_Field data type will have
36
-     * access to it.
37
-     */
38
-    protected string $_timezone = '';
39
-
40
-    /**
41
-     * date format
42
-     * pattern or format for displaying dates
43
-     */
44
-    protected string $_dt_frmt = '';
45
-
46
-    /**
47
-     * time format
48
-     * pattern or format for displaying time
49
-     */
50
-    protected string $_tm_frmt = '';
51
-
52
-    /**
53
-     * This property is for holding a cached array of object properties indexed by property name as the key.
54
-     * The purpose of this is for setting a cache on properties that may have calculated values after a
55
-     * prepare_for_get.  That way the cache can be checked first and the calculated property returned instead of having
56
-     * to recalculate. Used by _set_cached_property() and _get_cached_property() methods.
57
-     */
58
-    protected array $_cached_properties = [];
59
-
60
-    /**
61
-     * An array containing keys of the related model, and values are either an array of related mode objects or a
62
-     * single
63
-     * related model object. see the model's _model_relations. The keys should match those specified. And if the
64
-     * relation is of type EE_Belongs_To (or one of its children), then there should only be ONE related model object,
65
-     * all others have an array)
66
-     */
67
-    protected array $_model_relations = [];
68
-
69
-    /**
70
-     * Array where keys are field names (see the model's _fields property) and values are their values. To see what
71
-     * their types should be, look at what that field object returns on its prepare_for_get and prepare_for_set methods)
72
-     */
73
-    protected array $_fields = [];
74
-
75
-    /**
76
-     * indicating whether or not this model object is intended to ever be saved
77
-     * For example, we might create model objects intended to only be used for the duration
78
-     * of this request and to be thrown away, and if they were accidentally saved
79
-     * it would be a bug.
80
-     */
81
-    protected bool $_allow_persist = true;
82
-
83
-    /**
84
-     * indicating whether or not this model object's properties have changed since construction
85
-     */
86
-    protected bool $_has_changes = false;
87
-
88
-    /**
89
-     * This is a cache of results from custom selections done on a query that constructs this entity. The only purpose
90
-     * for these values is for retrieval of the results, they are not further queryable and they are not persisted to
91
-     * the db.  They also do not automatically update if there are any changes to the data that produced their results.
92
-     * The format is a simple array of field_alias => field_value.  So for instance if a custom select was something
93
-     * like,  "Select COUNT(Registration.REG_ID) as Registration_Count ...", then the resulting value will be in this
94
-     * array as:
95
-     * array(
96
-     *  'Registration_Count' => 24
97
-     * );
98
-     * Note: if the custom select configuration for the query included a data type, the value will be in the data type
99
-     * provided for the query (@see EventEspresso\core\domain\values\model\CustomSelects::__construct phpdocs for more
100
-     * info)
101
-     */
102
-    protected array $custom_selection_results = [];
103
-
104
-
105
-    /**
106
-     * basic constructor for Event Espresso classes, performs any necessary initialization, and verifies it's children
107
-     * play nice
108
-     *
109
-     * @param array   $fieldValues                             where each key is a field (ie, array key in the 2nd
110
-     *                                                         layer of the model's _fields array, (eg, EVT_ID,
111
-     *                                                         TXN_amount, QST_name, etc) and values are their values
112
-     * @param boolean $bydb                                    a flag for setting if the class is instantiated by the
113
-     *                                                         corresponding db model or not.
114
-     * @param string  $timezone                                indicate what timezone you want any datetime fields to
115
-     *                                                         be in when instantiating a EE_Base_Class object.
116
-     * @param array   $date_formats                            An array of date formats to set on construct where first
117
-     *                                                         value is the date_format and second value is the time
118
-     *                                                         format.
119
-     * @throws InvalidArgumentException
120
-     * @throws InvalidInterfaceException
121
-     * @throws InvalidDataTypeException
122
-     * @throws EE_Error
123
-     * @throws ReflectionException
124
-     */
125
-    protected function __construct($fieldValues = [], $bydb = false, $timezone = '', $date_formats = [])
126
-    {
127
-        $className = get_class($this);
128
-        do_action("AHEE__{$className}__construct", $this, $fieldValues);
129
-        $model        = $this->get_model();
130
-        $model_fields = $model->field_settings();
131
-        // ensure $fieldValues is an array
132
-        $fieldValues = is_array($fieldValues) ? $fieldValues : [$fieldValues];
133
-        // verify client code has not passed any invalid field names
134
-        foreach ($fieldValues as $field_name => $field_value) {
135
-            if (! isset($model_fields[ $field_name ])) {
136
-                throw new EE_Error(
137
-                    sprintf(
138
-                        esc_html__(
139
-                            'Invalid field (%s) passed to constructor of %s. Allowed fields are :%s',
140
-                            'event_espresso'
141
-                        ),
142
-                        $field_name,
143
-                        get_class($this),
144
-                        implode(', ', array_keys($model_fields))
145
-                    )
146
-                );
147
-            }
148
-        }
149
-
150
-        $date_format     = null;
151
-        $time_format     = null;
152
-        $this->_timezone = EEH_DTT_Helper::get_valid_timezone_string($timezone);
153
-        if (! empty($date_formats) && is_array($date_formats)) {
154
-            [$date_format, $time_format] = $date_formats;
155
-        }
156
-        $this->set_date_format($date_format);
157
-        $this->set_time_format($time_format);
158
-        // if db model is instantiating
159
-        foreach ($model_fields as $fieldName => $field) {
160
-            if ($bydb) {
161
-                // client code has indicated these field values are from the database
162
-                $this->set_from_db(
163
-                    $fieldName,
164
-                    $fieldValues[ $fieldName ] ?? null
165
-                );
166
-            } else {
167
-                // we're constructing a brand new instance of the model object.
168
-                // Generally, this means we'll need to do more field validation
169
-                $this->set(
170
-                    $fieldName,
171
-                    $fieldValues[ $fieldName ] ?? null,
172
-                    true
173
-                );
174
-            }
175
-        }
176
-        // remember what values were passed to this constructor
177
-        $this->_props_n_values_provided_in_constructor = $fieldValues;
178
-        // remember in entity mapper
179
-        if (! $bydb && $model->has_primary_key_field() && $this->ID()) {
180
-            $model->add_to_entity_map($this);
181
-        }
182
-        // setup all the relations
183
-        foreach ($model->relation_settings() as $relation_name => $relation_obj) {
184
-            if ($relation_obj instanceof EE_Belongs_To_Relation) {
185
-                $this->_model_relations[ $relation_name ] = null;
186
-            } else {
187
-                $this->_model_relations[ $relation_name ] = [];
188
-            }
189
-        }
190
-        /**
191
-         * Action done at the end of each model object construction
192
-         *
193
-         * @param EE_Base_Class $this the model object just created
194
-         */
195
-        do_action('AHEE__EE_Base_Class__construct__finished', $this);
196
-    }
197
-
198
-
199
-    /**
200
-     * Gets whether or not this model object is allowed to persist/be saved to the database.
201
-     *
202
-     * @return boolean
203
-     */
204
-    public function allow_persist()
205
-    {
206
-        return $this->_allow_persist;
207
-    }
208
-
209
-
210
-    /**
211
-     * Sets whether or not this model object should be allowed to be saved to the DB.
212
-     * Normally once this is set to FALSE you wouldn't set it back to TRUE, unless
213
-     * you got new information that somehow made you change your mind.
214
-     *
215
-     * @param boolean $allow_persist
216
-     * @return boolean
217
-     */
218
-    public function set_allow_persist($allow_persist)
219
-    {
220
-        return $this->_allow_persist = $allow_persist;
221
-    }
222
-
223
-
224
-    /**
225
-     * Gets the field's original value when this object was constructed during this request.
226
-     * This can be helpful when determining if a model object has changed or not
227
-     *
228
-     * @param string $field_name
229
-     * @return mixed|null
230
-     * @throws ReflectionException
231
-     * @throws InvalidArgumentException
232
-     * @throws InvalidInterfaceException
233
-     * @throws InvalidDataTypeException
234
-     * @throws EE_Error
235
-     */
236
-    public function get_original($field_name)
237
-    {
238
-        if (
239
-            isset($this->_props_n_values_provided_in_constructor[ $field_name ])
240
-            && $field_settings = $this->get_model()->field_settings_for($field_name)
241
-        ) {
242
-            return $field_settings->prepare_for_get($this->_props_n_values_provided_in_constructor[ $field_name ]);
243
-        }
244
-        return null;
245
-    }
246
-
247
-
248
-    /**
249
-     * @param EE_Base_Class $obj
250
-     * @return string
251
-     */
252
-    public function get_class($obj)
253
-    {
254
-        return get_class($obj);
255
-    }
256
-
257
-
258
-    /**
259
-     * Overrides parent because parent expects old models.
260
-     * This also doesn't do any validation, and won't work for serialized arrays
261
-     *
262
-     * @param string $field_name
263
-     * @param mixed  $field_value
264
-     * @param bool   $use_default
265
-     * @throws InvalidArgumentException
266
-     * @throws InvalidInterfaceException
267
-     * @throws InvalidDataTypeException
268
-     * @throws EE_Error
269
-     * @throws ReflectionException
270
-     * @throws Exception
271
-     */
272
-    public function set(string $field_name, $field_value, bool $use_default = false)
273
-    {
274
-        // if not using default and nothing has changed, and object has already been setup (has ID),
275
-        // then don't do anything
276
-        if (
277
-            ! $use_default
278
-            && $this->_fields[ $field_name ] === $field_value
279
-            && $this->ID()
280
-        ) {
281
-            return;
282
-        }
283
-        $model              = $this->get_model();
284
-        $this->_has_changes = true;
285
-        $field_obj          = $model->field_settings_for($field_name);
286
-        if (! $field_obj instanceof EE_Model_Field_Base) {
287
-            throw new EE_Error(
288
-                sprintf(
289
-                    esc_html__(
290
-                        'A valid EE_Model_Field_Base could not be found for the given field name: %s',
291
-                        'event_espresso'
292
-                    ),
293
-                    $field_name
294
-                )
295
-            );
296
-        }
297
-        // if ( method_exists( $field_obj, 'set_timezone' )) {
298
-        if ($field_obj instanceof EE_Datetime_Field) {
299
-            $field_obj->set_timezone($this->_timezone);
300
-            $field_obj->set_date_format($this->_dt_frmt);
301
-            $field_obj->set_time_format($this->_tm_frmt);
302
-        }
303
-
304
-        // should the value be null?
305
-        $value = $field_value === null && ($use_default || ! $field_obj->is_nullable())
306
-            ? $field_obj->get_default_value()
307
-            : $field_value;
308
-
309
-        $this->_fields[ $field_name ] = $field_obj->prepare_for_set($value);
310
-
311
-        // if we're not in the constructor...
312
-        // now check if what we set was a primary key
313
-        if (
314
-            // note: props_n_values_provided_in_constructor is only set at the END of the constructor
315
-            $this->_props_n_values_provided_in_constructor
316
-            && $field_value
317
-            && $field_name === $model->primary_key_name()
318
-        ) {
319
-            // if so, we want all this object's fields to be filled either with
320
-            // what we've explicitly set on this model
321
-            // or what we have in the db
322
-            // echo "setting primary key!";
323
-            $fields_on_model = self::_get_model(get_class($this))->field_settings();
324
-            $obj_in_db       = self::_get_model(get_class($this))->get_one_by_ID($field_value);
325
-            foreach ($fields_on_model as $field_obj) {
326
-                if (
327
-                    ! array_key_exists($field_obj->get_name(), $this->_props_n_values_provided_in_constructor)
328
-                    && $field_obj->get_name() !== $field_name
329
-                ) {
330
-                    $this->set($field_obj->get_name(), $obj_in_db->get($field_obj->get_name()));
331
-                }
332
-            }
333
-            // oh this model object has an ID? well make sure its in the entity mapper
334
-            $model->add_to_entity_map($this);
335
-        }
336
-        // let's unset any cache for this field_name from the $_cached_properties property.
337
-        $this->_clear_cached_property($field_name);
338
-    }
339
-
340
-
341
-    /**
342
-     * Overrides parent because parent expects old models.
343
-     * This also doesn't do any validation, and won't work for serialized arrays
344
-     *
345
-     * @param string $field_name
346
-     * @param mixed  $field_value_from_db
347
-     * @throws ReflectionException
348
-     * @throws InvalidArgumentException
349
-     * @throws InvalidInterfaceException
350
-     * @throws InvalidDataTypeException
351
-     * @throws EE_Error
352
-     */
353
-    public function set_from_db(string $field_name, $field_value_from_db)
354
-    {
355
-        $field_obj = $this->get_model()->field_settings_for($field_name);
356
-        if ($field_obj instanceof EE_Model_Field_Base) {
357
-            // you would think the DB has no NULLs for non-null label fields right? wrong!
358
-            // eg, a CPT model object could have an entry in the posts table, but no
359
-            // entry in the meta table. Meaning that all its columns in the meta table
360
-            // are null! yikes! so when we find one like that, use defaults for its meta columns
361
-            if ($field_value_from_db === null) {
362
-                if ($field_obj->is_nullable()) {
363
-                    // if the field allows nulls, then let it be null
364
-                    $field_value = null;
365
-                } else {
366
-                    $field_value = $field_obj->get_default_value();
367
-                }
368
-            } else {
369
-                $field_value = $field_obj->prepare_for_set_from_db($field_value_from_db);
370
-            }
371
-            $this->_fields[ $field_name ] = $field_value;
372
-            $this->_clear_cached_property($field_name);
373
-        }
374
-    }
375
-
376
-
377
-    /**
378
-     * Set custom select values for model.
379
-     *
380
-     * @param array $custom_select_values
381
-     */
382
-    public function setCustomSelectsValues(array $custom_select_values)
383
-    {
384
-        $this->custom_selection_results = $custom_select_values;
385
-    }
386
-
387
-
388
-    /**
389
-     * Returns the custom select value for the provided alias if its set.
390
-     * If not set, returns null.
391
-     *
392
-     * @param string $alias
393
-     * @return string|int|float|null
394
-     */
395
-    public function getCustomSelect($alias)
396
-    {
397
-        return $this->custom_selection_results[ $alias ] ?? null;
398
-    }
399
-
400
-
401
-    /**
402
-     * This sets the field value on the db column if it exists for the given $column_name or
403
-     * saves it to EE_Extra_Meta if the given $column_name does not match a db column.
404
-     *
405
-     * @param string $field_name  Must be the exact column name.
406
-     * @param mixed  $field_value The value to set.
407
-     * @return int|bool @see EE_Base_Class::update_extra_meta() for return docs.
408
-     * @throws InvalidArgumentException
409
-     * @throws InvalidInterfaceException
410
-     * @throws InvalidDataTypeException
411
-     * @throws EE_Error
412
-     * @throws ReflectionException
413
-     * @see EE_message::get_column_value for related documentation on the necessity of this method.
414
-     */
415
-    public function set_field_or_extra_meta($field_name, $field_value)
416
-    {
417
-        if ($this->get_model()->has_field($field_name)) {
418
-            $this->set($field_name, $field_value);
419
-            return true;
420
-        }
421
-        // ensure this object is saved first so that extra meta can be properly related.
422
-        $this->save();
423
-        return $this->update_extra_meta($field_name, $field_value);
424
-    }
425
-
426
-
427
-    /**
428
-     * This retrieves the value of the db column set on this class or if that's not present
429
-     * it will attempt to retrieve from extra_meta if found.
430
-     * Example Usage:
431
-     * Via EE_Message child class:
432
-     * Due to the dynamic nature of the EE_messages system, EE_messengers will always have a "to",
433
-     * "from", "subject", and "content" field (as represented in the EE_Message schema), however they may
434
-     * also have additional main fields specific to the messenger.  The system accommodates those extra
435
-     * fields through the EE_Extra_Meta table.  This method allows for EE_messengers to retrieve the
436
-     * value for those extra fields dynamically via the EE_message object.
437
-     *
438
-     * @param string $field_name expecting the fully qualified field name.
439
-     * @return mixed|null  value for the field if found.  null if not found.
440
-     * @throws ReflectionException
441
-     * @throws InvalidArgumentException
442
-     * @throws InvalidInterfaceException
443
-     * @throws InvalidDataTypeException
444
-     * @throws EE_Error
445
-     */
446
-    public function get_field_or_extra_meta($field_name)
447
-    {
448
-        if ($this->get_model()->has_field($field_name)) {
449
-            $column_value = $this->get($field_name);
450
-        } else {
451
-            // This isn't a column in the main table, let's see if it is in the extra meta.
452
-            $column_value = $this->get_extra_meta($field_name, true, null);
453
-        }
454
-        return $column_value;
455
-    }
456
-
457
-
458
-    /**
459
-     * See $_timezone property for description of what the timezone property is for.  This SETS the timezone internally
460
-     * for being able to reference what timezone we are running conversions on when converting TO the internal timezone
461
-     * (UTC Unix Timestamp) for the object OR when converting FROM the internal timezone (UTC Unix Timestamp). This is
462
-     * available to all child classes that may be using the EE_Datetime_Field for a field data type.
463
-     *
464
-     * @access public
465
-     * @param string $timezone A valid timezone string as described by @link http://www.php.net/manual/en/timezones.php
466
-     * @return void
467
-     * @throws InvalidArgumentException
468
-     * @throws InvalidInterfaceException
469
-     * @throws InvalidDataTypeException
470
-     * @throws EE_Error
471
-     * @throws ReflectionException
472
-     * @throws Exception
473
-     */
474
-    public function set_timezone($timezone = '')
475
-    {
476
-        $this->_timezone = EEH_DTT_Helper::get_valid_timezone_string($timezone);
477
-        // make sure we clear all cached properties because they won't be relevant now
478
-        $this->_clear_cached_properties();
479
-        // make sure we update field settings and the date for all EE_Datetime_Fields
480
-        $model_fields = $this->get_model()->field_settings(false);
481
-        foreach ($model_fields as $field_name => $field_obj) {
482
-            if ($field_obj instanceof EE_Datetime_Field) {
483
-                $field_obj->set_timezone($this->_timezone);
484
-                if (isset($this->_fields[ $field_name ]) && $this->_fields[ $field_name ] instanceof DateTime) {
485
-                    EEH_DTT_Helper::setTimezone($this->_fields[ $field_name ], new DateTimeZone($this->_timezone));
486
-                }
487
-            }
488
-        }
489
-    }
490
-
491
-
492
-    /**
493
-     * This just returns whatever is set for the current timezone.
494
-     *
495
-     * @access public
496
-     * @return string timezone string
497
-     */
498
-    public function get_timezone()
499
-    {
500
-        return $this->_timezone;
501
-    }
502
-
503
-
504
-    /**
505
-     * This sets the internal date format to what is sent in to be used as the new default for the class
506
-     * internally instead of wp set date format options
507
-     *
508
-     * @param string|null $format should be a format recognizable by PHP date() functions.
509
-     * @since 4.6
510
-     */
511
-    public function set_date_format(?string $format)
512
-    {
513
-        $this->_dt_frmt = new DateFormat($format);
514
-        // clear cached_properties because they won't be relevant now.
515
-        $this->_clear_cached_properties();
516
-    }
517
-
518
-
519
-    /**
520
-     * This sets the internal time format string to what is sent in to be used as the new default for the
521
-     * class internally instead of wp set time format options.
522
-     *
523
-     * @param string|null $format should be a format recognizable by PHP date() functions.
524
-     * @since 4.6
525
-     */
526
-    public function set_time_format(?string $format)
527
-    {
528
-        $this->_tm_frmt = new TimeFormat($format);
529
-        // clear cached_properties because they won't be relevant now.
530
-        $this->_clear_cached_properties();
531
-    }
532
-
533
-
534
-    /**
535
-     * This returns the current internal set format for the date and time formats.
536
-     *
537
-     * @param bool $full           if true (default), then return the full format.  Otherwise will return an array
538
-     *                             where the first value is the date format and the second value is the time format.
539
-     * @return string|array
540
-     */
541
-    public function get_format($full = true)
542
-    {
543
-        return $full ? $this->_dt_frmt . ' ' . $this->_tm_frmt : [$this->_dt_frmt, $this->_tm_frmt];
544
-    }
545
-
546
-
547
-    /**
548
-     * cache
549
-     * stores the passed model object on the current model object.
550
-     * In certain circumstances, we can use this cached model object instead of querying for another one entirely.
551
-     *
552
-     * @param string        $relation_name   one of the keys in the _model_relations array on the model. Eg
553
-     *                                       'Registration' associated with this model object
554
-     * @param EE_Base_Class $object_to_cache that has a relation to this model object. (Eg, if this is a Transaction,
555
-     *                                       that could be a payment or a registration)
556
-     * @param null          $cache_id        a string or number that will be used as the key for any Belongs_To_Many
557
-     *                                       items which will be stored in an array on this object
558
-     * @return mixed    index into cache, or just TRUE if the relation is of type Belongs_To (because there's only one
559
-     *                                       related thing, no array)
560
-     * @throws InvalidArgumentException
561
-     * @throws InvalidInterfaceException
562
-     * @throws InvalidDataTypeException
563
-     * @throws EE_Error
564
-     * @throws ReflectionException
565
-     */
566
-    public function cache($relation_name = '', $object_to_cache = null, $cache_id = null)
567
-    {
568
-        // its entirely possible that there IS no related object yet in which case there is nothing to cache.
569
-        if (! $object_to_cache instanceof EE_Base_Class) {
570
-            return false;
571
-        }
572
-        // also get "how" the object is related, or throw an error
573
-        if (! $relationship_to_model = $this->get_model()->related_settings_for($relation_name)) {
574
-            throw new EE_Error(
575
-                sprintf(
576
-                    esc_html__('There is no relationship to %s on a %s. Cannot cache it', 'event_espresso'),
577
-                    $relation_name,
578
-                    get_class($this)
579
-                )
580
-            );
581
-        }
582
-        // how many things are related ?
583
-        if ($relationship_to_model instanceof EE_Belongs_To_Relation) {
584
-            // if it's a "belongs to" relationship, then there's only one related model object
585
-            // eg, if this is a registration, there's only 1 attendee for it
586
-            // so for these model objects just set it to be cached
587
-            $this->_model_relations[ $relation_name ] = $object_to_cache;
588
-            $return                                   = true;
589
-        } else {
590
-            // otherwise, this is the "many" side of a one to many relationship,
591
-            // so we'll add the object to the array of related objects for that type.
592
-            // eg: if this is an event, there are many registrations for that event,
593
-            // so we cache the registrations in an array
594
-            if (! is_array($this->_model_relations[ $relation_name ])) {
595
-                // if for some reason, the cached item is a model object,
596
-                // then stick that in the array, otherwise start with an empty array
597
-                $this->_model_relations[ $relation_name ] =
598
-                    $this->_model_relations[ $relation_name ] instanceof EE_Base_Class
599
-                        ? [$this->_model_relations[ $relation_name ]]
600
-                        : [];
601
-            }
602
-            // first check for a cache_id which is normally empty
603
-            if (! empty($cache_id)) {
604
-                // if the cache_id exists, then it means we are purposely trying to cache this
605
-                // with a known key that can then be used to retrieve the object later on
606
-                $this->_model_relations[ $relation_name ][ $cache_id ] = $object_to_cache;
607
-                $return                                                = $cache_id;
608
-            } elseif ($object_to_cache->ID()) {
609
-                // OR the cached object originally came from the db, so let's just use it's PK for an ID
610
-                $this->_model_relations[ $relation_name ][ $object_to_cache->ID() ] = $object_to_cache;
611
-                $return                                                             = $object_to_cache->ID();
612
-            } else {
613
-                // OR it's a new object with no ID, so just throw it in the array with an auto-incremented ID
614
-                $this->_model_relations[ $relation_name ][] = $object_to_cache;
615
-                // move the internal pointer to the end of the array
616
-                end($this->_model_relations[ $relation_name ]);
617
-                // and grab the key so that we can return it
618
-                $return = key($this->_model_relations[ $relation_name ]);
619
-            }
620
-        }
621
-        return $return;
622
-    }
623
-
624
-
625
-    /**
626
-     * For adding an item to the cached_properties property.
627
-     *
628
-     * @access protected
629
-     * @param string      $fieldname the property item the corresponding value is for.
630
-     * @param mixed       $value     The value we are caching.
631
-     * @param string|null $cache_type
632
-     * @return void
633
-     * @throws ReflectionException
634
-     * @throws InvalidArgumentException
635
-     * @throws InvalidInterfaceException
636
-     * @throws InvalidDataTypeException
637
-     * @throws EE_Error
638
-     */
639
-    protected function _set_cached_property($fieldname, $value, $cache_type = null)
640
-    {
641
-        // first make sure this property exists
642
-        $this->get_model()->field_settings_for($fieldname);
643
-        $cache_type = empty($cache_type) ? 'standard' : $cache_type;
644
-
645
-        $this->_cached_properties[ $fieldname ][ $cache_type ] = $value;
646
-    }
647
-
648
-
649
-    /**
650
-     * This returns the value cached property if it exists OR the actual property value if the cache doesn't exist.
651
-     * This also SETS the cache if we return the actual property!
652
-     *
653
-     * @param string $fieldname        the name of the property we're trying to retrieve
654
-     * @param bool   $pretty
655
-     * @param string $extra_cache_ref  This allows the user to specify an extra cache ref for the given property
656
-     *                                 (in cases where the same property may be used for different outputs
657
-     *                                 - i.e. datetime, money etc.)
658
-     *                                 It can also accept certain pre-defined "schema" strings
659
-     *                                 to define how to output the property.
660
-     *                                 see the field's prepare_for_pretty_echoing for what strings can be used
661
-     * @return mixed                   whatever the value for the property is we're retrieving
662
-     * @throws ReflectionException
663
-     * @throws InvalidArgumentException
664
-     * @throws InvalidInterfaceException
665
-     * @throws InvalidDataTypeException
666
-     * @throws EE_Error
667
-     */
668
-    protected function _get_cached_property($fieldname, $pretty = false, $extra_cache_ref = null)
669
-    {
670
-        // verify the field exists
671
-        $model = $this->get_model();
672
-        $model->field_settings_for($fieldname);
673
-        $cache_type = $pretty ? 'pretty' : 'standard';
674
-        $cache_type .= ! empty($extra_cache_ref) ? '_' . $extra_cache_ref : '';
675
-        if (isset($this->_cached_properties[ $fieldname ][ $cache_type ])) {
676
-            return $this->_cached_properties[ $fieldname ][ $cache_type ];
677
-        }
678
-        $value = $this->_get_fresh_property($fieldname, $pretty, $extra_cache_ref);
679
-        $this->_set_cached_property($fieldname, $value, $cache_type);
680
-        return $value;
681
-    }
682
-
683
-
684
-    /**
685
-     * If the cache didn't fetch the needed item, this fetches it.
686
-     *
687
-     * @param string $fieldname
688
-     * @param bool   $pretty
689
-     * @param string $extra_cache_ref
690
-     * @return mixed
691
-     * @throws InvalidArgumentException
692
-     * @throws InvalidInterfaceException
693
-     * @throws InvalidDataTypeException
694
-     * @throws EE_Error
695
-     * @throws ReflectionException
696
-     */
697
-    protected function _get_fresh_property($fieldname, $pretty = false, $extra_cache_ref = null)
698
-    {
699
-        $field_obj = $this->get_model()->field_settings_for($fieldname);
700
-        // If this is an EE_Datetime_Field we need to make sure timezone, formats, and output are correct
701
-        if ($field_obj instanceof EE_Datetime_Field) {
702
-            $this->_prepare_datetime_field($field_obj, $pretty, $extra_cache_ref);
703
-        }
704
-        if (! isset($this->_fields[ $fieldname ])) {
705
-            $this->_fields[ $fieldname ] = null;
706
-        }
707
-        return $pretty
708
-            ? $field_obj->prepare_for_pretty_echoing($this->_fields[ $fieldname ], $extra_cache_ref)
709
-            : $field_obj->prepare_for_get($this->_fields[ $fieldname ]);
710
-    }
711
-
712
-
713
-    /**
714
-     * set timezone, formats, and output for EE_Datetime_Field objects
715
-     *
716
-     * @param EE_Datetime_Field $datetime_field
717
-     * @param bool              $pretty
718
-     * @param null              $date_or_time
719
-     * @return void
720
-     * @throws InvalidArgumentException
721
-     * @throws InvalidInterfaceException
722
-     * @throws InvalidDataTypeException
723
-     */
724
-    protected function _prepare_datetime_field(
725
-        EE_Datetime_Field $datetime_field,
726
-        $pretty = false,
727
-        $date_or_time = null
728
-    ) {
729
-        $datetime_field->set_timezone($this->_timezone);
730
-        $datetime_field->set_date_format($this->_dt_frmt, $pretty);
731
-        $datetime_field->set_time_format($this->_tm_frmt, $pretty);
732
-        // set the output returned
733
-        switch ($date_or_time) {
734
-            case 'D':
735
-                $datetime_field->set_date_time_output('date');
736
-                break;
737
-            case 'T':
738
-                $datetime_field->set_date_time_output('time');
739
-                break;
740
-            default:
741
-                $datetime_field->set_date_time_output();
742
-        }
743
-    }
744
-
745
-
746
-    /**
747
-     * This just takes care of clearing out the cached_properties
748
-     *
749
-     * @return void
750
-     */
751
-    protected function _clear_cached_properties()
752
-    {
753
-        $this->_cached_properties = [];
754
-    }
755
-
756
-
757
-    /**
758
-     * This just clears out ONE property if it exists in the cache
759
-     *
760
-     * @param string $property_name the property to remove if it exists (from the _cached_properties array)
761
-     * @return void
762
-     */
763
-    protected function _clear_cached_property($property_name)
764
-    {
765
-        if (isset($this->_cached_properties[ $property_name ])) {
766
-            unset($this->_cached_properties[ $property_name ]);
767
-        }
768
-    }
769
-
770
-
771
-    /**
772
-     * Ensures that this related thing is a model object.
773
-     *
774
-     * @param mixed  $object_or_id EE_base_Class/int/string either a related model object, or its ID
775
-     * @param string $model_name   name of the related thing, eg 'Attendee',
776
-     * @return EE_Base_Class
777
-     * @throws ReflectionException
778
-     * @throws InvalidArgumentException
779
-     * @throws InvalidInterfaceException
780
-     * @throws InvalidDataTypeException
781
-     * @throws EE_Error
782
-     */
783
-    protected function ensure_related_thing_is_model_obj($object_or_id, $model_name)
784
-    {
785
-        $other_model_instance = self::_get_model_instance_with_name(
786
-            self::_get_model_classname($model_name),
787
-            $this->_timezone
788
-        );
789
-        return $other_model_instance->ensure_is_obj($object_or_id);
790
-    }
791
-
792
-
793
-    /**
794
-     * Forgets the cached model of the given relation Name. So the next time we request it,
795
-     * we will fetch it again from the database. (Handy if you know it's changed somehow).
796
-     * If a specific object is supplied, and the relationship to it is either a HasMany or HABTM,
797
-     * then only remove that one object from our cached array. Otherwise, clear the entire list
798
-     *
799
-     * @param string $relation_name                        one of the keys in the _model_relations array on the model.
800
-     *                                                     Eg 'Registration'
801
-     * @param mixed  $object_to_remove_or_index_into_array or an index into the array of cached things, or NULL
802
-     *                                                     if you intend to use $clear_all = TRUE, or the relation only
803
-     *                                                     has 1 object anyway (ie, it's a BelongsToRelation)
804
-     * @param bool   $clear_all                            This flags clearing the entire cache relation property if
805
-     *                                                     this is HasMany or HABTM.
806
-     * @return EE_Base_Class|bool                          entity that was cleared from the cache,
807
-     *                                                     or true if we requested to remove a relation from all
808
-     *                                                     or false if entity was never cached anyway.
809
-     * @throws InvalidArgumentException
810
-     * @throws InvalidInterfaceException
811
-     * @throws InvalidDataTypeException
812
-     * @throws EE_Error
813
-     * @throws ReflectionException
814
-     */
815
-    public function clear_cache($relation_name, $object_to_remove_or_index_into_array = null, $clear_all = false)
816
-    {
817
-        $relationship_to_model = $this->get_model()->related_settings_for($relation_name);
818
-        $index_in_cache        = '';
819
-        if (! $relationship_to_model) {
820
-            throw new EE_Error(
821
-                sprintf(
822
-                    esc_html__('There is no relationship to %s on a %s. Cannot clear that cache', 'event_espresso'),
823
-                    $relation_name,
824
-                    get_class($this)
825
-                )
826
-            );
827
-        }
828
-        if ($clear_all) {
829
-            $obj_removed                              = true;
830
-            $this->_model_relations[ $relation_name ] = null;
831
-        } elseif ($relationship_to_model instanceof EE_Belongs_To_Relation) {
832
-            $obj_removed                              = $this->_model_relations[ $relation_name ];
833
-            $this->_model_relations[ $relation_name ] = null;
834
-        } else {
835
-            if (
836
-                $object_to_remove_or_index_into_array instanceof EE_Base_Class
837
-                && $object_to_remove_or_index_into_array->ID()
838
-            ) {
839
-                $index_in_cache = $object_to_remove_or_index_into_array->ID();
840
-                if (
841
-                    is_array($this->_model_relations[ $relation_name ])
842
-                    && ! isset($this->_model_relations[ $relation_name ][ $index_in_cache ])
843
-                ) {
844
-                    $index_found_at = null;
845
-                    // find this object in the array even though it has a different key
846
-                    foreach ($this->_model_relations[ $relation_name ] as $index => $obj) {
847
-                        /** @noinspection TypeUnsafeComparisonInspection */
848
-                        if (
849
-                            $obj instanceof EE_Base_Class
850
-                            && (
851
-                                $obj == $object_to_remove_or_index_into_array
852
-                                || $obj->ID() === $object_to_remove_or_index_into_array->ID()
853
-                            )
854
-                        ) {
855
-                            $index_found_at = $index;
856
-                            break;
857
-                        }
858
-                    }
859
-                    if ($index_found_at) {
860
-                        $index_in_cache = $index_found_at;
861
-                    } else {
862
-                        // it wasn't found. huh. well obviously it doesn't need to be removed from teh cache
863
-                        // if it wasn't in it to begin with. So we're done
864
-                        return $object_to_remove_or_index_into_array;
865
-                    }
866
-                }
867
-            } elseif ($object_to_remove_or_index_into_array instanceof EE_Base_Class) {
868
-                // so they provided a model object, but it's not yet saved to the DB... so let's go hunting for it!
869
-                foreach ($this->get_all_from_cache($relation_name) as $index => $potentially_obj_we_want) {
870
-                    /** @noinspection TypeUnsafeComparisonInspection */
871
-                    if ($potentially_obj_we_want == $object_to_remove_or_index_into_array) {
872
-                        $index_in_cache = $index;
873
-                    }
874
-                }
875
-            } else {
876
-                $index_in_cache = $object_to_remove_or_index_into_array;
877
-            }
878
-            // supposedly we've found it. But it could just be that the client code
879
-            // provided a bad index/object
880
-            if (isset($this->_model_relations[ $relation_name ][ $index_in_cache ])) {
881
-                $obj_removed = $this->_model_relations[ $relation_name ][ $index_in_cache ];
882
-                unset($this->_model_relations[ $relation_name ][ $index_in_cache ]);
883
-            } else {
884
-                // that thing was never cached anyway.
885
-                $obj_removed = false;
886
-            }
887
-        }
888
-        return $obj_removed;
889
-    }
890
-
891
-
892
-    /**
893
-     * update_cache_after_object_save
894
-     * Allows a cached item to have it's cache ID (within the array of cached items) reset using the new ID it has
895
-     * obtained after being saved to the db
896
-     *
897
-     * @param string        $relation_name      - the type of object that is cached
898
-     * @param EE_Base_Class $newly_saved_object - the newly saved object to be re-cached
899
-     * @param string        $current_cache_id   - the ID that was used when originally caching the object
900
-     * @return boolean TRUE on success, FALSE on fail
901
-     * @throws ReflectionException
902
-     * @throws InvalidArgumentException
903
-     * @throws InvalidInterfaceException
904
-     * @throws InvalidDataTypeException
905
-     * @throws EE_Error
906
-     */
907
-    public function update_cache_after_object_save(
908
-        $relation_name,
909
-        EE_Base_Class $newly_saved_object,
910
-        $current_cache_id = ''
911
-    ) {
912
-        // verify that incoming object is of the correct type
913
-        $obj_class = 'EE_' . $relation_name;
914
-        if ($newly_saved_object instanceof $obj_class) {
915
-            /* @type EE_Base_Class $newly_saved_object */
916
-            // now get the type of relation
917
-            $relationship_to_model = $this->get_model()->related_settings_for($relation_name);
918
-            // if this is a 1:1 relationship
919
-            if ($relationship_to_model instanceof EE_Belongs_To_Relation) {
920
-                // then just replace the cached object with the newly saved object
921
-                $this->_model_relations[ $relation_name ] = $newly_saved_object;
922
-                return true;
923
-                // or if it's some kind of sordid feral polyamorous relationship...
924
-            }
925
-            if (
926
-                is_array($this->_model_relations[ $relation_name ])
927
-                && isset($this->_model_relations[ $relation_name ][ $current_cache_id ])
928
-            ) {
929
-                // then remove the current cached item
930
-                unset($this->_model_relations[ $relation_name ][ $current_cache_id ]);
931
-                // and cache the newly saved object using it's new ID
932
-                $this->_model_relations[ $relation_name ][ $newly_saved_object->ID() ] = $newly_saved_object;
933
-                return true;
934
-            }
935
-        }
936
-        return false;
937
-    }
938
-
939
-
940
-    /**
941
-     * Fetches a single EE_Base_Class on that relation. (If the relation is of type
942
-     * BelongsTo, it will only ever have 1 object. However, other relations could have an array of objects)
943
-     *
944
-     * @param string $relation_name
945
-     * @return EE_Base_Class
946
-     */
947
-    public function get_one_from_cache($relation_name)
948
-    {
949
-        $cached_array_or_object = $this->_model_relations[ $relation_name ] ?? null;
950
-        if (is_array($cached_array_or_object)) {
951
-            return array_shift($cached_array_or_object);
952
-        }
953
-        return $cached_array_or_object;
954
-    }
955
-
956
-
957
-    /**
958
-     * Fetches a single EE_Base_Class on that relation. (If the relation is of type
959
-     * BelongsTo, it will only ever have 1 object. However, other relations could have an array of objects)
960
-     *
961
-     * @param string $relation_name
962
-     * @return EE_Base_Class[] NOT necessarily indexed by primary keys
963
-     * @throws InvalidArgumentException
964
-     * @throws InvalidInterfaceException
965
-     * @throws InvalidDataTypeException
966
-     * @throws EE_Error
967
-     * @throws ReflectionException
968
-     */
969
-    public function get_all_from_cache($relation_name)
970
-    {
971
-        $objects = $this->_model_relations[ $relation_name ] ?? [];
972
-        // if the result is not an array, but exists, make it an array
973
-        $objects = is_array($objects)
974
-            ? $objects
975
-            : [$objects];
976
-        // bugfix for https://events.codebasehq.com/projects/event-espresso/tickets/7143
977
-        // basically, if this model object was stored in the session, and these cached model objects
978
-        // already have IDs, let's make sure they're in their model's entity mapper
979
-        // otherwise we will have duplicates next time we call
980
-        // EE_Registry::instance()->load_model( $relation_name )->get_one_by_ID( $result->ID() );
981
-        $model = EE_Registry::instance()->load_model($relation_name);
982
-        foreach ($objects as $model_object) {
983
-            if ($model instanceof EEM_Base && $model_object instanceof EE_Base_Class) {
984
-                // ensure its in the map if it has an ID; otherwise it will be added to the map when its saved
985
-                if ($model_object->ID()) {
986
-                    $model->add_to_entity_map($model_object);
987
-                }
988
-            } else {
989
-                throw new EE_Error(
990
-                    sprintf(
991
-                        esc_html__(
992
-                            'Error retrieving related model objects. Either $1%s is not a model or $2%s is not a model object',
993
-                            'event_espresso'
994
-                        ),
995
-                        $relation_name,
996
-                        gettype($model_object)
997
-                    )
998
-                );
999
-            }
1000
-        }
1001
-        return $objects;
1002
-    }
1003
-
1004
-
1005
-    /**
1006
-     * Returns the next x number of EE_Base_Class objects in sequence from this object as found in the database
1007
-     * matching the given query conditions.
1008
-     *
1009
-     * @param null  $field_to_order_by  What field is being used as the reference point.
1010
-     * @param int   $limit              How many objects to return.
1011
-     * @param array $query_params       Any additional conditions on the query.
1012
-     * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1013
-     *                                  you can indicate just the columns you want returned
1014
-     * @return array|EE_Base_Class[]
1015
-     * @throws ReflectionException
1016
-     * @throws InvalidArgumentException
1017
-     * @throws InvalidInterfaceException
1018
-     * @throws InvalidDataTypeException
1019
-     * @throws EE_Error
1020
-     */
1021
-    public function next_x($field_to_order_by = null, $limit = 1, $query_params = [], $columns_to_select = null)
1022
-    {
1023
-        $model         = $this->get_model();
1024
-        $field         = empty($field_to_order_by) && $model->has_primary_key_field()
1025
-            ? $model->get_primary_key_field()->get_name()
1026
-            : $field_to_order_by;
1027
-        $current_value = ! empty($field)
1028
-            ? $this->get($field)
1029
-            : null;
1030
-        if (empty($field) || empty($current_value)) {
1031
-            return [];
1032
-        }
1033
-        return $model->next_x($current_value, $field, $limit, $query_params, $columns_to_select);
1034
-    }
1035
-
1036
-
1037
-    /**
1038
-     * Returns the previous x number of EE_Base_Class objects in sequence from this object as found in the database
1039
-     * matching the given query conditions.
1040
-     *
1041
-     * @param null  $field_to_order_by  What field is being used as the reference point.
1042
-     * @param int   $limit              How many objects to return.
1043
-     * @param array $query_params       Any additional conditions on the query.
1044
-     * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1045
-     *                                  you can indicate just the columns you want returned
1046
-     * @return array|EE_Base_Class[]
1047
-     * @throws ReflectionException
1048
-     * @throws InvalidArgumentException
1049
-     * @throws InvalidInterfaceException
1050
-     * @throws InvalidDataTypeException
1051
-     * @throws EE_Error
1052
-     */
1053
-    public function previous_x(
1054
-        $field_to_order_by = null,
1055
-        $limit = 1,
1056
-        $query_params = [],
1057
-        $columns_to_select = null
1058
-    ) {
1059
-        $model         = $this->get_model();
1060
-        $field         = empty($field_to_order_by) && $model->has_primary_key_field()
1061
-            ? $model->get_primary_key_field()->get_name()
1062
-            : $field_to_order_by;
1063
-        $current_value = ! empty($field) ? $this->get($field) : null;
1064
-        if (empty($field) || empty($current_value)) {
1065
-            return [];
1066
-        }
1067
-        return $model->previous_x($current_value, $field, $limit, $query_params, $columns_to_select);
1068
-    }
1069
-
1070
-
1071
-    /**
1072
-     * Returns the next EE_Base_Class object in sequence from this object as found in the database
1073
-     * matching the given query conditions.
1074
-     *
1075
-     * @param null  $field_to_order_by  What field is being used as the reference point.
1076
-     * @param array $query_params       Any additional conditions on the query.
1077
-     * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1078
-     *                                  you can indicate just the columns you want returned
1079
-     * @return array|EE_Base_Class
1080
-     * @throws ReflectionException
1081
-     * @throws InvalidArgumentException
1082
-     * @throws InvalidInterfaceException
1083
-     * @throws InvalidDataTypeException
1084
-     * @throws EE_Error
1085
-     */
1086
-    public function next($field_to_order_by = null, $query_params = [], $columns_to_select = null)
1087
-    {
1088
-        $model         = $this->get_model();
1089
-        $field         = empty($field_to_order_by) && $model->has_primary_key_field()
1090
-            ? $model->get_primary_key_field()->get_name()
1091
-            : $field_to_order_by;
1092
-        $current_value = ! empty($field) ? $this->get($field) : null;
1093
-        if (empty($field) || empty($current_value)) {
1094
-            return [];
1095
-        }
1096
-        return $model->next($current_value, $field, $query_params, $columns_to_select);
1097
-    }
1098
-
1099
-
1100
-    /**
1101
-     * Returns the previous EE_Base_Class object in sequence from this object as found in the database
1102
-     * matching the given query conditions.
1103
-     *
1104
-     * @param null  $field_to_order_by  What field is being used as the reference point.
1105
-     * @param array $query_params       Any additional conditions on the query.
1106
-     * @param null  $columns_to_select  If left null, then an EE_Base_Class object is returned, otherwise
1107
-     *                                  you can indicate just the column you want returned
1108
-     * @return array|EE_Base_Class
1109
-     * @throws ReflectionException
1110
-     * @throws InvalidArgumentException
1111
-     * @throws InvalidInterfaceException
1112
-     * @throws InvalidDataTypeException
1113
-     * @throws EE_Error
1114
-     */
1115
-    public function previous($field_to_order_by = null, $query_params = [], $columns_to_select = null)
1116
-    {
1117
-        $model         = $this->get_model();
1118
-        $field         = empty($field_to_order_by) && $model->has_primary_key_field()
1119
-            ? $model->get_primary_key_field()->get_name()
1120
-            : $field_to_order_by;
1121
-        $current_value = ! empty($field) ? $this->get($field) : null;
1122
-        if (empty($field) || empty($current_value)) {
1123
-            return [];
1124
-        }
1125
-        return $model->previous($current_value, $field, $query_params, $columns_to_select);
1126
-    }
1127
-
1128
-
1129
-    /**
1130
-     * verifies that the specified field is of the correct type
1131
-     *
1132
-     * @param string $field_name
1133
-     * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1134
-     *                                (in cases where the same property may be used for different outputs
1135
-     *                                - i.e. datetime, money etc.)
1136
-     * @return mixed
1137
-     * @throws ReflectionException
1138
-     * @throws InvalidArgumentException
1139
-     * @throws InvalidInterfaceException
1140
-     * @throws InvalidDataTypeException
1141
-     * @throws EE_Error
1142
-     */
1143
-    public function get($field_name, $extra_cache_ref = null)
1144
-    {
1145
-        return $this->_get_cached_property($field_name, false, $extra_cache_ref);
1146
-    }
1147
-
1148
-
1149
-    /**
1150
-     * This method simply returns the RAW unprocessed value for the given property in this class
1151
-     *
1152
-     * @param string $field_name A valid fieldname
1153
-     * @return mixed              Whatever the raw value stored on the property is.
1154
-     * @throws ReflectionException
1155
-     * @throws InvalidArgumentException
1156
-     * @throws InvalidInterfaceException
1157
-     * @throws InvalidDataTypeException
1158
-     * @throws EE_Error if fieldSettings is misconfigured or the field doesn't exist.
1159
-     */
1160
-    public function get_raw($field_name)
1161
-    {
1162
-        $field_settings = $this->get_model()->field_settings_for($field_name);
1163
-        return $field_settings instanceof EE_Datetime_Field && $this->_fields[ $field_name ] instanceof DateTime
1164
-            ? $this->_fields[ $field_name ]->format('U')
1165
-            : $this->_fields[ $field_name ];
1166
-    }
1167
-
1168
-
1169
-    /**
1170
-     * This is used to return the internal DateTime object used for a field that is a
1171
-     * EE_Datetime_Field.
1172
-     *
1173
-     * @param string $field_name               The field name retrieving the DateTime object.
1174
-     * @return mixed null | false | DateTime  If the requested field is NOT a EE_Datetime_Field then
1175
-     * @throws EE_Error an error is set and false returned.  If the field IS an
1176
-     *                                         EE_Datetime_Field and but the field value is null, then
1177
-     *                                         just null is returned (because that indicates that likely
1178
-     *                                         this field is nullable).
1179
-     * @throws InvalidArgumentException
1180
-     * @throws InvalidDataTypeException
1181
-     * @throws InvalidInterfaceException
1182
-     * @throws ReflectionException
1183
-     */
1184
-    public function get_DateTime_object($field_name)
1185
-    {
1186
-        $field_settings = $this->get_model()->field_settings_for($field_name);
1187
-        if (! $field_settings instanceof EE_Datetime_Field) {
1188
-            EE_Error::add_error(
1189
-                sprintf(
1190
-                    esc_html__(
1191
-                        'The field %s is not an EE_Datetime_Field field.  There is no DateTime object stored on this field type.',
1192
-                        'event_espresso'
1193
-                    ),
1194
-                    $field_name
1195
-                ),
1196
-                __FILE__,
1197
-                __FUNCTION__,
1198
-                __LINE__
1199
-            );
1200
-            return false;
1201
-        }
1202
-        return isset($this->_fields[ $field_name ]) && $this->_fields[ $field_name ] instanceof DateTime
1203
-            ? clone $this->_fields[ $field_name ]
1204
-            : null;
1205
-    }
1206
-
1207
-
1208
-    /**
1209
-     * To be used in template to immediately echo out the value, and format it for output.
1210
-     * Eg, should call stripslashes and whatnot before echoing
1211
-     *
1212
-     * @param string $field_name      the name of the field as it appears in the DB
1213
-     * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1214
-     *                                (in cases where the same property may be used for different outputs
1215
-     *                                - i.e. datetime, money etc.)
1216
-     * @return void
1217
-     * @throws ReflectionException
1218
-     * @throws InvalidArgumentException
1219
-     * @throws InvalidInterfaceException
1220
-     * @throws InvalidDataTypeException
1221
-     * @throws EE_Error
1222
-     */
1223
-    public function e($field_name, $extra_cache_ref = null)
1224
-    {
1225
-        echo wp_kses($this->get_pretty($field_name, $extra_cache_ref), AllowedTags::getWithFormTags());
1226
-    }
1227
-
1228
-
1229
-    /**
1230
-     * Exactly like e(), echoes out the field, but sets its schema to 'form_input', so that it
1231
-     * can be easily used as the value of form input.
1232
-     *
1233
-     * @param string $field_name
1234
-     * @return void
1235
-     * @throws ReflectionException
1236
-     * @throws InvalidArgumentException
1237
-     * @throws InvalidInterfaceException
1238
-     * @throws InvalidDataTypeException
1239
-     * @throws EE_Error
1240
-     */
1241
-    public function f($field_name)
1242
-    {
1243
-        $this->e($field_name, 'form_input');
1244
-    }
1245
-
1246
-
1247
-    /**
1248
-     * Same as `f()` but just returns the value instead of echoing it
1249
-     *
1250
-     * @param string $field_name
1251
-     * @return string
1252
-     * @throws ReflectionException
1253
-     * @throws InvalidArgumentException
1254
-     * @throws InvalidInterfaceException
1255
-     * @throws InvalidDataTypeException
1256
-     * @throws EE_Error
1257
-     */
1258
-    public function get_f($field_name)
1259
-    {
1260
-        return (string) $this->get_pretty($field_name, 'form_input');
1261
-    }
1262
-
1263
-
1264
-    /**
1265
-     * Gets a pretty view of the field's value. $extra_cache_ref can specify different formats for this.
1266
-     * The $extra_cache_ref will be passed to the model field's prepare_for_pretty_echoing, so consult the field's class
1267
-     * to see what options are available.
1268
-     *
1269
-     * @param string $field_name
1270
-     * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1271
-     *                                (in cases where the same property may be used for different outputs
1272
-     *                                - i.e. datetime, money etc.)
1273
-     * @return mixed
1274
-     * @throws ReflectionException
1275
-     * @throws InvalidArgumentException
1276
-     * @throws InvalidInterfaceException
1277
-     * @throws InvalidDataTypeException
1278
-     * @throws EE_Error
1279
-     */
1280
-    public function get_pretty($field_name, $extra_cache_ref = null)
1281
-    {
1282
-        return $this->_get_cached_property($field_name, true, $extra_cache_ref);
1283
-    }
1284
-
1285
-
1286
-    /**
1287
-     * This simply returns the datetime for the given field name
1288
-     * Note: this protected function is called by the wrapper get_date or get_time or get_datetime functions
1289
-     * (and the equivalent e_date, e_time, e_datetime).
1290
-     *
1291
-     * @access   protected
1292
-     * @param string      $field_name   Field on the instantiated EE_Base_Class child object
1293
-     * @param string|null $date_format  valid datetime format used for date
1294
-     *                                  (if '' then we just use the default on the field,
1295
-     *                                  if NULL we use the last-used format)
1296
-     * @param string|null $time_format  Same as above except this is for time format
1297
-     * @param string|null $date_or_time if NULL then both are returned, otherwise "D" = only date and "T" = only time.
1298
-     * @param bool|null   $echo         Whether the datetime is pretty echoing or just returned using vanilla get
1299
-     * @return string|bool|EE_Error string on success, FALSE on fail, or EE_Error Exception is thrown
1300
-     *                                  if field is not a valid dtt field, or void if echoing
1301
-     * @throws EE_Error
1302
-     * @throws ReflectionException
1303
-     */
1304
-    protected function _get_datetime(
1305
-        string $field_name,
1306
-        ?string $date_format = '',
1307
-        ?string $time_format = '',
1308
-        ?string $date_or_time = '',
1309
-        ?bool $echo = false
1310
-    ) {
1311
-        // clear cached property
1312
-        $this->_clear_cached_property($field_name);
1313
-        // reset format properties because they are used in get()
1314
-        $this->_dt_frmt = $date_format ?: $this->_dt_frmt;
1315
-        $this->_tm_frmt = $time_format ?: $this->_tm_frmt;
1316
-        if ($echo) {
1317
-            $this->e($field_name, $date_or_time);
1318
-            return '';
1319
-        }
1320
-        return $this->get($field_name, $date_or_time);
1321
-    }
1322
-
1323
-
1324
-    /**
1325
-     * below are wrapper functions for the various datetime outputs that can be obtained for JUST returning the date
1326
-     * portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1327
-     * other echoes the pretty value for dtt)
1328
-     *
1329
-     * @param string $field_name name of model object datetime field holding the value
1330
-     * @param string $format     format for the date returned (if NULL we use default in dt_frmt property)
1331
-     * @return string            datetime value formatted
1332
-     * @throws ReflectionException
1333
-     * @throws InvalidArgumentException
1334
-     * @throws InvalidInterfaceException
1335
-     * @throws InvalidDataTypeException
1336
-     * @throws EE_Error
1337
-     */
1338
-    public function get_date($field_name, $format = '')
1339
-    {
1340
-        return $this->_get_datetime($field_name, $format, null, 'D');
1341
-    }
1342
-
1343
-
1344
-    /**
1345
-     * @param        $field_name
1346
-     * @param string $format
1347
-     * @throws ReflectionException
1348
-     * @throws InvalidArgumentException
1349
-     * @throws InvalidInterfaceException
1350
-     * @throws InvalidDataTypeException
1351
-     * @throws EE_Error
1352
-     */
1353
-    public function e_date($field_name, $format = '')
1354
-    {
1355
-        $this->_get_datetime($field_name, $format, null, 'D', true);
1356
-    }
1357
-
1358
-
1359
-    /**
1360
-     * below are wrapper functions for the various datetime outputs that can be obtained for JUST returning the time
1361
-     * portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1362
-     * other echoes the pretty value for dtt)
1363
-     *
1364
-     * @param string $field_name name of model object datetime field holding the value
1365
-     * @param string $format     format for the time returned ( if NULL we use default in tm_frmt property)
1366
-     * @return string             datetime value formatted
1367
-     * @throws ReflectionException
1368
-     * @throws InvalidArgumentException
1369
-     * @throws InvalidInterfaceException
1370
-     * @throws InvalidDataTypeException
1371
-     * @throws EE_Error
1372
-     */
1373
-    public function get_time($field_name, $format = '')
1374
-    {
1375
-        return $this->_get_datetime($field_name, null, $format, 'T');
1376
-    }
1377
-
1378
-
1379
-    /**
1380
-     * @param        $field_name
1381
-     * @param string $format
1382
-     * @throws ReflectionException
1383
-     * @throws InvalidArgumentException
1384
-     * @throws InvalidInterfaceException
1385
-     * @throws InvalidDataTypeException
1386
-     * @throws EE_Error
1387
-     */
1388
-    public function e_time($field_name, $format = '')
1389
-    {
1390
-        $this->_get_datetime($field_name, null, $format, 'T', true);
1391
-    }
1392
-
1393
-
1394
-    /**
1395
-     * below are wrapper functions for the various datetime outputs that can be obtained for returning the date AND
1396
-     * time portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1397
-     * other echoes the pretty value for dtt)
1398
-     *
1399
-     * @param string $field_name  name of model object datetime field holding the value
1400
-     * @param string $date_format format for the date returned (if NULL we use default in dt_frmt property)
1401
-     * @param string $time_format format for the time returned (if NULL we use default in tm_frmt property)
1402
-     * @return string             datetime value formatted
1403
-     * @throws ReflectionException
1404
-     * @throws InvalidArgumentException
1405
-     * @throws InvalidInterfaceException
1406
-     * @throws InvalidDataTypeException
1407
-     * @throws EE_Error
1408
-     */
1409
-    public function get_datetime($field_name, $date_format = '', $time_format = '')
1410
-    {
1411
-        return $this->_get_datetime($field_name, $date_format, $time_format);
1412
-    }
1413
-
1414
-
1415
-    /**
1416
-     * @param string $field_name
1417
-     * @param string $date_format
1418
-     * @param string $time_format
1419
-     * @throws ReflectionException
1420
-     * @throws InvalidArgumentException
1421
-     * @throws InvalidInterfaceException
1422
-     * @throws InvalidDataTypeException
1423
-     * @throws EE_Error
1424
-     */
1425
-    public function e_datetime($field_name, $date_format = '', $time_format = '')
1426
-    {
1427
-        $this->_get_datetime($field_name, $date_format, $time_format, null, true);
1428
-    }
1429
-
1430
-
1431
-    /**
1432
-     * Get the i8ln value for a date using the WordPress @param string $field_name The EE_Datetime_Field reference for
1433
-     *                           the date being retrieved.
1434
-     *
1435
-     * @param string $format     PHP valid date/time string format.  If none is provided then the internal set format
1436
-     *                           on the object will be used.
1437
-     * @return string Date and time string in set locale or false if no field exists for the given
1438
-     * @throws ReflectionException
1439
-     * @throws InvalidArgumentException
1440
-     * @throws InvalidInterfaceException
1441
-     * @throws InvalidDataTypeException
1442
-     * @throws EE_Error
1443
-     *                           field name.
1444
-     * @see date_i18n function.
1445
-     */
1446
-    public function get_i18n_datetime(string $field_name, string $format = ''): string
1447
-    {
1448
-        $format = empty($format) ? $this->_dt_frmt . ' ' . $this->_tm_frmt : $format;
1449
-        return date_i18n(
1450
-            $format,
1451
-            EEH_DTT_Helper::get_timestamp_with_offset(
1452
-                $this->get_raw($field_name),
1453
-                $this->_timezone
1454
-            )
1455
-        );
1456
-    }
1457
-
1458
-
1459
-    /**
1460
-     * This method validates whether the given field name is a valid field on the model object as well as it is of a
1461
-     * type EE_Datetime_Field.  On success there will be returned the field settings.  On fail an EE_Error exception is
1462
-     * thrown.
1463
-     *
1464
-     * @param string $field_name The field name being checked
1465
-     * @return EE_Datetime_Field
1466
-     * @throws InvalidArgumentException
1467
-     * @throws InvalidInterfaceException
1468
-     * @throws InvalidDataTypeException
1469
-     * @throws EE_Error
1470
-     * @throws ReflectionException
1471
-     */
1472
-    protected function _get_dtt_field_settings($field_name)
1473
-    {
1474
-        $field = $this->get_model()->field_settings_for($field_name);
1475
-        // check if field is dtt
1476
-        if ($field instanceof EE_Datetime_Field) {
1477
-            return $field;
1478
-        }
1479
-        throw new EE_Error(
1480
-            sprintf(
1481
-                esc_html__(
1482
-                    'The field name "%s" has been requested for the EE_Base_Class datetime functions and it is not a valid EE_Datetime_Field.  Please check the spelling of the field and make sure it has been setup as a EE_Datetime_Field in the %s model constructor',
1483
-                    'event_espresso'
1484
-                ),
1485
-                $field_name,
1486
-                self::_get_model_classname(get_class($this))
1487
-            )
1488
-        );
1489
-    }
1490
-
1491
-
1492
-
1493
-
1494
-    /**
1495
-     * NOTE ABOUT BELOW:
1496
-     * These convenience date and time setters are for setting date and time independently.  In other words you might
1497
-     * want to change the time on a datetime_field but leave the date the same (or vice versa). IF on the other hand
1498
-     * you want to set both date and time at the same time, you can just use the models default set($fieldname,$value)
1499
-     * method and make sure you send the entire datetime value for setting.
1500
-     */
1501
-    /**
1502
-     * sets the time on a datetime property
1503
-     *
1504
-     * @access protected
1505
-     * @param string|Datetime $time      a valid time string for php datetime functions (or DateTime object)
1506
-     * @param string          $fieldname the name of the field the time is being set on (must match a EE_Datetime_Field)
1507
-     * @throws ReflectionException
1508
-     * @throws InvalidArgumentException
1509
-     * @throws InvalidInterfaceException
1510
-     * @throws InvalidDataTypeException
1511
-     * @throws EE_Error
1512
-     */
1513
-    protected function _set_time_for($time, $fieldname)
1514
-    {
1515
-        $this->_set_date_time('T', $time, $fieldname);
1516
-    }
1517
-
1518
-
1519
-    /**
1520
-     * sets the date on a datetime property
1521
-     *
1522
-     * @access protected
1523
-     * @param string|DateTime $date      a valid date string for php datetime functions ( or DateTime object)
1524
-     * @param string          $fieldname the name of the field the date is being set on (must match a EE_Datetime_Field)
1525
-     * @throws ReflectionException
1526
-     * @throws InvalidArgumentException
1527
-     * @throws InvalidInterfaceException
1528
-     * @throws InvalidDataTypeException
1529
-     * @throws EE_Error
1530
-     */
1531
-    protected function _set_date_for($date, $fieldname)
1532
-    {
1533
-        $this->_set_date_time('D', $date, $fieldname);
1534
-    }
1535
-
1536
-
1537
-    /**
1538
-     * This takes care of setting a date or time independently on a given model object property. This method also
1539
-     * verifies that the given field_name matches a model object property and is for a EE_Datetime_Field field
1540
-     *
1541
-     * @access protected
1542
-     * @param string          $what           "T" for time, 'B' for both, 'D' for Date.
1543
-     * @param string|DateTime $datetime_value A valid Date or Time string (or DateTime object)
1544
-     * @param string          $field_name     the name of the field the date OR time is being set on (must match a
1545
-     *                                        EE_Datetime_Field property)
1546
-     * @throws ReflectionException
1547
-     * @throws InvalidArgumentException
1548
-     * @throws InvalidInterfaceException
1549
-     * @throws InvalidDataTypeException
1550
-     * @throws EE_Error
1551
-     */
1552
-    protected function _set_date_time(string $what, $datetime_value, string $field_name)
1553
-    {
1554
-        $field = $this->_get_dtt_field_settings($field_name);
1555
-        $field->set_timezone($this->_timezone);
1556
-        $field->set_date_format($this->_dt_frmt);
1557
-        $field->set_time_format($this->_tm_frmt);
1558
-        switch ($what) {
1559
-            case 'T':
1560
-                $this->_fields[ $field_name ] = $field->prepare_for_set_with_new_time(
1561
-                    $datetime_value,
1562
-                    $this->_fields[ $field_name ]
1563
-                );
1564
-                $this->_has_changes           = true;
1565
-                break;
1566
-            case 'D':
1567
-                $this->_fields[ $field_name ] = $field->prepare_for_set_with_new_date(
1568
-                    $datetime_value,
1569
-                    $this->_fields[ $field_name ]
1570
-                );
1571
-                $this->_has_changes           = true;
1572
-                break;
1573
-            case 'B':
1574
-                $this->_fields[ $field_name ] = $field->prepare_for_set($datetime_value);
1575
-                $this->_has_changes           = true;
1576
-                break;
1577
-        }
1578
-        $this->_clear_cached_property($field_name);
1579
-    }
1580
-
1581
-
1582
-    /**
1583
-     * This will return a timestamp for the website timezone but ONLY when the current website timezone is different
1584
-     * than the timezone set for the website. NOTE, this currently only works well with methods that return values.  If
1585
-     * you use it with methods that echo values the $_timestamp property may not get reset to its original value and
1586
-     * that could lead to some unexpected results!
1587
-     *
1588
-     * @access public
1589
-     * @param string $field_name               This is the name of the field on the object that contains the date/time
1590
-     *                                         value being returned.
1591
-     * @param string $callback                 must match a valid method in this class (defaults to get_datetime)
1592
-     * @param mixed (array|string) $args       This is the arguments that will be passed to the callback.
1593
-     * @param string $prepend                  You can include something to prepend on the timestamp
1594
-     * @param string $append                   You can include something to append on the timestamp
1595
-     * @return string timestamp
1596
-     * @throws ReflectionException
1597
-     * @throws InvalidArgumentException
1598
-     * @throws InvalidInterfaceException
1599
-     * @throws InvalidDataTypeException
1600
-     * @throws EE_Error
1601
-     */
1602
-    public function display_in_my_timezone(
1603
-        $field_name,
1604
-        $callback = 'get_datetime',
1605
-        $args = null,
1606
-        $prepend = '',
1607
-        $append = ''
1608
-    ) {
1609
-        $timezone = EEH_DTT_Helper::get_timezone();
1610
-        if ($timezone === $this->_timezone) {
1611
-            return '';
1612
-        }
1613
-        $original_timezone = $this->_timezone;
1614
-        $this->set_timezone($timezone);
1615
-        $fn   = (array) $field_name;
1616
-        $args = array_merge($fn, (array) $args);
1617
-        if (! method_exists($this, $callback)) {
1618
-            throw new EE_Error(
1619
-                sprintf(
1620
-                    esc_html__(
1621
-                        'The method named "%s" given as the callback param in "display_in_my_timezone" does not exist.  Please check your spelling',
1622
-                        'event_espresso'
1623
-                    ),
1624
-                    $callback
1625
-                )
1626
-            );
1627
-        }
1628
-        $args   = (array) $args;
1629
-        $return = $prepend . call_user_func_array([$this, $callback], $args) . $append;
1630
-        $this->set_timezone($original_timezone);
1631
-        return $return;
1632
-    }
1633
-
1634
-
1635
-    /**
1636
-     * Deletes this model object.
1637
-     * This calls the `EE_Base_Class::_delete` method.  Child classes wishing to change default behaviour should
1638
-     * override
1639
-     * `EE_Base_Class::_delete` NOT this class.
1640
-     *
1641
-     * @return int
1642
-     * @throws ReflectionException
1643
-     * @throws InvalidArgumentException
1644
-     * @throws InvalidInterfaceException
1645
-     * @throws InvalidDataTypeException
1646
-     * @throws EE_Error
1647
-     */
1648
-    public function delete()
1649
-    {
1650
-        /**
1651
-         * Called just before the `EE_Base_Class::_delete` method call.
1652
-         * Note:
1653
-         * `EE_Base_Class::_delete` might be overridden by child classes so any client code hooking into these actions
1654
-         * should be aware that `_delete` may not always result in a permanent delete.
1655
-         * For example, `EE_Soft_Delete_Base_Class::_delete`
1656
-         * soft deletes (trash) the object and does not permanently delete it.
1657
-         *
1658
-         * @param EE_Base_Class $model_object about to be 'deleted'
1659
-         */
1660
-        do_action('AHEE__EE_Base_Class__delete__before', $this);
1661
-        $deleted = $this->_delete();
1662
-        /**
1663
-         * Called just after the `EE_Base_Class::_delete` method call.
1664
-         * Note:
1665
-         * `EE_Base_Class::_delete` might be overridden by child classes so any client code hooking into these actions
1666
-         * should be aware that `_delete` may not always result in a permanent delete.
1667
-         * For example `EE_Soft_Base_Class::_delete`
1668
-         * soft deletes (trash) the object and does not permanently delete it.
1669
-         *
1670
-         * @param EE_Base_Class $model_object that was just 'deleted'
1671
-         * @param boolean       $deleted
1672
-         */
1673
-        do_action('AHEE__EE_Base_Class__delete__end', $this, $deleted);
1674
-        return $deleted;
1675
-    }
1676
-
1677
-
1678
-    /**
1679
-     * Calls the specific delete method for the instantiated class.
1680
-     * This method is called by the public `EE_Base_Class::delete` method.  Any child classes desiring to override
1681
-     * default functionality for "delete" (which is to call `permanently_delete`) should override this method NOT
1682
-     * `EE_Base_Class::delete`
1683
-     *
1684
-     * @return int
1685
-     * @throws ReflectionException
1686
-     * @throws InvalidArgumentException
1687
-     * @throws InvalidInterfaceException
1688
-     * @throws InvalidDataTypeException
1689
-     * @throws EE_Error
1690
-     */
1691
-    protected function _delete(): int
1692
-    {
1693
-        return $this->delete_permanently();
1694
-    }
1695
-
1696
-
1697
-    /**
1698
-     * Deletes this model object permanently from db
1699
-     * (but keep in mind related models may block the delete and return an error)
1700
-     *
1701
-     * @return int
1702
-     * @throws ReflectionException
1703
-     * @throws InvalidArgumentException
1704
-     * @throws InvalidInterfaceException
1705
-     * @throws InvalidDataTypeException
1706
-     * @throws EE_Error
1707
-     */
1708
-    public function delete_permanently(): int
1709
-    {
1710
-        /**
1711
-         * Called just before HARD deleting a model object
1712
-         *
1713
-         * @param EE_Base_Class $model_object about to be 'deleted'
1714
-         */
1715
-        do_action('AHEE__EE_Base_Class__delete_permanently__before', $this);
1716
-        $model  = $this->get_model();
1717
-        $result = $model->delete_permanently_by_ID($this->ID());
1718
-        $this->refresh_cache_of_related_objects();
1719
-        /**
1720
-         * Called just after HARD deleting a model object
1721
-         *
1722
-         * @param EE_Base_Class $model_object that was just 'deleted'
1723
-         * @param boolean       $result
1724
-         */
1725
-        do_action('AHEE__EE_Base_Class__delete_permanently__end', $this, $result);
1726
-        return $result;
1727
-    }
1728
-
1729
-
1730
-    /**
1731
-     * When this model object is deleted, it may still be cached on related model objects. This clears the cache of
1732
-     * related model objects
1733
-     *
1734
-     * @throws ReflectionException
1735
-     * @throws InvalidArgumentException
1736
-     * @throws InvalidInterfaceException
1737
-     * @throws InvalidDataTypeException
1738
-     * @throws EE_Error
1739
-     */
1740
-    public function refresh_cache_of_related_objects()
1741
-    {
1742
-        $model = $this->get_model();
1743
-        foreach ($model->relation_settings() as $relation_name => $relation_obj) {
1744
-            if (! empty($this->_model_relations[ $relation_name ])) {
1745
-                $related_objects = $this->_model_relations[ $relation_name ];
1746
-                if ($relation_obj instanceof EE_Belongs_To_Relation) {
1747
-                    // this relation only stores a single model object, not an array
1748
-                    // but let's make it consistent
1749
-                    $related_objects = [$related_objects];
1750
-                }
1751
-                foreach ($related_objects as $related_object) {
1752
-                    // only refresh their cache if they're in memory
1753
-                    if ($related_object instanceof EE_Base_Class) {
1754
-                        $related_object->clear_cache(
1755
-                            $model->get_this_model_name(),
1756
-                            $this
1757
-                        );
1758
-                    }
1759
-                }
1760
-            }
1761
-        }
1762
-    }
1763
-
1764
-
1765
-    /**
1766
-     *        Saves this object to the database. An array may be supplied to set some values on this
1767
-     * object just before saving.
1768
-     *
1769
-     * @access public
1770
-     * @param array $set_cols_n_values keys are field names, values are their new values,
1771
-     *                                 if provided during the save() method (often client code will change the fields'
1772
-     *                                 values before calling save)
1773
-     * @return bool|int|string         1 on a successful update
1774
-     *                                 the ID of the new entry on insert
1775
-     *                                 0 on failure or if the model object isn't allowed to persist
1776
-     *                                 (as determined by EE_Base_Class::allow_persist())
1777
-     * @throws InvalidInterfaceException
1778
-     * @throws InvalidDataTypeException
1779
-     * @throws EE_Error
1780
-     * @throws InvalidArgumentException
1781
-     * @throws ReflectionException
1782
-     */
1783
-    public function save($set_cols_n_values = [])
1784
-    {
1785
-        $model = $this->get_model();
1786
-        /**
1787
-         * Filters the fields we're about to save on the model object
1788
-         *
1789
-         * @param array         $set_cols_n_values
1790
-         * @param EE_Base_Class $model_object
1791
-         */
1792
-        $set_cols_n_values = (array) apply_filters(
1793
-            'FHEE__EE_Base_Class__save__set_cols_n_values',
1794
-            $set_cols_n_values,
1795
-            $this
1796
-        );
1797
-        // set attributes as provided in $set_cols_n_values
1798
-        foreach ($set_cols_n_values as $column => $value) {
1799
-            $this->set($column, $value);
1800
-        }
1801
-        // no changes ? then don't do anything
1802
-        if (! $this->_has_changes && $this->ID() && $model->get_primary_key_field()->is_auto_increment()) {
1803
-            return 0;
1804
-        }
1805
-        /**
1806
-         * Saving a model object.
1807
-         * Before we perform a save, this action is fired.
1808
-         *
1809
-         * @param EE_Base_Class $model_object the model object about to be saved.
1810
-         */
1811
-        do_action('AHEE__EE_Base_Class__save__begin', $this);
1812
-        if (! $this->allow_persist()) {
1813
-            return 0;
1814
-        }
1815
-        // now get current attribute values
1816
-        $save_cols_n_values = $this->_fields;
1817
-        // if the object already has an ID, update it. Otherwise, insert it
1818
-        // also: change the assumption about values passed to the model NOT being prepare dby the model object.
1819
-        // They have been
1820
-        $old_assumption_concerning_value_preparation = $model
1821
-            ->get_assumption_concerning_values_already_prepared_by_model_object();
1822
-        $model->assume_values_already_prepared_by_model_object(true);
1823
-        // does this model have an autoincrement PK?
1824
-        if ($model->has_primary_key_field()) {
1825
-            if ($model->get_primary_key_field()->is_auto_increment()) {
1826
-                // ok check if it's set, if so: update; if not, insert
1827
-                if (! empty($save_cols_n_values[ $model->primary_key_name() ])) {
1828
-                    $results = $model->update_by_ID($save_cols_n_values, $this->ID());
1829
-                } else {
1830
-                    unset($save_cols_n_values[ $model->primary_key_name() ]);
1831
-                    $results = $model->insert($save_cols_n_values);
1832
-                    if ($results) {
1833
-                        // if successful, set the primary key
1834
-                        // but don't use the normal SET method, because it will check if
1835
-                        // an item with the same ID exists in the mapper & db, then
1836
-                        // will find it in the db (because we just added it) and THAT object
1837
-                        // will get added to the mapper before we can add this one!
1838
-                        // but if we just avoid using the SET method, all that headache can be avoided
1839
-                        $pk_field_name                   = $model->primary_key_name();
1840
-                        $this->_fields[ $pk_field_name ] = $results;
1841
-                        $this->_clear_cached_property($pk_field_name);
1842
-                        $model->add_to_entity_map($this);
1843
-                        $this->_update_cached_related_model_objs_fks();
1844
-                    }
1845
-                }
1846
-            } else {// PK is NOT auto-increment
1847
-                // so check if one like it already exists in the db
1848
-                if ($model->exists_by_ID($this->ID())) {
1849
-                    if (WP_DEBUG && ! $this->in_entity_map()) {
1850
-                        throw new EE_Error(
1851
-                            sprintf(
1852
-                                esc_html__(
1853
-                                    'Using a model object %1$s that is NOT in the entity map, can lead to unexpected errors. You should either: %4$s 1. Put it in the entity mapper by calling %2$s %4$s 2. Discard this model object and use what is in the entity mapper %4$s 3. Fetch from the database using %3$s',
1854
-                                    'event_espresso'
1855
-                                ),
1856
-                                get_class($this),
1857
-                                get_class($model) . '::instance()->add_to_entity_map()',
1858
-                                get_class($model) . '::instance()->get_one_by_ID()',
1859
-                                '<br />'
1860
-                            )
1861
-                        );
1862
-                    }
1863
-                    $results = $model->update_by_ID($save_cols_n_values, $this->ID());
1864
-                } else {
1865
-                    $results = $model->insert($save_cols_n_values);
1866
-                    $this->_update_cached_related_model_objs_fks();
1867
-                }
1868
-            }
1869
-        } else {// there is NO primary key
1870
-            $already_in_db = false;
1871
-            foreach ($model->unique_indexes() as $index) {
1872
-                $uniqueness_where_params = array_intersect_key($save_cols_n_values, $index->fields());
1873
-                if ($model->exists([$uniqueness_where_params])) {
1874
-                    $already_in_db = true;
1875
-                }
1876
-            }
1877
-            if ($already_in_db) {
1878
-                $combined_pk_fields_n_values = array_intersect_key(
1879
-                    $save_cols_n_values,
1880
-                    $model->get_combined_primary_key_fields()
1881
-                );
1882
-                $results                     = $model->update(
1883
-                    $save_cols_n_values,
1884
-                    $combined_pk_fields_n_values
1885
-                );
1886
-            } else {
1887
-                $results = $model->insert($save_cols_n_values);
1888
-            }
1889
-        }
1890
-        // restore the old assumption about values being prepared by the model object
1891
-        $model->assume_values_already_prepared_by_model_object(
1892
-            $old_assumption_concerning_value_preparation
1893
-        );
1894
-        /**
1895
-         * After saving the model object this action is called
1896
-         *
1897
-         * @param EE_Base_Class $model_object which was just saved
1898
-         * @param boolean|int   $results      if it were updated, TRUE or FALSE; if it were newly inserted
1899
-         *                                    the new ID (or 0 if an error occurred and it wasn't updated)
1900
-         */
1901
-        do_action('AHEE__EE_Base_Class__save__end', $this, $results);
1902
-        $this->_has_changes = false;
1903
-        return $results;
1904
-    }
1905
-
1906
-
1907
-    /**
1908
-     * Updates the foreign key on related models objects pointing to this to have this model object's ID
1909
-     * as their foreign key.  If the cached related model objects already exist in the db, saves them (so that the DB
1910
-     * is consistent) Especially useful in case we JUST added this model object ot the database and we want to let its
1911
-     * cached relations with foreign keys to it know about that change. Eg: we've created a transaction but haven't
1912
-     * saved it to the db. We also create a registration and don't save it to the DB, but we DO cache it on the
1913
-     * transaction. Now, when we save the transaction, the registration's TXN_ID will be automatically updated, whether
1914
-     * or not they exist in the DB (if they do, their DB records will be automatically updated)
1915
-     *
1916
-     * @return void
1917
-     * @throws ReflectionException
1918
-     * @throws InvalidArgumentException
1919
-     * @throws InvalidInterfaceException
1920
-     * @throws InvalidDataTypeException
1921
-     * @throws EE_Error
1922
-     */
1923
-    protected function _update_cached_related_model_objs_fks()
1924
-    {
1925
-        $model = $this->get_model();
1926
-        foreach ($model->relation_settings() as $relation_name => $relation_obj) {
1927
-            if ($relation_obj instanceof EE_Has_Many_Relation) {
1928
-                foreach ($this->get_all_from_cache($relation_name) as $related_model_obj_in_cache) {
1929
-                    $fk_to_this = $related_model_obj_in_cache->get_model()->get_foreign_key_to(
1930
-                        $model->get_this_model_name()
1931
-                    );
1932
-                    $related_model_obj_in_cache->set($fk_to_this->get_name(), $this->ID());
1933
-                    if ($related_model_obj_in_cache->ID()) {
1934
-                        $related_model_obj_in_cache->save();
1935
-                    }
1936
-                }
1937
-            }
1938
-        }
1939
-    }
1940
-
1941
-
1942
-    /**
1943
-     * Saves this model object and its NEW cached relations to the database.
1944
-     * (Meaning, for now, IT DOES NOT WORK if the cached items already exist in the DB.
1945
-     * In order for that to work, we would need to mark model objects as dirty/clean...
1946
-     * because otherwise, there's a potential for infinite looping of saving
1947
-     * Saves the cached related model objects, and ensures the relation between them
1948
-     * and this object and properly setup
1949
-     *
1950
-     * @return int ID of new model object on save; 0 on failure+
1951
-     * @throws ReflectionException
1952
-     * @throws InvalidArgumentException
1953
-     * @throws InvalidInterfaceException
1954
-     * @throws InvalidDataTypeException
1955
-     * @throws EE_Error
1956
-     */
1957
-    public function save_new_cached_related_model_objs()
1958
-    {
1959
-        // make sure this has been saved
1960
-        if (! $this->ID()) {
1961
-            $id = $this->save();
1962
-        } else {
1963
-            $id = $this->ID();
1964
-        }
1965
-        // now save all the NEW cached model objects  (ie they don't exist in the DB)
1966
-        foreach ($this->get_model()->relation_settings() as $relation_name => $relationObj) {
1967
-            if ($this->_model_relations[ $relation_name ]) {
1968
-                // is this a relation where we should expect just ONE related object (ie, EE_Belongs_To_relation)
1969
-                // or MANY related objects (ie, EE_HABTM_Relation or EE_Has_Many_Relation)?
1970
-                /* @var $related_model_obj EE_Base_Class */
1971
-                if ($relationObj instanceof EE_Belongs_To_Relation) {
1972
-                    // add a relation to that relation type (which saves the appropriate thing in the process)
1973
-                    // but ONLY if it DOES NOT exist in the DB
1974
-                    $related_model_obj = $this->_model_relations[ $relation_name ];
1975
-                    // if( ! $related_model_obj->ID()){
1976
-                    $this->_add_relation_to($related_model_obj, $relation_name);
1977
-                    $related_model_obj->save_new_cached_related_model_objs();
1978
-                    // }
1979
-                } else {
1980
-                    foreach ($this->_model_relations[ $relation_name ] as $related_model_obj) {
1981
-                        // add a relation to that relation type (which saves the appropriate thing in the process)
1982
-                        // but ONLY if it DOES NOT exist in the DB
1983
-                        // if( ! $related_model_obj->ID()){
1984
-                        $this->_add_relation_to($related_model_obj, $relation_name);
1985
-                        $related_model_obj->save_new_cached_related_model_objs();
1986
-                        // }
1987
-                    }
1988
-                }
1989
-            }
1990
-        }
1991
-        return $id;
1992
-    }
1993
-
1994
-
1995
-    /**
1996
-     * for getting a model while instantiated.
1997
-     *
1998
-     * @return EEM_Base | EEM_CPT_Base
1999
-     * @throws ReflectionException
2000
-     * @throws InvalidArgumentException
2001
-     * @throws InvalidInterfaceException
2002
-     * @throws InvalidDataTypeException
2003
-     * @throws EE_Error
2004
-     */
2005
-    public function get_model()
2006
-    {
2007
-        if (! $this->_model) {
2008
-            $modelName    = self::_get_model_classname(get_class($this));
2009
-            $this->_model = self::_get_model_instance_with_name($modelName, $this->_timezone);
2010
-        } else {
2011
-            $this->_model->set_timezone($this->_timezone);
2012
-        }
2013
-        return $this->_model;
2014
-    }
2015
-
2016
-
2017
-    /**
2018
-     * @param $props_n_values
2019
-     * @param $classname
2020
-     * @return mixed bool|EE_Base_Class|EEM_CPT_Base
2021
-     * @throws ReflectionException
2022
-     * @throws InvalidArgumentException
2023
-     * @throws InvalidInterfaceException
2024
-     * @throws InvalidDataTypeException
2025
-     * @throws EE_Error
2026
-     */
2027
-    protected static function _get_object_from_entity_mapper($props_n_values, $classname)
2028
-    {
2029
-        // TODO: will not work for Term_Relationships because they have no PK!
2030
-        $primary_id_ref = self::_get_primary_key_name($classname);
2031
-        if (
2032
-            array_key_exists($primary_id_ref, $props_n_values)
2033
-            && ! empty($props_n_values[ $primary_id_ref ])
2034
-        ) {
2035
-            $id = $props_n_values[ $primary_id_ref ];
2036
-            return self::_get_model($classname)->get_from_entity_map($id);
2037
-        }
2038
-        return false;
2039
-    }
2040
-
2041
-
2042
-    /**
2043
-     * This is called by child static "new_instance" method and we'll check to see if there is an existing db entry for
2044
-     * the primary key (if present in incoming values). If there is a key in the incoming array that matches the
2045
-     * primary key for the model AND it is not null, then we check the db. If there's a an object we return it.  If not
2046
-     * we return false.
2047
-     *
2048
-     * @param array  $props_n_values    incoming array of properties and their values
2049
-     * @param string $classname         the classname of the child class
2050
-     * @param null   $timezone
2051
-     * @param array  $date_formats      incoming date_formats in an array where the first value is the
2052
-     *                                  date_format and the second value is the time format
2053
-     * @return mixed (EE_Base_Class|bool)
2054
-     * @throws InvalidArgumentException
2055
-     * @throws InvalidInterfaceException
2056
-     * @throws InvalidDataTypeException
2057
-     * @throws EE_Error
2058
-     * @throws ReflectionException
2059
-     */
2060
-    protected static function _check_for_object($props_n_values, $classname, $timezone = '', $date_formats = [])
2061
-    {
2062
-        $existing = null;
2063
-        $model    = self::_get_model($classname, $timezone);
2064
-        if ($model->has_primary_key_field()) {
2065
-            $primary_id_ref = self::_get_primary_key_name($classname);
2066
-            if (
2067
-                array_key_exists($primary_id_ref, $props_n_values)
2068
-                && ! empty($props_n_values[ $primary_id_ref ])
2069
-            ) {
2070
-                $existing = $model->get_one_by_ID(
2071
-                    $props_n_values[ $primary_id_ref ]
2072
-                );
2073
-            }
2074
-        } elseif ($model->has_all_combined_primary_key_fields($props_n_values)) {
2075
-            // no primary key on this model, but there's still a matching item in the DB
2076
-            $existing = self::_get_model($classname, $timezone)->get_one_by_ID(
2077
-                self::_get_model($classname, $timezone)
2078
-                    ->get_index_primary_key_string($props_n_values)
2079
-            );
2080
-        }
2081
-        if ($existing) {
2082
-            // set date formats if present before setting values
2083
-            if (! empty($date_formats) && is_array($date_formats)) {
2084
-                $existing->set_date_format($date_formats[0]);
2085
-                $existing->set_time_format($date_formats[1]);
2086
-            } else {
2087
-                // set default formats for date and time
2088
-                $existing->set_date_format(get_option('date_format'));
2089
-                $existing->set_time_format(get_option('time_format'));
2090
-            }
2091
-            foreach ($props_n_values as $property => $field_value) {
2092
-                $existing->set($property, $field_value);
2093
-            }
2094
-            return $existing;
2095
-        }
2096
-        return false;
2097
-    }
2098
-
2099
-
2100
-    /**
2101
-     * Gets the EEM_*_Model for this class
2102
-     *
2103
-     * @access public now, as this is more convenient
2104
-     * @param      $classname
2105
-     * @param null $timezone
2106
-     * @return EEM_Base
2107
-     * @throws InvalidArgumentException
2108
-     * @throws InvalidInterfaceException
2109
-     * @throws InvalidDataTypeException
2110
-     * @throws EE_Error
2111
-     * @throws ReflectionException
2112
-     */
2113
-    protected static function _get_model($classname, $timezone = '')
2114
-    {
2115
-        // find model for this class
2116
-        if (! $classname) {
2117
-            throw new EE_Error(
2118
-                sprintf(
2119
-                    esc_html__(
2120
-                        'What were you thinking calling _get_model(%s)?? You need to specify the class name',
2121
-                        'event_espresso'
2122
-                    ),
2123
-                    $classname
2124
-                )
2125
-            );
2126
-        }
2127
-        $modelName = self::_get_model_classname($classname);
2128
-        return self::_get_model_instance_with_name($modelName, $timezone);
2129
-    }
2130
-
2131
-
2132
-    /**
2133
-     * Gets the model instance (eg instance of EEM_Attendee) given its classname (eg EE_Attendee)
2134
-     *
2135
-     * @param string $model_classname
2136
-     * @param null   $timezone
2137
-     * @return EEM_Base
2138
-     * @throws ReflectionException
2139
-     * @throws InvalidArgumentException
2140
-     * @throws InvalidInterfaceException
2141
-     * @throws InvalidDataTypeException
2142
-     * @throws EE_Error
2143
-     */
2144
-    protected static function _get_model_instance_with_name($model_classname, $timezone = '')
2145
-    {
2146
-        $model_classname = str_replace('EEM_', '', $model_classname);
2147
-        $model           = EE_Registry::instance()->load_model($model_classname);
2148
-        $model->set_timezone($timezone);
2149
-        return $model;
2150
-    }
2151
-
2152
-
2153
-    /**
2154
-     * If a model name is provided (eg Registration), gets the model classname for that model.
2155
-     * Also works if a model class's classname is provided (eg EE_Registration).
2156
-     *
2157
-     * @param string|null $model_name
2158
-     * @return string like EEM_Attendee
2159
-     */
2160
-    private static function _get_model_classname($model_name = '')
2161
-    {
2162
-        return strpos((string) $model_name, 'EE_') === 0
2163
-            ? str_replace('EE_', 'EEM_', $model_name)
2164
-            : 'EEM_' . $model_name;
2165
-    }
2166
-
2167
-
2168
-    /**
2169
-     * returns the name of the primary key attribute
2170
-     *
2171
-     * @param null $classname
2172
-     * @return string
2173
-     * @throws InvalidArgumentException
2174
-     * @throws InvalidInterfaceException
2175
-     * @throws InvalidDataTypeException
2176
-     * @throws EE_Error
2177
-     * @throws ReflectionException
2178
-     */
2179
-    protected static function _get_primary_key_name($classname = null)
2180
-    {
2181
-        if (! $classname) {
2182
-            throw new EE_Error(
2183
-                sprintf(
2184
-                    esc_html__('What were you thinking calling _get_primary_key_name(%s)', 'event_espresso'),
2185
-                    $classname
2186
-                )
2187
-            );
2188
-        }
2189
-        return self::_get_model($classname)->get_primary_key_field()->get_name();
2190
-    }
2191
-
2192
-
2193
-    /**
2194
-     * Gets the value of the primary key.
2195
-     * If the object hasn't yet been saved, it should be whatever the model field's default was
2196
-     * (eg, if this were the EE_Event class, look at the primary key field on EEM_Event and see what its default value
2197
-     * is. Usually defaults for integer primary keys are 0; string primary keys are usually NULL).
2198
-     *
2199
-     * @return mixed, if the primary key is of type INT it'll be an int. Otherwise it could be a string
2200
-     * @throws ReflectionException
2201
-     * @throws InvalidArgumentException
2202
-     * @throws InvalidInterfaceException
2203
-     * @throws InvalidDataTypeException
2204
-     * @throws EE_Error
2205
-     */
2206
-    public function ID()
2207
-    {
2208
-        $model = $this->get_model();
2209
-        // now that we know the name of the variable, use a variable variable to get its value and return its
2210
-        if ($model->has_primary_key_field()) {
2211
-            return $this->_fields[ $model->primary_key_name() ];
2212
-        }
2213
-        return $model->get_index_primary_key_string($this->_fields);
2214
-    }
2215
-
2216
-
2217
-    /**
2218
-     * @param EE_Base_Class|int|string $otherModelObjectOrID
2219
-     * @param string                   $relation_name
2220
-     * @return bool
2221
-     * @throws EE_Error
2222
-     * @throws ReflectionException
2223
-     * @since   5.0.0.p
2224
-     */
2225
-    public function hasRelation($otherModelObjectOrID, string $relation_name): bool
2226
-    {
2227
-        $other_model = self::_get_model_instance_with_name(
2228
-            self::_get_model_classname($relation_name),
2229
-            $this->_timezone
2230
-        );
2231
-        $primary_key = $other_model->primary_key_name();
2232
-        /** @var EE_Base_Class $otherModelObject */
2233
-        $otherModelObject = $other_model->ensure_is_obj($otherModelObjectOrID, $relation_name);
2234
-        return $this->count_related($relation_name, [[$primary_key => $otherModelObject->ID()]]) > 0;
2235
-    }
2236
-
2237
-
2238
-    /**
2239
-     * Adds a relationship to the specified EE_Base_Class object, given the relationship's name. Eg, if the current
2240
-     * model is related to a group of events, the $relation_name should be 'Event', and should be a key in the EE
2241
-     * Model's $_model_relations array. If this model object doesn't exist in the DB, just caches the related thing
2242
-     *
2243
-     * @param mixed  $otherObjectModelObjectOrID       EE_Base_Class or the ID of the other object
2244
-     * @param string $relation_name                    eg 'Events','Question',etc.
2245
-     *                                                 an attendee to a group, you also want to specify which role they
2246
-     *                                                 will have in that group. So you would use this parameter to
2247
-     *                                                 specify array('role-column-name'=>'role-id')
2248
-     * @param array  $extra_join_model_fields_n_values You can optionally include an array of key=>value pairs that
2249
-     *                                                 allow you to further constrict the relation to being added.
2250
-     *                                                 However, keep in mind that the columns (keys) given must match a
2251
-     *                                                 column on the JOIN table and currently only the HABTM models
2252
-     *                                                 accept these additional conditions.  Also remember that if an
2253
-     *                                                 exact match isn't found for these extra cols/val pairs, then a
2254
-     *                                                 NEW row is created in the join table.
2255
-     * @param null   $cache_id
2256
-     * @return EE_Base_Class the object the relation was added to
2257
-     * @throws ReflectionException
2258
-     * @throws InvalidArgumentException
2259
-     * @throws InvalidInterfaceException
2260
-     * @throws InvalidDataTypeException
2261
-     * @throws EE_Error
2262
-     */
2263
-    public function _add_relation_to(
2264
-        $otherObjectModelObjectOrID,
2265
-        $relation_name,
2266
-        $extra_join_model_fields_n_values = [],
2267
-        $cache_id = null
2268
-    ) {
2269
-        $model = $this->get_model();
2270
-        // if this thing exists in the DB, save the relation to the DB
2271
-        if ($this->ID()) {
2272
-            $otherObject = $model->add_relationship_to(
2273
-                $this,
2274
-                $otherObjectModelObjectOrID,
2275
-                $relation_name,
2276
-                $extra_join_model_fields_n_values
2277
-            );
2278
-            // clear cache so future get_many_related and get_first_related() return new results.
2279
-            $this->clear_cache($relation_name, $otherObject, true);
2280
-            if ($otherObject instanceof EE_Base_Class) {
2281
-                $otherObject->clear_cache($model->get_this_model_name(), $this);
2282
-            }
2283
-        } else {
2284
-            // this thing doesn't exist in the DB,  so just cache it
2285
-            if (! $otherObjectModelObjectOrID instanceof EE_Base_Class) {
2286
-                throw new EE_Error(
2287
-                    sprintf(
2288
-                        esc_html__(
2289
-                            'Before a model object is saved to the database, calls to _add_relation_to must be passed an actual object, not just an ID. You provided %s as the model object to a %s',
2290
-                            'event_espresso'
2291
-                        ),
2292
-                        $otherObjectModelObjectOrID,
2293
-                        get_class($this)
2294
-                    )
2295
-                );
2296
-            }
2297
-            $otherObject = $otherObjectModelObjectOrID;
2298
-            $this->cache($relation_name, $otherObjectModelObjectOrID, $cache_id);
2299
-        }
2300
-        if ($otherObject instanceof EE_Base_Class) {
2301
-            // fix the reciprocal relation too
2302
-            if ($otherObject->ID()) {
2303
-                // its saved so assumed relations exist in the DB, so we can just
2304
-                // clear the cache so future queries use the updated info in the DB
2305
-                $otherObject->clear_cache(
2306
-                    $model->get_this_model_name(),
2307
-                    null,
2308
-                    true
2309
-                );
2310
-            } else {
2311
-                // it's not saved, so it caches relations like this
2312
-                $otherObject->cache($model->get_this_model_name(), $this);
2313
-            }
2314
-        }
2315
-        return $otherObject;
2316
-    }
2317
-
2318
-
2319
-    /**
2320
-     * Removes a relationship to the specified EE_Base_Class object, given the relationships' name. Eg, if the current
2321
-     * model is related to a group of events, the $relation_name should be 'Events', and should be a key in the EE
2322
-     * Model's $_model_relations array. If this model object doesn't exist in the DB, just removes the related thing
2323
-     * from the cache
2324
-     *
2325
-     * @param mixed  $otherObjectModelObjectOrID
2326
-     *                EE_Base_Class or the ID of the other object, OR an array key into the cache if this isn't saved
2327
-     *                to the DB yet
2328
-     * @param string $relation_name
2329
-     * @param array  $where_query
2330
-     *                You can optionally include an array of key=>value pairs that allow you to further constrict the
2331
-     *                relation to being added. However, keep in mind that the columns (keys) given must match a column
2332
-     *                on the JOIN table and currently only the HABTM models accept these additional conditions. Also
2333
-     *                remember that if an exact match isn't found for these extra cols/val pairs, then no row is
2334
-     *                deleted.
2335
-     * @return EE_Base_Class|bool   the related entity that was removed
2336
-     *                              or true if multiple entities removed
2337
-     *                              or false if nothing was cached
2338
-     * @throws ReflectionException
2339
-     * @throws InvalidArgumentException
2340
-     * @throws InvalidInterfaceException
2341
-     * @throws InvalidDataTypeException
2342
-     * @throws EE_Error
2343
-     */
2344
-    public function _remove_relation_to($otherObjectModelObjectOrID, $relation_name, $where_query = [])
2345
-    {
2346
-        if ($this->ID()) {
2347
-            // if this exists in the DB, save the relation change to the DB too
2348
-            $otherObject = $this->get_model()->remove_relationship_to(
2349
-                $this,
2350
-                $otherObjectModelObjectOrID,
2351
-                $relation_name,
2352
-                $where_query
2353
-            );
2354
-            $this->clear_cache(
2355
-                $relation_name,
2356
-                $otherObject
2357
-            );
2358
-        } else {
2359
-            // this doesn't exist in the DB, just remove it from the cache
2360
-            $otherObject = $this->clear_cache(
2361
-                $relation_name,
2362
-                $otherObjectModelObjectOrID
2363
-            );
2364
-        }
2365
-        if ($otherObject instanceof EE_Base_Class) {
2366
-            $otherObject->clear_cache(
2367
-                $this->get_model()->get_this_model_name(),
2368
-                $this
2369
-            );
2370
-        }
2371
-        return $otherObject;
2372
-    }
2373
-
2374
-
2375
-    /**
2376
-     * Removes ALL the related things for the $relation_name.
2377
-     *
2378
-     * @param string $relation_name
2379
-     * @param array  $where_query_params @see
2380
-     *                                   https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
2381
-     * @return EE_Base_Class
2382
-     * @throws ReflectionException
2383
-     * @throws InvalidArgumentException
2384
-     * @throws InvalidInterfaceException
2385
-     * @throws InvalidDataTypeException
2386
-     * @throws EE_Error
2387
-     */
2388
-    public function _remove_relations($relation_name, $where_query_params = [])
2389
-    {
2390
-        if ($this->ID()) {
2391
-            // if this exists in the DB, save the relation change to the DB too
2392
-            $otherObjects = $this->get_model()->remove_relations(
2393
-                $this,
2394
-                $relation_name,
2395
-                $where_query_params
2396
-            );
2397
-            $this->clear_cache(
2398
-                $relation_name,
2399
-                null,
2400
-                true
2401
-            );
2402
-        } else {
2403
-            // this doesn't exist in the DB, just remove it from the cache
2404
-            $otherObjects = $this->clear_cache(
2405
-                $relation_name,
2406
-                null,
2407
-                true
2408
-            );
2409
-        }
2410
-        if (is_array($otherObjects)) {
2411
-            foreach ($otherObjects as $otherObject) {
2412
-                $otherObject->clear_cache(
2413
-                    $this->get_model()->get_this_model_name(),
2414
-                    $this
2415
-                );
2416
-            }
2417
-        }
2418
-        return $otherObjects;
2419
-    }
2420
-
2421
-
2422
-    /**
2423
-     * Gets all the related model objects of the specified type. Eg, if the current class if
2424
-     * EE_Event, you could call $this->get_many_related('Registration') to get an array of all the
2425
-     * EE_Registration objects which related to this event. Note: by default, we remove the "default query params"
2426
-     * because we want to get even deleted items etc.
2427
-     *
2428
-     * @param string      $relation_name key in the model's _model_relations array
2429
-     * @param array|null  $query_params  @see
2430
-     *                              https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
2431
-     * @return EE_Base_Class[]     Results not necessarily indexed by IDs, because some results might not have primary
2432
-     *                              keys or might not be saved yet. Consider using EEM_Base::get_IDs() on these
2433
-     *                              results if you want IDs
2434
-     * @throws ReflectionException
2435
-     * @throws InvalidArgumentException
2436
-     * @throws InvalidInterfaceException
2437
-     * @throws InvalidDataTypeException
2438
-     * @throws EE_Error
2439
-     */
2440
-    public function get_many_related($relation_name, $query_params = [])
2441
-    {
2442
-        if ($this->ID()) {
2443
-            // this exists in the DB, so get the related things from either the cache or the DB
2444
-            // if there are query parameters, forget about caching the related model objects.
2445
-            if ($query_params) {
2446
-                $related_model_objects = $this->get_model()->get_all_related(
2447
-                    $this,
2448
-                    $relation_name,
2449
-                    $query_params
2450
-                );
2451
-            } else {
2452
-                // did we already cache the result of this query?
2453
-                $cached_results = $this->get_all_from_cache($relation_name);
2454
-                if (! $cached_results) {
2455
-                    $related_model_objects = $this->get_model()->get_all_related(
2456
-                        $this,
2457
-                        $relation_name,
2458
-                        $query_params
2459
-                    );
2460
-                    // if no query parameters were passed, then we got all the related model objects
2461
-                    // for that relation. We can cache them then.
2462
-                    foreach ($related_model_objects as $related_model_object) {
2463
-                        $this->cache($relation_name, $related_model_object);
2464
-                    }
2465
-                } else {
2466
-                    $related_model_objects = $cached_results;
2467
-                }
2468
-            }
2469
-        } else {
2470
-            // this doesn't exist in the DB, so just get the related things from the cache
2471
-            $related_model_objects = $this->get_all_from_cache($relation_name);
2472
-        }
2473
-        return $related_model_objects;
2474
-    }
2475
-
2476
-
2477
-    /**
2478
-     * Instead of getting the related model objects, simply counts them. Ignores default_where_conditions by default,
2479
-     * unless otherwise specified in the $query_params
2480
-     *
2481
-     * @param string $relation_name  model_name like 'Event', or 'Registration'
2482
-     * @param array  $query_params   @see
2483
-     *                               https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2484
-     * @param string $field_to_count name of field to count by. By default, uses primary key
2485
-     * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2486
-     *                               that by the setting $distinct to TRUE;
2487
-     * @return int
2488
-     * @throws ReflectionException
2489
-     * @throws InvalidArgumentException
2490
-     * @throws InvalidInterfaceException
2491
-     * @throws InvalidDataTypeException
2492
-     * @throws EE_Error
2493
-     */
2494
-    public function count_related($relation_name, $query_params = [], $field_to_count = null, $distinct = false)
2495
-    {
2496
-        return $this->get_model()->count_related(
2497
-            $this,
2498
-            $relation_name,
2499
-            $query_params,
2500
-            $field_to_count,
2501
-            $distinct
2502
-        );
2503
-    }
2504
-
2505
-
2506
-    /**
2507
-     * Instead of getting the related model objects, simply sums up the values of the specified field.
2508
-     * Note: ignores default_where_conditions by default, unless otherwise specified in the $query_params
2509
-     *
2510
-     * @param string $relation_name model_name like 'Event', or 'Registration'
2511
-     * @param array  $query_params  @see
2512
-     *                              https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2513
-     * @param string $field_to_sum  name of field to count by.
2514
-     *                              By default, uses primary key
2515
-     *                              (which doesn't make much sense, so you should probably change it)
2516
-     * @return int
2517
-     * @throws ReflectionException
2518
-     * @throws InvalidArgumentException
2519
-     * @throws InvalidInterfaceException
2520
-     * @throws InvalidDataTypeException
2521
-     * @throws EE_Error
2522
-     */
2523
-    public function sum_related($relation_name, $query_params = [], $field_to_sum = null)
2524
-    {
2525
-        return $this->get_model()->sum_related(
2526
-            $this,
2527
-            $relation_name,
2528
-            $query_params,
2529
-            $field_to_sum
2530
-        );
2531
-    }
2532
-
2533
-
2534
-    /**
2535
-     * Gets the first (ie, one) related model object of the specified type.
2536
-     *
2537
-     * @param string $relation_name key in the model's _model_relations array
2538
-     * @param array  $query_params  @see
2539
-     *                              https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2540
-     * @return EE_Base_Class|null (not an array, a single object)
2541
-     * @throws ReflectionException
2542
-     * @throws InvalidArgumentException
2543
-     * @throws InvalidInterfaceException
2544
-     * @throws InvalidDataTypeException
2545
-     * @throws EE_Error
2546
-     */
2547
-    public function get_first_related(string $relation_name, array $query_params = []): ?EE_Base_Class
2548
-    {
2549
-        $model = $this->get_model();
2550
-        if ($this->ID()) {// this exists in the DB, get from the cache OR the DB
2551
-            // if they've provided some query parameters, don't bother trying to cache the result
2552
-            // also make sure we're not caching the result of get_first_related
2553
-            // on a relation which should have an array of objects (because the cache might have an array of objects)
2554
-            if (
2555
-                $query_params
2556
-                || ! $model->related_settings_for($relation_name) instanceof EE_Belongs_To_Relation
2557
-            ) {
2558
-                $related_model_object = $model->get_first_related(
2559
-                    $this,
2560
-                    $relation_name,
2561
-                    $query_params
2562
-                );
2563
-            } else {
2564
-                // first, check if we've already cached the result of this query
2565
-                $cached_result = $this->get_one_from_cache($relation_name);
2566
-                if (! $cached_result) {
2567
-                    $related_model_object = $model->get_first_related(
2568
-                        $this,
2569
-                        $relation_name,
2570
-                        $query_params
2571
-                    );
2572
-                    $this->cache($relation_name, $related_model_object);
2573
-                } else {
2574
-                    $related_model_object = $cached_result;
2575
-                }
2576
-            }
2577
-        } else {
2578
-            $related_model_object = null;
2579
-            // this doesn't exist in the Db,
2580
-            // but maybe the relation is of type belongs to, and so the related thing might
2581
-            if ($model->related_settings_for($relation_name) instanceof EE_Belongs_To_Relation) {
2582
-                $related_model_object = $model->get_first_related(
2583
-                    $this,
2584
-                    $relation_name,
2585
-                    $query_params
2586
-                );
2587
-            }
2588
-            // this doesn't exist in the DB and apparently the thing it belongs to doesn't either,
2589
-            // just get what's cached on this object
2590
-            if (! $related_model_object) {
2591
-                $related_model_object = $this->get_one_from_cache($relation_name);
2592
-            }
2593
-        }
2594
-        return $related_model_object;
2595
-    }
2596
-
2597
-
2598
-    /**
2599
-     * Does a delete on all related objects of type $relation_name and removes
2600
-     * the current model object's relation to them. If they can't be deleted (because
2601
-     * of blocking related model objects) does nothing. If the related model objects are
2602
-     * soft-deletable, they will be soft-deleted regardless of related blocking model objects.
2603
-     * If this model object doesn't exist yet in the DB, just removes its related things
2604
-     *
2605
-     * @param string $relation_name
2606
-     * @param array  $query_params @see
2607
-     *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2608
-     * @return int how many deleted
2609
-     * @throws ReflectionException
2610
-     * @throws InvalidArgumentException
2611
-     * @throws InvalidInterfaceException
2612
-     * @throws InvalidDataTypeException
2613
-     * @throws EE_Error
2614
-     */
2615
-    public function delete_related($relation_name, $query_params = [])
2616
-    {
2617
-        if ($this->ID()) {
2618
-            $count = $this->get_model()->delete_related(
2619
-                $this,
2620
-                $relation_name,
2621
-                $query_params
2622
-            );
2623
-        } else {
2624
-            $count = count($this->get_all_from_cache($relation_name));
2625
-            $this->clear_cache($relation_name, null, true);
2626
-        }
2627
-        return $count;
2628
-    }
2629
-
2630
-
2631
-    /**
2632
-     * Does a hard delete (ie, removes the DB row) on all related objects of type $relation_name and removes
2633
-     * the current model object's relation to them. If they can't be deleted (because
2634
-     * of blocking related model objects) just does a soft delete on it instead, if possible.
2635
-     * If the related thing isn't a soft-deletable model object, this function is identical
2636
-     * to delete_related(). If this model object doesn't exist in the DB, just remove its related things
2637
-     *
2638
-     * @param string $relation_name
2639
-     * @param array  $query_params @see
2640
-     *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2641
-     * @return int how many deleted (including those soft deleted)
2642
-     * @throws ReflectionException
2643
-     * @throws InvalidArgumentException
2644
-     * @throws InvalidInterfaceException
2645
-     * @throws InvalidDataTypeException
2646
-     * @throws EE_Error
2647
-     */
2648
-    public function delete_related_permanently($relation_name, $query_params = [])
2649
-    {
2650
-        $count = $this->ID()
2651
-            ? $this->get_model()->delete_related_permanently(
2652
-                $this,
2653
-                $relation_name,
2654
-                $query_params
2655
-            )
2656
-            : count($this->get_all_from_cache($relation_name));
2657
-
2658
-        $this->clear_cache($relation_name, null, true);
2659
-        return $count;
2660
-    }
2661
-
2662
-
2663
-    /**
2664
-     * is_set
2665
-     * Just a simple utility function children can use for checking if property exists
2666
-     *
2667
-     * @access  public
2668
-     * @param string $field_name property to check
2669
-     * @return bool                              TRUE if existing,FALSE if not.
2670
-     */
2671
-    public function is_set($field_name)
2672
-    {
2673
-        return isset($this->_fields[ $field_name ]);
2674
-    }
2675
-
2676
-
2677
-    /**
2678
-     * Just a simple utility function children can use for checking if property (or properties) exists and throwing an
2679
-     * EE_Error exception if they don't
2680
-     *
2681
-     * @param mixed (string|array) $properties properties to check
2682
-     * @return bool                              TRUE if existing, throw EE_Error if not.
2683
-     * @throws EE_Error
2684
-     */
2685
-    protected function _property_exists($properties)
2686
-    {
2687
-        foreach ((array) $properties as $property_name) {
2688
-            // first make sure this property exists
2689
-            if (! $this->_fields[ $property_name ]) {
2690
-                throw new EE_Error(
2691
-                    sprintf(
2692
-                        esc_html__(
2693
-                            'Trying to retrieve a non-existent property (%s).  Double check the spelling please',
2694
-                            'event_espresso'
2695
-                        ),
2696
-                        $property_name
2697
-                    )
2698
-                );
2699
-            }
2700
-        }
2701
-        return true;
2702
-    }
2703
-
2704
-
2705
-    /**
2706
-     * This simply returns an array of model fields for this object
2707
-     *
2708
-     * @return array
2709
-     * @throws ReflectionException
2710
-     * @throws InvalidArgumentException
2711
-     * @throws InvalidInterfaceException
2712
-     * @throws InvalidDataTypeException
2713
-     * @throws EE_Error
2714
-     */
2715
-    public function model_field_array()
2716
-    {
2717
-        $fields     = $this->get_model()->field_settings(false);
2718
-        $properties = [];
2719
-        // remove prepended underscore
2720
-        foreach ($fields as $field_name => $settings) {
2721
-            $properties[ $field_name ] = $this->get($field_name);
2722
-        }
2723
-        return $properties;
2724
-    }
2725
-
2726
-
2727
-    /**
2728
-     * Very handy general function to allow for plugins to extend any child of EE_Base_Class.
2729
-     * If a method is called on a child of EE_Base_Class that doesn't exist, this function is called
2730
-     * (http://www.garfieldtech.com/blog/php-magic-call) and passed the method's name and arguments.
2731
-     * Instead of requiring a plugin to extend the EE_Base_Class
2732
-     * (which works fine is there's only 1 plugin, but when will that happen?)
2733
-     * they can add a hook onto 'filters_hook_espresso__{className}__{methodName}'
2734
-     * (eg, filters_hook_espresso__EE_Answer__my_great_function)
2735
-     * and accepts 2 arguments: the object on which the function was called,
2736
-     * and an array of the original arguments passed to the function.
2737
-     * Whatever their callback function returns will be returned by this function.
2738
-     * Example: in functions.php (or in a plugin):
2739
-     *      add_filter('FHEE__EE_Answer__my_callback','my_callback',10,3);
2740
-     *      function my_callback($previousReturnValue,EE_Base_Class $object,$argsArray){
2741
-     *          $returnString= "you called my_callback! and passed args:".implode(",",$argsArray);
2742
-     *          return $previousReturnValue.$returnString;
2743
-     *      }
2744
-     * require('EE_Answer.class.php');
2745
-     * echo EE_Answer::new_instance(['REG_ID' => 2,'QST_ID' => 3,'ANS_value' => The answer is 42'])
2746
-     *      ->my_callback('monkeys',100);
2747
-     * // will output "you called my_callback! and passed args:monkeys,100"
2748
-     *
2749
-     * @param string $methodName name of method which was called on a child of EE_Base_Class, but which
2750
-     * @param array  $args       array of original arguments passed to the function
2751
-     * @return mixed whatever the plugin which calls add_filter decides
2752
-     * @throws EE_Error
2753
-     */
2754
-    public function __call($methodName, $args)
2755
-    {
2756
-        $className = get_class($this);
2757
-        $tagName   = "FHEE__{$className}__{$methodName}";
2758
-        if (! has_filter($tagName)) {
2759
-            throw new EE_Error(
2760
-                sprintf(
2761
-                    esc_html__(
2762
-                        "Method %s on class %s does not exist! You can create one with the following code in functions.php or in a plugin: add_filter('%s','my_callback',10,3);function my_callback(\$previousReturnValue,EE_Base_Class \$object, \$argsArray){/*function body*/return \$whatever;}",
2763
-                        'event_espresso'
2764
-                    ),
2765
-                    $methodName,
2766
-                    $className,
2767
-                    $tagName
2768
-                )
2769
-            );
2770
-        }
2771
-        return apply_filters($tagName, null, $this, $args);
2772
-    }
2773
-
2774
-
2775
-    /**
2776
-     * Similar to insert_post_meta, adds a record in the Extra_Meta model's table with the given key and value.
2777
-     * A $previous_value can be specified in case there are many meta rows with the same key
2778
-     *
2779
-     * @param string $meta_key
2780
-     * @param mixed  $meta_value
2781
-     * @param mixed  $previous_value
2782
-     * @return bool|int # of records updated (or BOOLEAN if we actually ended up inserting the extra meta row)
2783
-     *                  NOTE: if the values haven't changed, returns 0
2784
-     * @throws InvalidArgumentException
2785
-     * @throws InvalidInterfaceException
2786
-     * @throws InvalidDataTypeException
2787
-     * @throws EE_Error
2788
-     * @throws ReflectionException
2789
-     */
2790
-    public function update_extra_meta(string $meta_key, $meta_value, $previous_value = null)
2791
-    {
2792
-        $query_params = [
2793
-            [
2794
-                'EXM_key'  => $meta_key,
2795
-                'OBJ_ID'   => $this->ID(),
2796
-                'EXM_type' => $this->get_model()->get_this_model_name(),
2797
-            ],
2798
-        ];
2799
-        if ($previous_value !== null) {
2800
-            $query_params[0]['EXM_value'] = $previous_value;
2801
-        }
2802
-        $existing_rows_like_that = EEM_Extra_Meta::instance()->get_all($query_params);
2803
-        if (! $existing_rows_like_that) {
2804
-            return $this->add_extra_meta($meta_key, $meta_value);
2805
-        }
2806
-        foreach ($existing_rows_like_that as $existing_row) {
2807
-            $existing_row->save(['EXM_value' => $meta_value]);
2808
-        }
2809
-        return count($existing_rows_like_that);
2810
-    }
2811
-
2812
-
2813
-    /**
2814
-     * Adds a new extra meta record. If $unique is set to TRUE, we'll first double-check
2815
-     * no other extra meta for this model object have the same key. Returns TRUE if the
2816
-     * extra meta row was entered, false if not
2817
-     *
2818
-     * @param string $meta_key
2819
-     * @param mixed  $meta_value
2820
-     * @param bool   $unique
2821
-     * @return bool
2822
-     * @throws InvalidArgumentException
2823
-     * @throws InvalidInterfaceException
2824
-     * @throws InvalidDataTypeException
2825
-     * @throws EE_Error
2826
-     * @throws ReflectionException
2827
-     */
2828
-    public function add_extra_meta(string $meta_key, $meta_value, bool $unique = false): bool
2829
-    {
2830
-        if ($unique) {
2831
-            $existing_extra_meta = EEM_Extra_Meta::instance()->get_one(
2832
-                [
2833
-                    [
2834
-                        'EXM_key'  => $meta_key,
2835
-                        'OBJ_ID'   => $this->ID(),
2836
-                        'EXM_type' => $this->get_model()->get_this_model_name(),
2837
-                    ],
2838
-                ]
2839
-            );
2840
-            if ($existing_extra_meta) {
2841
-                return false;
2842
-            }
2843
-        }
2844
-        $new_extra_meta = EE_Extra_Meta::new_instance(
2845
-            [
2846
-                'EXM_key'   => $meta_key,
2847
-                'EXM_value' => $meta_value,
2848
-                'OBJ_ID'    => $this->ID(),
2849
-                'EXM_type'  => $this->get_model()->get_this_model_name(),
2850
-            ]
2851
-        );
2852
-        $new_extra_meta->save();
2853
-        return true;
2854
-    }
2855
-
2856
-
2857
-    /**
2858
-     * Deletes all the extra meta rows for this record as specified by key. If $meta_value
2859
-     * is specified, only deletes extra meta records with that value.
2860
-     *
2861
-     * @param string $meta_key
2862
-     * @param mixed  $meta_value
2863
-     * @return int number of extra meta rows deleted
2864
-     * @throws InvalidArgumentException
2865
-     * @throws InvalidInterfaceException
2866
-     * @throws InvalidDataTypeException
2867
-     * @throws EE_Error
2868
-     * @throws ReflectionException
2869
-     */
2870
-    public function delete_extra_meta(string $meta_key, $meta_value = null)
2871
-    {
2872
-        $query_params = [
2873
-            [
2874
-                'EXM_key'  => $meta_key,
2875
-                'OBJ_ID'   => $this->ID(),
2876
-                'EXM_type' => $this->get_model()->get_this_model_name(),
2877
-            ],
2878
-        ];
2879
-        if ($meta_value !== null) {
2880
-            $query_params[0]['EXM_value'] = $meta_value;
2881
-        }
2882
-        return EEM_Extra_Meta::instance()->delete($query_params);
2883
-    }
2884
-
2885
-
2886
-    /**
2887
-     * Gets the extra meta with the given meta key. If you specify "single" we just return 1, otherwise
2888
-     * an array of everything found. Requires that this model actually have a relation of type EE_Has_Many_Any_Relation.
2889
-     * You can specify $default is case you haven't found the extra meta
2890
-     *
2891
-     * @param string     $meta_key
2892
-     * @param bool       $single
2893
-     * @param mixed      $default if we don't find anything, what should we return?
2894
-     * @param array|null $extra_where
2895
-     * @return mixed single value if $single; array if ! $single
2896
-     * @throws ReflectionException
2897
-     * @throws EE_Error
2898
-     */
2899
-    public function get_extra_meta(string $meta_key, bool $single = false, $default = null, ?array $extra_where = [])
2900
-    {
2901
-        $query_params = [$extra_where + ['EXM_key' => $meta_key]];
2902
-        if ($single) {
2903
-            $result = $this->get_first_related('Extra_Meta', $query_params);
2904
-            if ($result instanceof EE_Extra_Meta) {
2905
-                return $result->value();
2906
-            }
2907
-        } else {
2908
-            $results = $this->get_many_related('Extra_Meta', $query_params);
2909
-            if ($results) {
2910
-                $values = [];
2911
-                foreach ($results as $result) {
2912
-                    if ($result instanceof EE_Extra_Meta) {
2913
-                        $values[ $result->ID() ] = $result->value();
2914
-                    }
2915
-                }
2916
-                return $values;
2917
-            }
2918
-        }
2919
-        // if nothing discovered yet return default.
2920
-        return apply_filters(
2921
-            'FHEE__EE_Base_Class__get_extra_meta__default_value',
2922
-            $default,
2923
-            $meta_key,
2924
-            $single,
2925
-            $this
2926
-        );
2927
-    }
2928
-
2929
-
2930
-    /**
2931
-     * Returns a simple array of all the extra meta associated with this model object.
2932
-     * If $one_of_each_key is true (Default), it will be an array of simple key-value pairs, keys being the
2933
-     * extra meta's key, and teh value being its value. However, if there are duplicate extra meta rows with
2934
-     * the same key, only one will be used. (eg array('foo'=>'bar','monkey'=>123))
2935
-     * If $one_of_each_key is false, it will return an array with the top-level keys being
2936
-     * the extra meta keys, but their values are also arrays, which have the extra-meta's ID as their sub-key, and
2937
-     * finally the extra meta's value as each sub-value. (eg
2938
-     * array('foo'=>array(1=>'bar',2=>'bill'),'monkey'=>array(3=>123)))
2939
-     *
2940
-     * @param bool $one_of_each_key
2941
-     * @return array
2942
-     * @throws ReflectionException
2943
-     * @throws InvalidArgumentException
2944
-     * @throws InvalidInterfaceException
2945
-     * @throws InvalidDataTypeException
2946
-     * @throws EE_Error
2947
-     */
2948
-    public function all_extra_meta_array(bool $one_of_each_key = true): array
2949
-    {
2950
-        $return_array = [];
2951
-        if ($one_of_each_key) {
2952
-            $extra_meta_objs = $this->get_many_related(
2953
-                'Extra_Meta',
2954
-                ['group_by' => 'EXM_key']
2955
-            );
2956
-            foreach ($extra_meta_objs as $extra_meta_obj) {
2957
-                if ($extra_meta_obj instanceof EE_Extra_Meta) {
2958
-                    $return_array[ $extra_meta_obj->key() ] = $extra_meta_obj->value();
2959
-                }
2960
-            }
2961
-        } else {
2962
-            $extra_meta_objs = $this->get_many_related('Extra_Meta');
2963
-            foreach ($extra_meta_objs as $extra_meta_obj) {
2964
-                if ($extra_meta_obj instanceof EE_Extra_Meta) {
2965
-                    if (! isset($return_array[ $extra_meta_obj->key() ])) {
2966
-                        $return_array[ $extra_meta_obj->key() ] = [];
2967
-                    }
2968
-                    $return_array[ $extra_meta_obj->key() ][ $extra_meta_obj->ID() ] = $extra_meta_obj->value();
2969
-                }
2970
-            }
2971
-        }
2972
-        return $return_array;
2973
-    }
2974
-
2975
-
2976
-    /**
2977
-     * Gets a pretty nice displayable nice for this model object. Often overridden
2978
-     *
2979
-     * @return string
2980
-     * @throws ReflectionException
2981
-     * @throws InvalidArgumentException
2982
-     * @throws InvalidInterfaceException
2983
-     * @throws InvalidDataTypeException
2984
-     * @throws EE_Error
2985
-     */
2986
-    public function name()
2987
-    {
2988
-        // find a field that's not a text field
2989
-        $field_we_can_use = $this->get_model()->get_a_field_of_type('EE_Text_Field_Base');
2990
-        if ($field_we_can_use) {
2991
-            return $this->get($field_we_can_use->get_name());
2992
-        }
2993
-        $first_few_properties = $this->model_field_array();
2994
-        $first_few_properties = array_slice($first_few_properties, 0, 3);
2995
-        $name_parts           = [];
2996
-        foreach ($first_few_properties as $name => $value) {
2997
-            $name_parts[] = "$name:$value";
2998
-        }
2999
-        return implode(',', $name_parts);
3000
-    }
3001
-
3002
-
3003
-    /**
3004
-     * in_entity_map
3005
-     * Checks if this model object has been proven to already be in the entity map
3006
-     *
3007
-     * @return boolean
3008
-     * @throws ReflectionException
3009
-     * @throws InvalidArgumentException
3010
-     * @throws InvalidInterfaceException
3011
-     * @throws InvalidDataTypeException
3012
-     * @throws EE_Error
3013
-     */
3014
-    public function in_entity_map()
3015
-    {
3016
-        // well, if we looked, did we find it in the entity map?
3017
-        return $this->ID() && $this->get_model()->get_from_entity_map($this->ID()) === $this;
3018
-    }
3019
-
3020
-
3021
-    /**
3022
-     * refresh_from_db
3023
-     * Makes sure the fields and values on this model object are in-sync with what's in the database.
3024
-     *
3025
-     * @throws ReflectionException
3026
-     * @throws InvalidArgumentException
3027
-     * @throws InvalidInterfaceException
3028
-     * @throws InvalidDataTypeException
3029
-     * @throws EE_Error if this model object isn't in the entity mapper (because then you should
3030
-     * just use what's in the entity mapper and refresh it) and WP_DEBUG is TRUE
3031
-     */
3032
-    public function refresh_from_db()
3033
-    {
3034
-        if ($this->ID() && $this->in_entity_map()) {
3035
-            $this->get_model()->refresh_entity_map_from_db($this->ID());
3036
-        } else {
3037
-            // if it doesn't have ID, you shouldn't be asking to refresh it from teh database (because its not in the database)
3038
-            // if it has an ID but it's not in the map, and you're asking me to refresh it
3039
-            // that's kinda dangerous. You should just use what's in the entity map, or add this to the entity map if there's
3040
-            // absolutely nothing in it for this ID
3041
-            if (WP_DEBUG) {
3042
-                throw new EE_Error(
3043
-                    sprintf(
3044
-                        esc_html__(
3045
-                            'Trying to refresh a model object with ID "%1$s" that\'s not in the entity map? First off: you should put it in the entity map by calling %2$s. Second off, if you want what\'s in the database right now, you should just call %3$s yourself and discard this model object.',
3046
-                            'event_espresso'
3047
-                        ),
3048
-                        $this->ID(),
3049
-                        get_class($this->get_model()) . '::instance()->add_to_entity_map()',
3050
-                        get_class($this->get_model()) . '::instance()->refresh_entity_map()'
3051
-                    )
3052
-                );
3053
-            }
3054
-        }
3055
-    }
3056
-
3057
-
3058
-    /**
3059
-     * Change $fields' values to $new_value_sql (which is a string of raw SQL)
3060
-     *
3061
-     * @param EE_Model_Field_Base[] $fields
3062
-     * @param string                $new_value_sql
3063
-     *          example: 'column_name=123',
3064
-     *          or 'column_name=column_name+1',
3065
-     *          or 'column_name= CASE
3066
-     *          WHEN (`column_name` + `other_column` + 5) <= `yet_another_column`
3067
-     *          THEN `column_name` + 5
3068
-     *          ELSE `column_name`
3069
-     *          END'
3070
-     *          Also updates $field on this model object with the latest value from the database.
3071
-     * @return bool
3072
-     * @throws EE_Error
3073
-     * @throws InvalidArgumentException
3074
-     * @throws InvalidDataTypeException
3075
-     * @throws InvalidInterfaceException
3076
-     * @throws ReflectionException
3077
-     * @since 4.9.80.p
3078
-     */
3079
-    protected function updateFieldsInDB($fields, $new_value_sql)
3080
-    {
3081
-        // First make sure this model object actually exists in the DB. It would be silly to try to update it in the DB
3082
-        // if it wasn't even there to start off.
3083
-        if (! $this->ID()) {
3084
-            $this->save();
3085
-        }
3086
-        global $wpdb;
3087
-        if (empty($fields)) {
3088
-            throw new InvalidArgumentException(
3089
-                esc_html__(
3090
-                    'EE_Base_Class::updateFieldsInDB was passed an empty array of fields.',
3091
-                    'event_espresso'
3092
-                )
3093
-            );
3094
-        }
3095
-        $first_field = reset($fields);
3096
-        $table_alias = $first_field->get_table_alias();
3097
-        foreach ($fields as $field) {
3098
-            if ($table_alias !== $field->get_table_alias()) {
3099
-                throw new InvalidArgumentException(
3100
-                    sprintf(
3101
-                        esc_html__(
3102
-                        // @codingStandardsIgnoreStart
3103
-                            'EE_Base_Class::updateFieldsInDB was passed fields for different tables ("%1$s" and "%2$s"), which is not supported. Instead, please call the method multiple times.',
3104
-                            // @codingStandardsIgnoreEnd
3105
-                            'event_espresso'
3106
-                        ),
3107
-                        $table_alias,
3108
-                        $field->get_table_alias()
3109
-                    )
3110
-                );
3111
-            }
3112
-        }
3113
-        // Ok the fields are now known to all be for the same table. Proceed with creating the SQL to update it.
3114
-        $table_obj      = $this->get_model()->get_table_obj_by_alias($table_alias);
3115
-        $table_pk_value = $this->ID();
3116
-        $table_name     = $table_obj->get_table_name();
3117
-        if ($table_obj instanceof EE_Secondary_Table) {
3118
-            $table_pk_field_name = $table_obj->get_fk_on_table();
3119
-        } else {
3120
-            $table_pk_field_name = $table_obj->get_pk_column();
3121
-        }
3122
-
3123
-        $query  =
3124
-            "UPDATE `{$table_name}`
18
+	/**
19
+	 * @var EEM_Base|null
20
+	 */
21
+	protected $_model = null;
22
+
23
+	/**
24
+	 * This is an array of the original properties and values provided during construction
25
+	 * of this model object. (keys are model field names, values are their values).
26
+	 * This list is important to remember so that when we are merging data from the db, we know
27
+	 * which values to override and which to not override.
28
+	 */
29
+	protected ?array $_props_n_values_provided_in_constructor = null;
30
+
31
+	/**
32
+	 * Timezone
33
+	 * This gets set by the "set_timezone()" method so that we know what timezone incoming strings|timestamps are in.
34
+	 * This can also be used before a get to set what timezone you want strings coming out of the object to be in.  NOT
35
+	 * all EE_Base_Class child classes use this property but any that use a EE_Datetime_Field data type will have
36
+	 * access to it.
37
+	 */
38
+	protected string $_timezone = '';
39
+
40
+	/**
41
+	 * date format
42
+	 * pattern or format for displaying dates
43
+	 */
44
+	protected string $_dt_frmt = '';
45
+
46
+	/**
47
+	 * time format
48
+	 * pattern or format for displaying time
49
+	 */
50
+	protected string $_tm_frmt = '';
51
+
52
+	/**
53
+	 * This property is for holding a cached array of object properties indexed by property name as the key.
54
+	 * The purpose of this is for setting a cache on properties that may have calculated values after a
55
+	 * prepare_for_get.  That way the cache can be checked first and the calculated property returned instead of having
56
+	 * to recalculate. Used by _set_cached_property() and _get_cached_property() methods.
57
+	 */
58
+	protected array $_cached_properties = [];
59
+
60
+	/**
61
+	 * An array containing keys of the related model, and values are either an array of related mode objects or a
62
+	 * single
63
+	 * related model object. see the model's _model_relations. The keys should match those specified. And if the
64
+	 * relation is of type EE_Belongs_To (or one of its children), then there should only be ONE related model object,
65
+	 * all others have an array)
66
+	 */
67
+	protected array $_model_relations = [];
68
+
69
+	/**
70
+	 * Array where keys are field names (see the model's _fields property) and values are their values. To see what
71
+	 * their types should be, look at what that field object returns on its prepare_for_get and prepare_for_set methods)
72
+	 */
73
+	protected array $_fields = [];
74
+
75
+	/**
76
+	 * indicating whether or not this model object is intended to ever be saved
77
+	 * For example, we might create model objects intended to only be used for the duration
78
+	 * of this request and to be thrown away, and if they were accidentally saved
79
+	 * it would be a bug.
80
+	 */
81
+	protected bool $_allow_persist = true;
82
+
83
+	/**
84
+	 * indicating whether or not this model object's properties have changed since construction
85
+	 */
86
+	protected bool $_has_changes = false;
87
+
88
+	/**
89
+	 * This is a cache of results from custom selections done on a query that constructs this entity. The only purpose
90
+	 * for these values is for retrieval of the results, they are not further queryable and they are not persisted to
91
+	 * the db.  They also do not automatically update if there are any changes to the data that produced their results.
92
+	 * The format is a simple array of field_alias => field_value.  So for instance if a custom select was something
93
+	 * like,  "Select COUNT(Registration.REG_ID) as Registration_Count ...", then the resulting value will be in this
94
+	 * array as:
95
+	 * array(
96
+	 *  'Registration_Count' => 24
97
+	 * );
98
+	 * Note: if the custom select configuration for the query included a data type, the value will be in the data type
99
+	 * provided for the query (@see EventEspresso\core\domain\values\model\CustomSelects::__construct phpdocs for more
100
+	 * info)
101
+	 */
102
+	protected array $custom_selection_results = [];
103
+
104
+
105
+	/**
106
+	 * basic constructor for Event Espresso classes, performs any necessary initialization, and verifies it's children
107
+	 * play nice
108
+	 *
109
+	 * @param array   $fieldValues                             where each key is a field (ie, array key in the 2nd
110
+	 *                                                         layer of the model's _fields array, (eg, EVT_ID,
111
+	 *                                                         TXN_amount, QST_name, etc) and values are their values
112
+	 * @param boolean $bydb                                    a flag for setting if the class is instantiated by the
113
+	 *                                                         corresponding db model or not.
114
+	 * @param string  $timezone                                indicate what timezone you want any datetime fields to
115
+	 *                                                         be in when instantiating a EE_Base_Class object.
116
+	 * @param array   $date_formats                            An array of date formats to set on construct where first
117
+	 *                                                         value is the date_format and second value is the time
118
+	 *                                                         format.
119
+	 * @throws InvalidArgumentException
120
+	 * @throws InvalidInterfaceException
121
+	 * @throws InvalidDataTypeException
122
+	 * @throws EE_Error
123
+	 * @throws ReflectionException
124
+	 */
125
+	protected function __construct($fieldValues = [], $bydb = false, $timezone = '', $date_formats = [])
126
+	{
127
+		$className = get_class($this);
128
+		do_action("AHEE__{$className}__construct", $this, $fieldValues);
129
+		$model        = $this->get_model();
130
+		$model_fields = $model->field_settings();
131
+		// ensure $fieldValues is an array
132
+		$fieldValues = is_array($fieldValues) ? $fieldValues : [$fieldValues];
133
+		// verify client code has not passed any invalid field names
134
+		foreach ($fieldValues as $field_name => $field_value) {
135
+			if (! isset($model_fields[ $field_name ])) {
136
+				throw new EE_Error(
137
+					sprintf(
138
+						esc_html__(
139
+							'Invalid field (%s) passed to constructor of %s. Allowed fields are :%s',
140
+							'event_espresso'
141
+						),
142
+						$field_name,
143
+						get_class($this),
144
+						implode(', ', array_keys($model_fields))
145
+					)
146
+				);
147
+			}
148
+		}
149
+
150
+		$date_format     = null;
151
+		$time_format     = null;
152
+		$this->_timezone = EEH_DTT_Helper::get_valid_timezone_string($timezone);
153
+		if (! empty($date_formats) && is_array($date_formats)) {
154
+			[$date_format, $time_format] = $date_formats;
155
+		}
156
+		$this->set_date_format($date_format);
157
+		$this->set_time_format($time_format);
158
+		// if db model is instantiating
159
+		foreach ($model_fields as $fieldName => $field) {
160
+			if ($bydb) {
161
+				// client code has indicated these field values are from the database
162
+				$this->set_from_db(
163
+					$fieldName,
164
+					$fieldValues[ $fieldName ] ?? null
165
+				);
166
+			} else {
167
+				// we're constructing a brand new instance of the model object.
168
+				// Generally, this means we'll need to do more field validation
169
+				$this->set(
170
+					$fieldName,
171
+					$fieldValues[ $fieldName ] ?? null,
172
+					true
173
+				);
174
+			}
175
+		}
176
+		// remember what values were passed to this constructor
177
+		$this->_props_n_values_provided_in_constructor = $fieldValues;
178
+		// remember in entity mapper
179
+		if (! $bydb && $model->has_primary_key_field() && $this->ID()) {
180
+			$model->add_to_entity_map($this);
181
+		}
182
+		// setup all the relations
183
+		foreach ($model->relation_settings() as $relation_name => $relation_obj) {
184
+			if ($relation_obj instanceof EE_Belongs_To_Relation) {
185
+				$this->_model_relations[ $relation_name ] = null;
186
+			} else {
187
+				$this->_model_relations[ $relation_name ] = [];
188
+			}
189
+		}
190
+		/**
191
+		 * Action done at the end of each model object construction
192
+		 *
193
+		 * @param EE_Base_Class $this the model object just created
194
+		 */
195
+		do_action('AHEE__EE_Base_Class__construct__finished', $this);
196
+	}
197
+
198
+
199
+	/**
200
+	 * Gets whether or not this model object is allowed to persist/be saved to the database.
201
+	 *
202
+	 * @return boolean
203
+	 */
204
+	public function allow_persist()
205
+	{
206
+		return $this->_allow_persist;
207
+	}
208
+
209
+
210
+	/**
211
+	 * Sets whether or not this model object should be allowed to be saved to the DB.
212
+	 * Normally once this is set to FALSE you wouldn't set it back to TRUE, unless
213
+	 * you got new information that somehow made you change your mind.
214
+	 *
215
+	 * @param boolean $allow_persist
216
+	 * @return boolean
217
+	 */
218
+	public function set_allow_persist($allow_persist)
219
+	{
220
+		return $this->_allow_persist = $allow_persist;
221
+	}
222
+
223
+
224
+	/**
225
+	 * Gets the field's original value when this object was constructed during this request.
226
+	 * This can be helpful when determining if a model object has changed or not
227
+	 *
228
+	 * @param string $field_name
229
+	 * @return mixed|null
230
+	 * @throws ReflectionException
231
+	 * @throws InvalidArgumentException
232
+	 * @throws InvalidInterfaceException
233
+	 * @throws InvalidDataTypeException
234
+	 * @throws EE_Error
235
+	 */
236
+	public function get_original($field_name)
237
+	{
238
+		if (
239
+			isset($this->_props_n_values_provided_in_constructor[ $field_name ])
240
+			&& $field_settings = $this->get_model()->field_settings_for($field_name)
241
+		) {
242
+			return $field_settings->prepare_for_get($this->_props_n_values_provided_in_constructor[ $field_name ]);
243
+		}
244
+		return null;
245
+	}
246
+
247
+
248
+	/**
249
+	 * @param EE_Base_Class $obj
250
+	 * @return string
251
+	 */
252
+	public function get_class($obj)
253
+	{
254
+		return get_class($obj);
255
+	}
256
+
257
+
258
+	/**
259
+	 * Overrides parent because parent expects old models.
260
+	 * This also doesn't do any validation, and won't work for serialized arrays
261
+	 *
262
+	 * @param string $field_name
263
+	 * @param mixed  $field_value
264
+	 * @param bool   $use_default
265
+	 * @throws InvalidArgumentException
266
+	 * @throws InvalidInterfaceException
267
+	 * @throws InvalidDataTypeException
268
+	 * @throws EE_Error
269
+	 * @throws ReflectionException
270
+	 * @throws Exception
271
+	 */
272
+	public function set(string $field_name, $field_value, bool $use_default = false)
273
+	{
274
+		// if not using default and nothing has changed, and object has already been setup (has ID),
275
+		// then don't do anything
276
+		if (
277
+			! $use_default
278
+			&& $this->_fields[ $field_name ] === $field_value
279
+			&& $this->ID()
280
+		) {
281
+			return;
282
+		}
283
+		$model              = $this->get_model();
284
+		$this->_has_changes = true;
285
+		$field_obj          = $model->field_settings_for($field_name);
286
+		if (! $field_obj instanceof EE_Model_Field_Base) {
287
+			throw new EE_Error(
288
+				sprintf(
289
+					esc_html__(
290
+						'A valid EE_Model_Field_Base could not be found for the given field name: %s',
291
+						'event_espresso'
292
+					),
293
+					$field_name
294
+				)
295
+			);
296
+		}
297
+		// if ( method_exists( $field_obj, 'set_timezone' )) {
298
+		if ($field_obj instanceof EE_Datetime_Field) {
299
+			$field_obj->set_timezone($this->_timezone);
300
+			$field_obj->set_date_format($this->_dt_frmt);
301
+			$field_obj->set_time_format($this->_tm_frmt);
302
+		}
303
+
304
+		// should the value be null?
305
+		$value = $field_value === null && ($use_default || ! $field_obj->is_nullable())
306
+			? $field_obj->get_default_value()
307
+			: $field_value;
308
+
309
+		$this->_fields[ $field_name ] = $field_obj->prepare_for_set($value);
310
+
311
+		// if we're not in the constructor...
312
+		// now check if what we set was a primary key
313
+		if (
314
+			// note: props_n_values_provided_in_constructor is only set at the END of the constructor
315
+			$this->_props_n_values_provided_in_constructor
316
+			&& $field_value
317
+			&& $field_name === $model->primary_key_name()
318
+		) {
319
+			// if so, we want all this object's fields to be filled either with
320
+			// what we've explicitly set on this model
321
+			// or what we have in the db
322
+			// echo "setting primary key!";
323
+			$fields_on_model = self::_get_model(get_class($this))->field_settings();
324
+			$obj_in_db       = self::_get_model(get_class($this))->get_one_by_ID($field_value);
325
+			foreach ($fields_on_model as $field_obj) {
326
+				if (
327
+					! array_key_exists($field_obj->get_name(), $this->_props_n_values_provided_in_constructor)
328
+					&& $field_obj->get_name() !== $field_name
329
+				) {
330
+					$this->set($field_obj->get_name(), $obj_in_db->get($field_obj->get_name()));
331
+				}
332
+			}
333
+			// oh this model object has an ID? well make sure its in the entity mapper
334
+			$model->add_to_entity_map($this);
335
+		}
336
+		// let's unset any cache for this field_name from the $_cached_properties property.
337
+		$this->_clear_cached_property($field_name);
338
+	}
339
+
340
+
341
+	/**
342
+	 * Overrides parent because parent expects old models.
343
+	 * This also doesn't do any validation, and won't work for serialized arrays
344
+	 *
345
+	 * @param string $field_name
346
+	 * @param mixed  $field_value_from_db
347
+	 * @throws ReflectionException
348
+	 * @throws InvalidArgumentException
349
+	 * @throws InvalidInterfaceException
350
+	 * @throws InvalidDataTypeException
351
+	 * @throws EE_Error
352
+	 */
353
+	public function set_from_db(string $field_name, $field_value_from_db)
354
+	{
355
+		$field_obj = $this->get_model()->field_settings_for($field_name);
356
+		if ($field_obj instanceof EE_Model_Field_Base) {
357
+			// you would think the DB has no NULLs for non-null label fields right? wrong!
358
+			// eg, a CPT model object could have an entry in the posts table, but no
359
+			// entry in the meta table. Meaning that all its columns in the meta table
360
+			// are null! yikes! so when we find one like that, use defaults for its meta columns
361
+			if ($field_value_from_db === null) {
362
+				if ($field_obj->is_nullable()) {
363
+					// if the field allows nulls, then let it be null
364
+					$field_value = null;
365
+				} else {
366
+					$field_value = $field_obj->get_default_value();
367
+				}
368
+			} else {
369
+				$field_value = $field_obj->prepare_for_set_from_db($field_value_from_db);
370
+			}
371
+			$this->_fields[ $field_name ] = $field_value;
372
+			$this->_clear_cached_property($field_name);
373
+		}
374
+	}
375
+
376
+
377
+	/**
378
+	 * Set custom select values for model.
379
+	 *
380
+	 * @param array $custom_select_values
381
+	 */
382
+	public function setCustomSelectsValues(array $custom_select_values)
383
+	{
384
+		$this->custom_selection_results = $custom_select_values;
385
+	}
386
+
387
+
388
+	/**
389
+	 * Returns the custom select value for the provided alias if its set.
390
+	 * If not set, returns null.
391
+	 *
392
+	 * @param string $alias
393
+	 * @return string|int|float|null
394
+	 */
395
+	public function getCustomSelect($alias)
396
+	{
397
+		return $this->custom_selection_results[ $alias ] ?? null;
398
+	}
399
+
400
+
401
+	/**
402
+	 * This sets the field value on the db column if it exists for the given $column_name or
403
+	 * saves it to EE_Extra_Meta if the given $column_name does not match a db column.
404
+	 *
405
+	 * @param string $field_name  Must be the exact column name.
406
+	 * @param mixed  $field_value The value to set.
407
+	 * @return int|bool @see EE_Base_Class::update_extra_meta() for return docs.
408
+	 * @throws InvalidArgumentException
409
+	 * @throws InvalidInterfaceException
410
+	 * @throws InvalidDataTypeException
411
+	 * @throws EE_Error
412
+	 * @throws ReflectionException
413
+	 * @see EE_message::get_column_value for related documentation on the necessity of this method.
414
+	 */
415
+	public function set_field_or_extra_meta($field_name, $field_value)
416
+	{
417
+		if ($this->get_model()->has_field($field_name)) {
418
+			$this->set($field_name, $field_value);
419
+			return true;
420
+		}
421
+		// ensure this object is saved first so that extra meta can be properly related.
422
+		$this->save();
423
+		return $this->update_extra_meta($field_name, $field_value);
424
+	}
425
+
426
+
427
+	/**
428
+	 * This retrieves the value of the db column set on this class or if that's not present
429
+	 * it will attempt to retrieve from extra_meta if found.
430
+	 * Example Usage:
431
+	 * Via EE_Message child class:
432
+	 * Due to the dynamic nature of the EE_messages system, EE_messengers will always have a "to",
433
+	 * "from", "subject", and "content" field (as represented in the EE_Message schema), however they may
434
+	 * also have additional main fields specific to the messenger.  The system accommodates those extra
435
+	 * fields through the EE_Extra_Meta table.  This method allows for EE_messengers to retrieve the
436
+	 * value for those extra fields dynamically via the EE_message object.
437
+	 *
438
+	 * @param string $field_name expecting the fully qualified field name.
439
+	 * @return mixed|null  value for the field if found.  null if not found.
440
+	 * @throws ReflectionException
441
+	 * @throws InvalidArgumentException
442
+	 * @throws InvalidInterfaceException
443
+	 * @throws InvalidDataTypeException
444
+	 * @throws EE_Error
445
+	 */
446
+	public function get_field_or_extra_meta($field_name)
447
+	{
448
+		if ($this->get_model()->has_field($field_name)) {
449
+			$column_value = $this->get($field_name);
450
+		} else {
451
+			// This isn't a column in the main table, let's see if it is in the extra meta.
452
+			$column_value = $this->get_extra_meta($field_name, true, null);
453
+		}
454
+		return $column_value;
455
+	}
456
+
457
+
458
+	/**
459
+	 * See $_timezone property for description of what the timezone property is for.  This SETS the timezone internally
460
+	 * for being able to reference what timezone we are running conversions on when converting TO the internal timezone
461
+	 * (UTC Unix Timestamp) for the object OR when converting FROM the internal timezone (UTC Unix Timestamp). This is
462
+	 * available to all child classes that may be using the EE_Datetime_Field for a field data type.
463
+	 *
464
+	 * @access public
465
+	 * @param string $timezone A valid timezone string as described by @link http://www.php.net/manual/en/timezones.php
466
+	 * @return void
467
+	 * @throws InvalidArgumentException
468
+	 * @throws InvalidInterfaceException
469
+	 * @throws InvalidDataTypeException
470
+	 * @throws EE_Error
471
+	 * @throws ReflectionException
472
+	 * @throws Exception
473
+	 */
474
+	public function set_timezone($timezone = '')
475
+	{
476
+		$this->_timezone = EEH_DTT_Helper::get_valid_timezone_string($timezone);
477
+		// make sure we clear all cached properties because they won't be relevant now
478
+		$this->_clear_cached_properties();
479
+		// make sure we update field settings and the date for all EE_Datetime_Fields
480
+		$model_fields = $this->get_model()->field_settings(false);
481
+		foreach ($model_fields as $field_name => $field_obj) {
482
+			if ($field_obj instanceof EE_Datetime_Field) {
483
+				$field_obj->set_timezone($this->_timezone);
484
+				if (isset($this->_fields[ $field_name ]) && $this->_fields[ $field_name ] instanceof DateTime) {
485
+					EEH_DTT_Helper::setTimezone($this->_fields[ $field_name ], new DateTimeZone($this->_timezone));
486
+				}
487
+			}
488
+		}
489
+	}
490
+
491
+
492
+	/**
493
+	 * This just returns whatever is set for the current timezone.
494
+	 *
495
+	 * @access public
496
+	 * @return string timezone string
497
+	 */
498
+	public function get_timezone()
499
+	{
500
+		return $this->_timezone;
501
+	}
502
+
503
+
504
+	/**
505
+	 * This sets the internal date format to what is sent in to be used as the new default for the class
506
+	 * internally instead of wp set date format options
507
+	 *
508
+	 * @param string|null $format should be a format recognizable by PHP date() functions.
509
+	 * @since 4.6
510
+	 */
511
+	public function set_date_format(?string $format)
512
+	{
513
+		$this->_dt_frmt = new DateFormat($format);
514
+		// clear cached_properties because they won't be relevant now.
515
+		$this->_clear_cached_properties();
516
+	}
517
+
518
+
519
+	/**
520
+	 * This sets the internal time format string to what is sent in to be used as the new default for the
521
+	 * class internally instead of wp set time format options.
522
+	 *
523
+	 * @param string|null $format should be a format recognizable by PHP date() functions.
524
+	 * @since 4.6
525
+	 */
526
+	public function set_time_format(?string $format)
527
+	{
528
+		$this->_tm_frmt = new TimeFormat($format);
529
+		// clear cached_properties because they won't be relevant now.
530
+		$this->_clear_cached_properties();
531
+	}
532
+
533
+
534
+	/**
535
+	 * This returns the current internal set format for the date and time formats.
536
+	 *
537
+	 * @param bool $full           if true (default), then return the full format.  Otherwise will return an array
538
+	 *                             where the first value is the date format and the second value is the time format.
539
+	 * @return string|array
540
+	 */
541
+	public function get_format($full = true)
542
+	{
543
+		return $full ? $this->_dt_frmt . ' ' . $this->_tm_frmt : [$this->_dt_frmt, $this->_tm_frmt];
544
+	}
545
+
546
+
547
+	/**
548
+	 * cache
549
+	 * stores the passed model object on the current model object.
550
+	 * In certain circumstances, we can use this cached model object instead of querying for another one entirely.
551
+	 *
552
+	 * @param string        $relation_name   one of the keys in the _model_relations array on the model. Eg
553
+	 *                                       'Registration' associated with this model object
554
+	 * @param EE_Base_Class $object_to_cache that has a relation to this model object. (Eg, if this is a Transaction,
555
+	 *                                       that could be a payment or a registration)
556
+	 * @param null          $cache_id        a string or number that will be used as the key for any Belongs_To_Many
557
+	 *                                       items which will be stored in an array on this object
558
+	 * @return mixed    index into cache, or just TRUE if the relation is of type Belongs_To (because there's only one
559
+	 *                                       related thing, no array)
560
+	 * @throws InvalidArgumentException
561
+	 * @throws InvalidInterfaceException
562
+	 * @throws InvalidDataTypeException
563
+	 * @throws EE_Error
564
+	 * @throws ReflectionException
565
+	 */
566
+	public function cache($relation_name = '', $object_to_cache = null, $cache_id = null)
567
+	{
568
+		// its entirely possible that there IS no related object yet in which case there is nothing to cache.
569
+		if (! $object_to_cache instanceof EE_Base_Class) {
570
+			return false;
571
+		}
572
+		// also get "how" the object is related, or throw an error
573
+		if (! $relationship_to_model = $this->get_model()->related_settings_for($relation_name)) {
574
+			throw new EE_Error(
575
+				sprintf(
576
+					esc_html__('There is no relationship to %s on a %s. Cannot cache it', 'event_espresso'),
577
+					$relation_name,
578
+					get_class($this)
579
+				)
580
+			);
581
+		}
582
+		// how many things are related ?
583
+		if ($relationship_to_model instanceof EE_Belongs_To_Relation) {
584
+			// if it's a "belongs to" relationship, then there's only one related model object
585
+			// eg, if this is a registration, there's only 1 attendee for it
586
+			// so for these model objects just set it to be cached
587
+			$this->_model_relations[ $relation_name ] = $object_to_cache;
588
+			$return                                   = true;
589
+		} else {
590
+			// otherwise, this is the "many" side of a one to many relationship,
591
+			// so we'll add the object to the array of related objects for that type.
592
+			// eg: if this is an event, there are many registrations for that event,
593
+			// so we cache the registrations in an array
594
+			if (! is_array($this->_model_relations[ $relation_name ])) {
595
+				// if for some reason, the cached item is a model object,
596
+				// then stick that in the array, otherwise start with an empty array
597
+				$this->_model_relations[ $relation_name ] =
598
+					$this->_model_relations[ $relation_name ] instanceof EE_Base_Class
599
+						? [$this->_model_relations[ $relation_name ]]
600
+						: [];
601
+			}
602
+			// first check for a cache_id which is normally empty
603
+			if (! empty($cache_id)) {
604
+				// if the cache_id exists, then it means we are purposely trying to cache this
605
+				// with a known key that can then be used to retrieve the object later on
606
+				$this->_model_relations[ $relation_name ][ $cache_id ] = $object_to_cache;
607
+				$return                                                = $cache_id;
608
+			} elseif ($object_to_cache->ID()) {
609
+				// OR the cached object originally came from the db, so let's just use it's PK for an ID
610
+				$this->_model_relations[ $relation_name ][ $object_to_cache->ID() ] = $object_to_cache;
611
+				$return                                                             = $object_to_cache->ID();
612
+			} else {
613
+				// OR it's a new object with no ID, so just throw it in the array with an auto-incremented ID
614
+				$this->_model_relations[ $relation_name ][] = $object_to_cache;
615
+				// move the internal pointer to the end of the array
616
+				end($this->_model_relations[ $relation_name ]);
617
+				// and grab the key so that we can return it
618
+				$return = key($this->_model_relations[ $relation_name ]);
619
+			}
620
+		}
621
+		return $return;
622
+	}
623
+
624
+
625
+	/**
626
+	 * For adding an item to the cached_properties property.
627
+	 *
628
+	 * @access protected
629
+	 * @param string      $fieldname the property item the corresponding value is for.
630
+	 * @param mixed       $value     The value we are caching.
631
+	 * @param string|null $cache_type
632
+	 * @return void
633
+	 * @throws ReflectionException
634
+	 * @throws InvalidArgumentException
635
+	 * @throws InvalidInterfaceException
636
+	 * @throws InvalidDataTypeException
637
+	 * @throws EE_Error
638
+	 */
639
+	protected function _set_cached_property($fieldname, $value, $cache_type = null)
640
+	{
641
+		// first make sure this property exists
642
+		$this->get_model()->field_settings_for($fieldname);
643
+		$cache_type = empty($cache_type) ? 'standard' : $cache_type;
644
+
645
+		$this->_cached_properties[ $fieldname ][ $cache_type ] = $value;
646
+	}
647
+
648
+
649
+	/**
650
+	 * This returns the value cached property if it exists OR the actual property value if the cache doesn't exist.
651
+	 * This also SETS the cache if we return the actual property!
652
+	 *
653
+	 * @param string $fieldname        the name of the property we're trying to retrieve
654
+	 * @param bool   $pretty
655
+	 * @param string $extra_cache_ref  This allows the user to specify an extra cache ref for the given property
656
+	 *                                 (in cases where the same property may be used for different outputs
657
+	 *                                 - i.e. datetime, money etc.)
658
+	 *                                 It can also accept certain pre-defined "schema" strings
659
+	 *                                 to define how to output the property.
660
+	 *                                 see the field's prepare_for_pretty_echoing for what strings can be used
661
+	 * @return mixed                   whatever the value for the property is we're retrieving
662
+	 * @throws ReflectionException
663
+	 * @throws InvalidArgumentException
664
+	 * @throws InvalidInterfaceException
665
+	 * @throws InvalidDataTypeException
666
+	 * @throws EE_Error
667
+	 */
668
+	protected function _get_cached_property($fieldname, $pretty = false, $extra_cache_ref = null)
669
+	{
670
+		// verify the field exists
671
+		$model = $this->get_model();
672
+		$model->field_settings_for($fieldname);
673
+		$cache_type = $pretty ? 'pretty' : 'standard';
674
+		$cache_type .= ! empty($extra_cache_ref) ? '_' . $extra_cache_ref : '';
675
+		if (isset($this->_cached_properties[ $fieldname ][ $cache_type ])) {
676
+			return $this->_cached_properties[ $fieldname ][ $cache_type ];
677
+		}
678
+		$value = $this->_get_fresh_property($fieldname, $pretty, $extra_cache_ref);
679
+		$this->_set_cached_property($fieldname, $value, $cache_type);
680
+		return $value;
681
+	}
682
+
683
+
684
+	/**
685
+	 * If the cache didn't fetch the needed item, this fetches it.
686
+	 *
687
+	 * @param string $fieldname
688
+	 * @param bool   $pretty
689
+	 * @param string $extra_cache_ref
690
+	 * @return mixed
691
+	 * @throws InvalidArgumentException
692
+	 * @throws InvalidInterfaceException
693
+	 * @throws InvalidDataTypeException
694
+	 * @throws EE_Error
695
+	 * @throws ReflectionException
696
+	 */
697
+	protected function _get_fresh_property($fieldname, $pretty = false, $extra_cache_ref = null)
698
+	{
699
+		$field_obj = $this->get_model()->field_settings_for($fieldname);
700
+		// If this is an EE_Datetime_Field we need to make sure timezone, formats, and output are correct
701
+		if ($field_obj instanceof EE_Datetime_Field) {
702
+			$this->_prepare_datetime_field($field_obj, $pretty, $extra_cache_ref);
703
+		}
704
+		if (! isset($this->_fields[ $fieldname ])) {
705
+			$this->_fields[ $fieldname ] = null;
706
+		}
707
+		return $pretty
708
+			? $field_obj->prepare_for_pretty_echoing($this->_fields[ $fieldname ], $extra_cache_ref)
709
+			: $field_obj->prepare_for_get($this->_fields[ $fieldname ]);
710
+	}
711
+
712
+
713
+	/**
714
+	 * set timezone, formats, and output for EE_Datetime_Field objects
715
+	 *
716
+	 * @param EE_Datetime_Field $datetime_field
717
+	 * @param bool              $pretty
718
+	 * @param null              $date_or_time
719
+	 * @return void
720
+	 * @throws InvalidArgumentException
721
+	 * @throws InvalidInterfaceException
722
+	 * @throws InvalidDataTypeException
723
+	 */
724
+	protected function _prepare_datetime_field(
725
+		EE_Datetime_Field $datetime_field,
726
+		$pretty = false,
727
+		$date_or_time = null
728
+	) {
729
+		$datetime_field->set_timezone($this->_timezone);
730
+		$datetime_field->set_date_format($this->_dt_frmt, $pretty);
731
+		$datetime_field->set_time_format($this->_tm_frmt, $pretty);
732
+		// set the output returned
733
+		switch ($date_or_time) {
734
+			case 'D':
735
+				$datetime_field->set_date_time_output('date');
736
+				break;
737
+			case 'T':
738
+				$datetime_field->set_date_time_output('time');
739
+				break;
740
+			default:
741
+				$datetime_field->set_date_time_output();
742
+		}
743
+	}
744
+
745
+
746
+	/**
747
+	 * This just takes care of clearing out the cached_properties
748
+	 *
749
+	 * @return void
750
+	 */
751
+	protected function _clear_cached_properties()
752
+	{
753
+		$this->_cached_properties = [];
754
+	}
755
+
756
+
757
+	/**
758
+	 * This just clears out ONE property if it exists in the cache
759
+	 *
760
+	 * @param string $property_name the property to remove if it exists (from the _cached_properties array)
761
+	 * @return void
762
+	 */
763
+	protected function _clear_cached_property($property_name)
764
+	{
765
+		if (isset($this->_cached_properties[ $property_name ])) {
766
+			unset($this->_cached_properties[ $property_name ]);
767
+		}
768
+	}
769
+
770
+
771
+	/**
772
+	 * Ensures that this related thing is a model object.
773
+	 *
774
+	 * @param mixed  $object_or_id EE_base_Class/int/string either a related model object, or its ID
775
+	 * @param string $model_name   name of the related thing, eg 'Attendee',
776
+	 * @return EE_Base_Class
777
+	 * @throws ReflectionException
778
+	 * @throws InvalidArgumentException
779
+	 * @throws InvalidInterfaceException
780
+	 * @throws InvalidDataTypeException
781
+	 * @throws EE_Error
782
+	 */
783
+	protected function ensure_related_thing_is_model_obj($object_or_id, $model_name)
784
+	{
785
+		$other_model_instance = self::_get_model_instance_with_name(
786
+			self::_get_model_classname($model_name),
787
+			$this->_timezone
788
+		);
789
+		return $other_model_instance->ensure_is_obj($object_or_id);
790
+	}
791
+
792
+
793
+	/**
794
+	 * Forgets the cached model of the given relation Name. So the next time we request it,
795
+	 * we will fetch it again from the database. (Handy if you know it's changed somehow).
796
+	 * If a specific object is supplied, and the relationship to it is either a HasMany or HABTM,
797
+	 * then only remove that one object from our cached array. Otherwise, clear the entire list
798
+	 *
799
+	 * @param string $relation_name                        one of the keys in the _model_relations array on the model.
800
+	 *                                                     Eg 'Registration'
801
+	 * @param mixed  $object_to_remove_or_index_into_array or an index into the array of cached things, or NULL
802
+	 *                                                     if you intend to use $clear_all = TRUE, or the relation only
803
+	 *                                                     has 1 object anyway (ie, it's a BelongsToRelation)
804
+	 * @param bool   $clear_all                            This flags clearing the entire cache relation property if
805
+	 *                                                     this is HasMany or HABTM.
806
+	 * @return EE_Base_Class|bool                          entity that was cleared from the cache,
807
+	 *                                                     or true if we requested to remove a relation from all
808
+	 *                                                     or false if entity was never cached anyway.
809
+	 * @throws InvalidArgumentException
810
+	 * @throws InvalidInterfaceException
811
+	 * @throws InvalidDataTypeException
812
+	 * @throws EE_Error
813
+	 * @throws ReflectionException
814
+	 */
815
+	public function clear_cache($relation_name, $object_to_remove_or_index_into_array = null, $clear_all = false)
816
+	{
817
+		$relationship_to_model = $this->get_model()->related_settings_for($relation_name);
818
+		$index_in_cache        = '';
819
+		if (! $relationship_to_model) {
820
+			throw new EE_Error(
821
+				sprintf(
822
+					esc_html__('There is no relationship to %s on a %s. Cannot clear that cache', 'event_espresso'),
823
+					$relation_name,
824
+					get_class($this)
825
+				)
826
+			);
827
+		}
828
+		if ($clear_all) {
829
+			$obj_removed                              = true;
830
+			$this->_model_relations[ $relation_name ] = null;
831
+		} elseif ($relationship_to_model instanceof EE_Belongs_To_Relation) {
832
+			$obj_removed                              = $this->_model_relations[ $relation_name ];
833
+			$this->_model_relations[ $relation_name ] = null;
834
+		} else {
835
+			if (
836
+				$object_to_remove_or_index_into_array instanceof EE_Base_Class
837
+				&& $object_to_remove_or_index_into_array->ID()
838
+			) {
839
+				$index_in_cache = $object_to_remove_or_index_into_array->ID();
840
+				if (
841
+					is_array($this->_model_relations[ $relation_name ])
842
+					&& ! isset($this->_model_relations[ $relation_name ][ $index_in_cache ])
843
+				) {
844
+					$index_found_at = null;
845
+					// find this object in the array even though it has a different key
846
+					foreach ($this->_model_relations[ $relation_name ] as $index => $obj) {
847
+						/** @noinspection TypeUnsafeComparisonInspection */
848
+						if (
849
+							$obj instanceof EE_Base_Class
850
+							&& (
851
+								$obj == $object_to_remove_or_index_into_array
852
+								|| $obj->ID() === $object_to_remove_or_index_into_array->ID()
853
+							)
854
+						) {
855
+							$index_found_at = $index;
856
+							break;
857
+						}
858
+					}
859
+					if ($index_found_at) {
860
+						$index_in_cache = $index_found_at;
861
+					} else {
862
+						// it wasn't found. huh. well obviously it doesn't need to be removed from teh cache
863
+						// if it wasn't in it to begin with. So we're done
864
+						return $object_to_remove_or_index_into_array;
865
+					}
866
+				}
867
+			} elseif ($object_to_remove_or_index_into_array instanceof EE_Base_Class) {
868
+				// so they provided a model object, but it's not yet saved to the DB... so let's go hunting for it!
869
+				foreach ($this->get_all_from_cache($relation_name) as $index => $potentially_obj_we_want) {
870
+					/** @noinspection TypeUnsafeComparisonInspection */
871
+					if ($potentially_obj_we_want == $object_to_remove_or_index_into_array) {
872
+						$index_in_cache = $index;
873
+					}
874
+				}
875
+			} else {
876
+				$index_in_cache = $object_to_remove_or_index_into_array;
877
+			}
878
+			// supposedly we've found it. But it could just be that the client code
879
+			// provided a bad index/object
880
+			if (isset($this->_model_relations[ $relation_name ][ $index_in_cache ])) {
881
+				$obj_removed = $this->_model_relations[ $relation_name ][ $index_in_cache ];
882
+				unset($this->_model_relations[ $relation_name ][ $index_in_cache ]);
883
+			} else {
884
+				// that thing was never cached anyway.
885
+				$obj_removed = false;
886
+			}
887
+		}
888
+		return $obj_removed;
889
+	}
890
+
891
+
892
+	/**
893
+	 * update_cache_after_object_save
894
+	 * Allows a cached item to have it's cache ID (within the array of cached items) reset using the new ID it has
895
+	 * obtained after being saved to the db
896
+	 *
897
+	 * @param string        $relation_name      - the type of object that is cached
898
+	 * @param EE_Base_Class $newly_saved_object - the newly saved object to be re-cached
899
+	 * @param string        $current_cache_id   - the ID that was used when originally caching the object
900
+	 * @return boolean TRUE on success, FALSE on fail
901
+	 * @throws ReflectionException
902
+	 * @throws InvalidArgumentException
903
+	 * @throws InvalidInterfaceException
904
+	 * @throws InvalidDataTypeException
905
+	 * @throws EE_Error
906
+	 */
907
+	public function update_cache_after_object_save(
908
+		$relation_name,
909
+		EE_Base_Class $newly_saved_object,
910
+		$current_cache_id = ''
911
+	) {
912
+		// verify that incoming object is of the correct type
913
+		$obj_class = 'EE_' . $relation_name;
914
+		if ($newly_saved_object instanceof $obj_class) {
915
+			/* @type EE_Base_Class $newly_saved_object */
916
+			// now get the type of relation
917
+			$relationship_to_model = $this->get_model()->related_settings_for($relation_name);
918
+			// if this is a 1:1 relationship
919
+			if ($relationship_to_model instanceof EE_Belongs_To_Relation) {
920
+				// then just replace the cached object with the newly saved object
921
+				$this->_model_relations[ $relation_name ] = $newly_saved_object;
922
+				return true;
923
+				// or if it's some kind of sordid feral polyamorous relationship...
924
+			}
925
+			if (
926
+				is_array($this->_model_relations[ $relation_name ])
927
+				&& isset($this->_model_relations[ $relation_name ][ $current_cache_id ])
928
+			) {
929
+				// then remove the current cached item
930
+				unset($this->_model_relations[ $relation_name ][ $current_cache_id ]);
931
+				// and cache the newly saved object using it's new ID
932
+				$this->_model_relations[ $relation_name ][ $newly_saved_object->ID() ] = $newly_saved_object;
933
+				return true;
934
+			}
935
+		}
936
+		return false;
937
+	}
938
+
939
+
940
+	/**
941
+	 * Fetches a single EE_Base_Class on that relation. (If the relation is of type
942
+	 * BelongsTo, it will only ever have 1 object. However, other relations could have an array of objects)
943
+	 *
944
+	 * @param string $relation_name
945
+	 * @return EE_Base_Class
946
+	 */
947
+	public function get_one_from_cache($relation_name)
948
+	{
949
+		$cached_array_or_object = $this->_model_relations[ $relation_name ] ?? null;
950
+		if (is_array($cached_array_or_object)) {
951
+			return array_shift($cached_array_or_object);
952
+		}
953
+		return $cached_array_or_object;
954
+	}
955
+
956
+
957
+	/**
958
+	 * Fetches a single EE_Base_Class on that relation. (If the relation is of type
959
+	 * BelongsTo, it will only ever have 1 object. However, other relations could have an array of objects)
960
+	 *
961
+	 * @param string $relation_name
962
+	 * @return EE_Base_Class[] NOT necessarily indexed by primary keys
963
+	 * @throws InvalidArgumentException
964
+	 * @throws InvalidInterfaceException
965
+	 * @throws InvalidDataTypeException
966
+	 * @throws EE_Error
967
+	 * @throws ReflectionException
968
+	 */
969
+	public function get_all_from_cache($relation_name)
970
+	{
971
+		$objects = $this->_model_relations[ $relation_name ] ?? [];
972
+		// if the result is not an array, but exists, make it an array
973
+		$objects = is_array($objects)
974
+			? $objects
975
+			: [$objects];
976
+		// bugfix for https://events.codebasehq.com/projects/event-espresso/tickets/7143
977
+		// basically, if this model object was stored in the session, and these cached model objects
978
+		// already have IDs, let's make sure they're in their model's entity mapper
979
+		// otherwise we will have duplicates next time we call
980
+		// EE_Registry::instance()->load_model( $relation_name )->get_one_by_ID( $result->ID() );
981
+		$model = EE_Registry::instance()->load_model($relation_name);
982
+		foreach ($objects as $model_object) {
983
+			if ($model instanceof EEM_Base && $model_object instanceof EE_Base_Class) {
984
+				// ensure its in the map if it has an ID; otherwise it will be added to the map when its saved
985
+				if ($model_object->ID()) {
986
+					$model->add_to_entity_map($model_object);
987
+				}
988
+			} else {
989
+				throw new EE_Error(
990
+					sprintf(
991
+						esc_html__(
992
+							'Error retrieving related model objects. Either $1%s is not a model or $2%s is not a model object',
993
+							'event_espresso'
994
+						),
995
+						$relation_name,
996
+						gettype($model_object)
997
+					)
998
+				);
999
+			}
1000
+		}
1001
+		return $objects;
1002
+	}
1003
+
1004
+
1005
+	/**
1006
+	 * Returns the next x number of EE_Base_Class objects in sequence from this object as found in the database
1007
+	 * matching the given query conditions.
1008
+	 *
1009
+	 * @param null  $field_to_order_by  What field is being used as the reference point.
1010
+	 * @param int   $limit              How many objects to return.
1011
+	 * @param array $query_params       Any additional conditions on the query.
1012
+	 * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1013
+	 *                                  you can indicate just the columns you want returned
1014
+	 * @return array|EE_Base_Class[]
1015
+	 * @throws ReflectionException
1016
+	 * @throws InvalidArgumentException
1017
+	 * @throws InvalidInterfaceException
1018
+	 * @throws InvalidDataTypeException
1019
+	 * @throws EE_Error
1020
+	 */
1021
+	public function next_x($field_to_order_by = null, $limit = 1, $query_params = [], $columns_to_select = null)
1022
+	{
1023
+		$model         = $this->get_model();
1024
+		$field         = empty($field_to_order_by) && $model->has_primary_key_field()
1025
+			? $model->get_primary_key_field()->get_name()
1026
+			: $field_to_order_by;
1027
+		$current_value = ! empty($field)
1028
+			? $this->get($field)
1029
+			: null;
1030
+		if (empty($field) || empty($current_value)) {
1031
+			return [];
1032
+		}
1033
+		return $model->next_x($current_value, $field, $limit, $query_params, $columns_to_select);
1034
+	}
1035
+
1036
+
1037
+	/**
1038
+	 * Returns the previous x number of EE_Base_Class objects in sequence from this object as found in the database
1039
+	 * matching the given query conditions.
1040
+	 *
1041
+	 * @param null  $field_to_order_by  What field is being used as the reference point.
1042
+	 * @param int   $limit              How many objects to return.
1043
+	 * @param array $query_params       Any additional conditions on the query.
1044
+	 * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1045
+	 *                                  you can indicate just the columns you want returned
1046
+	 * @return array|EE_Base_Class[]
1047
+	 * @throws ReflectionException
1048
+	 * @throws InvalidArgumentException
1049
+	 * @throws InvalidInterfaceException
1050
+	 * @throws InvalidDataTypeException
1051
+	 * @throws EE_Error
1052
+	 */
1053
+	public function previous_x(
1054
+		$field_to_order_by = null,
1055
+		$limit = 1,
1056
+		$query_params = [],
1057
+		$columns_to_select = null
1058
+	) {
1059
+		$model         = $this->get_model();
1060
+		$field         = empty($field_to_order_by) && $model->has_primary_key_field()
1061
+			? $model->get_primary_key_field()->get_name()
1062
+			: $field_to_order_by;
1063
+		$current_value = ! empty($field) ? $this->get($field) : null;
1064
+		if (empty($field) || empty($current_value)) {
1065
+			return [];
1066
+		}
1067
+		return $model->previous_x($current_value, $field, $limit, $query_params, $columns_to_select);
1068
+	}
1069
+
1070
+
1071
+	/**
1072
+	 * Returns the next EE_Base_Class object in sequence from this object as found in the database
1073
+	 * matching the given query conditions.
1074
+	 *
1075
+	 * @param null  $field_to_order_by  What field is being used as the reference point.
1076
+	 * @param array $query_params       Any additional conditions on the query.
1077
+	 * @param null  $columns_to_select  If left null, then an array of EE_Base_Class objects is returned, otherwise
1078
+	 *                                  you can indicate just the columns you want returned
1079
+	 * @return array|EE_Base_Class
1080
+	 * @throws ReflectionException
1081
+	 * @throws InvalidArgumentException
1082
+	 * @throws InvalidInterfaceException
1083
+	 * @throws InvalidDataTypeException
1084
+	 * @throws EE_Error
1085
+	 */
1086
+	public function next($field_to_order_by = null, $query_params = [], $columns_to_select = null)
1087
+	{
1088
+		$model         = $this->get_model();
1089
+		$field         = empty($field_to_order_by) && $model->has_primary_key_field()
1090
+			? $model->get_primary_key_field()->get_name()
1091
+			: $field_to_order_by;
1092
+		$current_value = ! empty($field) ? $this->get($field) : null;
1093
+		if (empty($field) || empty($current_value)) {
1094
+			return [];
1095
+		}
1096
+		return $model->next($current_value, $field, $query_params, $columns_to_select);
1097
+	}
1098
+
1099
+
1100
+	/**
1101
+	 * Returns the previous EE_Base_Class object in sequence from this object as found in the database
1102
+	 * matching the given query conditions.
1103
+	 *
1104
+	 * @param null  $field_to_order_by  What field is being used as the reference point.
1105
+	 * @param array $query_params       Any additional conditions on the query.
1106
+	 * @param null  $columns_to_select  If left null, then an EE_Base_Class object is returned, otherwise
1107
+	 *                                  you can indicate just the column you want returned
1108
+	 * @return array|EE_Base_Class
1109
+	 * @throws ReflectionException
1110
+	 * @throws InvalidArgumentException
1111
+	 * @throws InvalidInterfaceException
1112
+	 * @throws InvalidDataTypeException
1113
+	 * @throws EE_Error
1114
+	 */
1115
+	public function previous($field_to_order_by = null, $query_params = [], $columns_to_select = null)
1116
+	{
1117
+		$model         = $this->get_model();
1118
+		$field         = empty($field_to_order_by) && $model->has_primary_key_field()
1119
+			? $model->get_primary_key_field()->get_name()
1120
+			: $field_to_order_by;
1121
+		$current_value = ! empty($field) ? $this->get($field) : null;
1122
+		if (empty($field) || empty($current_value)) {
1123
+			return [];
1124
+		}
1125
+		return $model->previous($current_value, $field, $query_params, $columns_to_select);
1126
+	}
1127
+
1128
+
1129
+	/**
1130
+	 * verifies that the specified field is of the correct type
1131
+	 *
1132
+	 * @param string $field_name
1133
+	 * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1134
+	 *                                (in cases where the same property may be used for different outputs
1135
+	 *                                - i.e. datetime, money etc.)
1136
+	 * @return mixed
1137
+	 * @throws ReflectionException
1138
+	 * @throws InvalidArgumentException
1139
+	 * @throws InvalidInterfaceException
1140
+	 * @throws InvalidDataTypeException
1141
+	 * @throws EE_Error
1142
+	 */
1143
+	public function get($field_name, $extra_cache_ref = null)
1144
+	{
1145
+		return $this->_get_cached_property($field_name, false, $extra_cache_ref);
1146
+	}
1147
+
1148
+
1149
+	/**
1150
+	 * This method simply returns the RAW unprocessed value for the given property in this class
1151
+	 *
1152
+	 * @param string $field_name A valid fieldname
1153
+	 * @return mixed              Whatever the raw value stored on the property is.
1154
+	 * @throws ReflectionException
1155
+	 * @throws InvalidArgumentException
1156
+	 * @throws InvalidInterfaceException
1157
+	 * @throws InvalidDataTypeException
1158
+	 * @throws EE_Error if fieldSettings is misconfigured or the field doesn't exist.
1159
+	 */
1160
+	public function get_raw($field_name)
1161
+	{
1162
+		$field_settings = $this->get_model()->field_settings_for($field_name);
1163
+		return $field_settings instanceof EE_Datetime_Field && $this->_fields[ $field_name ] instanceof DateTime
1164
+			? $this->_fields[ $field_name ]->format('U')
1165
+			: $this->_fields[ $field_name ];
1166
+	}
1167
+
1168
+
1169
+	/**
1170
+	 * This is used to return the internal DateTime object used for a field that is a
1171
+	 * EE_Datetime_Field.
1172
+	 *
1173
+	 * @param string $field_name               The field name retrieving the DateTime object.
1174
+	 * @return mixed null | false | DateTime  If the requested field is NOT a EE_Datetime_Field then
1175
+	 * @throws EE_Error an error is set and false returned.  If the field IS an
1176
+	 *                                         EE_Datetime_Field and but the field value is null, then
1177
+	 *                                         just null is returned (because that indicates that likely
1178
+	 *                                         this field is nullable).
1179
+	 * @throws InvalidArgumentException
1180
+	 * @throws InvalidDataTypeException
1181
+	 * @throws InvalidInterfaceException
1182
+	 * @throws ReflectionException
1183
+	 */
1184
+	public function get_DateTime_object($field_name)
1185
+	{
1186
+		$field_settings = $this->get_model()->field_settings_for($field_name);
1187
+		if (! $field_settings instanceof EE_Datetime_Field) {
1188
+			EE_Error::add_error(
1189
+				sprintf(
1190
+					esc_html__(
1191
+						'The field %s is not an EE_Datetime_Field field.  There is no DateTime object stored on this field type.',
1192
+						'event_espresso'
1193
+					),
1194
+					$field_name
1195
+				),
1196
+				__FILE__,
1197
+				__FUNCTION__,
1198
+				__LINE__
1199
+			);
1200
+			return false;
1201
+		}
1202
+		return isset($this->_fields[ $field_name ]) && $this->_fields[ $field_name ] instanceof DateTime
1203
+			? clone $this->_fields[ $field_name ]
1204
+			: null;
1205
+	}
1206
+
1207
+
1208
+	/**
1209
+	 * To be used in template to immediately echo out the value, and format it for output.
1210
+	 * Eg, should call stripslashes and whatnot before echoing
1211
+	 *
1212
+	 * @param string $field_name      the name of the field as it appears in the DB
1213
+	 * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1214
+	 *                                (in cases where the same property may be used for different outputs
1215
+	 *                                - i.e. datetime, money etc.)
1216
+	 * @return void
1217
+	 * @throws ReflectionException
1218
+	 * @throws InvalidArgumentException
1219
+	 * @throws InvalidInterfaceException
1220
+	 * @throws InvalidDataTypeException
1221
+	 * @throws EE_Error
1222
+	 */
1223
+	public function e($field_name, $extra_cache_ref = null)
1224
+	{
1225
+		echo wp_kses($this->get_pretty($field_name, $extra_cache_ref), AllowedTags::getWithFormTags());
1226
+	}
1227
+
1228
+
1229
+	/**
1230
+	 * Exactly like e(), echoes out the field, but sets its schema to 'form_input', so that it
1231
+	 * can be easily used as the value of form input.
1232
+	 *
1233
+	 * @param string $field_name
1234
+	 * @return void
1235
+	 * @throws ReflectionException
1236
+	 * @throws InvalidArgumentException
1237
+	 * @throws InvalidInterfaceException
1238
+	 * @throws InvalidDataTypeException
1239
+	 * @throws EE_Error
1240
+	 */
1241
+	public function f($field_name)
1242
+	{
1243
+		$this->e($field_name, 'form_input');
1244
+	}
1245
+
1246
+
1247
+	/**
1248
+	 * Same as `f()` but just returns the value instead of echoing it
1249
+	 *
1250
+	 * @param string $field_name
1251
+	 * @return string
1252
+	 * @throws ReflectionException
1253
+	 * @throws InvalidArgumentException
1254
+	 * @throws InvalidInterfaceException
1255
+	 * @throws InvalidDataTypeException
1256
+	 * @throws EE_Error
1257
+	 */
1258
+	public function get_f($field_name)
1259
+	{
1260
+		return (string) $this->get_pretty($field_name, 'form_input');
1261
+	}
1262
+
1263
+
1264
+	/**
1265
+	 * Gets a pretty view of the field's value. $extra_cache_ref can specify different formats for this.
1266
+	 * The $extra_cache_ref will be passed to the model field's prepare_for_pretty_echoing, so consult the field's class
1267
+	 * to see what options are available.
1268
+	 *
1269
+	 * @param string $field_name
1270
+	 * @param string $extra_cache_ref This allows the user to specify an extra cache ref for the given property
1271
+	 *                                (in cases where the same property may be used for different outputs
1272
+	 *                                - i.e. datetime, money etc.)
1273
+	 * @return mixed
1274
+	 * @throws ReflectionException
1275
+	 * @throws InvalidArgumentException
1276
+	 * @throws InvalidInterfaceException
1277
+	 * @throws InvalidDataTypeException
1278
+	 * @throws EE_Error
1279
+	 */
1280
+	public function get_pretty($field_name, $extra_cache_ref = null)
1281
+	{
1282
+		return $this->_get_cached_property($field_name, true, $extra_cache_ref);
1283
+	}
1284
+
1285
+
1286
+	/**
1287
+	 * This simply returns the datetime for the given field name
1288
+	 * Note: this protected function is called by the wrapper get_date or get_time or get_datetime functions
1289
+	 * (and the equivalent e_date, e_time, e_datetime).
1290
+	 *
1291
+	 * @access   protected
1292
+	 * @param string      $field_name   Field on the instantiated EE_Base_Class child object
1293
+	 * @param string|null $date_format  valid datetime format used for date
1294
+	 *                                  (if '' then we just use the default on the field,
1295
+	 *                                  if NULL we use the last-used format)
1296
+	 * @param string|null $time_format  Same as above except this is for time format
1297
+	 * @param string|null $date_or_time if NULL then both are returned, otherwise "D" = only date and "T" = only time.
1298
+	 * @param bool|null   $echo         Whether the datetime is pretty echoing or just returned using vanilla get
1299
+	 * @return string|bool|EE_Error string on success, FALSE on fail, or EE_Error Exception is thrown
1300
+	 *                                  if field is not a valid dtt field, or void if echoing
1301
+	 * @throws EE_Error
1302
+	 * @throws ReflectionException
1303
+	 */
1304
+	protected function _get_datetime(
1305
+		string $field_name,
1306
+		?string $date_format = '',
1307
+		?string $time_format = '',
1308
+		?string $date_or_time = '',
1309
+		?bool $echo = false
1310
+	) {
1311
+		// clear cached property
1312
+		$this->_clear_cached_property($field_name);
1313
+		// reset format properties because they are used in get()
1314
+		$this->_dt_frmt = $date_format ?: $this->_dt_frmt;
1315
+		$this->_tm_frmt = $time_format ?: $this->_tm_frmt;
1316
+		if ($echo) {
1317
+			$this->e($field_name, $date_or_time);
1318
+			return '';
1319
+		}
1320
+		return $this->get($field_name, $date_or_time);
1321
+	}
1322
+
1323
+
1324
+	/**
1325
+	 * below are wrapper functions for the various datetime outputs that can be obtained for JUST returning the date
1326
+	 * portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1327
+	 * other echoes the pretty value for dtt)
1328
+	 *
1329
+	 * @param string $field_name name of model object datetime field holding the value
1330
+	 * @param string $format     format for the date returned (if NULL we use default in dt_frmt property)
1331
+	 * @return string            datetime value formatted
1332
+	 * @throws ReflectionException
1333
+	 * @throws InvalidArgumentException
1334
+	 * @throws InvalidInterfaceException
1335
+	 * @throws InvalidDataTypeException
1336
+	 * @throws EE_Error
1337
+	 */
1338
+	public function get_date($field_name, $format = '')
1339
+	{
1340
+		return $this->_get_datetime($field_name, $format, null, 'D');
1341
+	}
1342
+
1343
+
1344
+	/**
1345
+	 * @param        $field_name
1346
+	 * @param string $format
1347
+	 * @throws ReflectionException
1348
+	 * @throws InvalidArgumentException
1349
+	 * @throws InvalidInterfaceException
1350
+	 * @throws InvalidDataTypeException
1351
+	 * @throws EE_Error
1352
+	 */
1353
+	public function e_date($field_name, $format = '')
1354
+	{
1355
+		$this->_get_datetime($field_name, $format, null, 'D', true);
1356
+	}
1357
+
1358
+
1359
+	/**
1360
+	 * below are wrapper functions for the various datetime outputs that can be obtained for JUST returning the time
1361
+	 * portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1362
+	 * other echoes the pretty value for dtt)
1363
+	 *
1364
+	 * @param string $field_name name of model object datetime field holding the value
1365
+	 * @param string $format     format for the time returned ( if NULL we use default in tm_frmt property)
1366
+	 * @return string             datetime value formatted
1367
+	 * @throws ReflectionException
1368
+	 * @throws InvalidArgumentException
1369
+	 * @throws InvalidInterfaceException
1370
+	 * @throws InvalidDataTypeException
1371
+	 * @throws EE_Error
1372
+	 */
1373
+	public function get_time($field_name, $format = '')
1374
+	{
1375
+		return $this->_get_datetime($field_name, null, $format, 'T');
1376
+	}
1377
+
1378
+
1379
+	/**
1380
+	 * @param        $field_name
1381
+	 * @param string $format
1382
+	 * @throws ReflectionException
1383
+	 * @throws InvalidArgumentException
1384
+	 * @throws InvalidInterfaceException
1385
+	 * @throws InvalidDataTypeException
1386
+	 * @throws EE_Error
1387
+	 */
1388
+	public function e_time($field_name, $format = '')
1389
+	{
1390
+		$this->_get_datetime($field_name, null, $format, 'T', true);
1391
+	}
1392
+
1393
+
1394
+	/**
1395
+	 * below are wrapper functions for the various datetime outputs that can be obtained for returning the date AND
1396
+	 * time portion of a datetime value. (note the only difference between get_ and e_ is one returns the value and the
1397
+	 * other echoes the pretty value for dtt)
1398
+	 *
1399
+	 * @param string $field_name  name of model object datetime field holding the value
1400
+	 * @param string $date_format format for the date returned (if NULL we use default in dt_frmt property)
1401
+	 * @param string $time_format format for the time returned (if NULL we use default in tm_frmt property)
1402
+	 * @return string             datetime value formatted
1403
+	 * @throws ReflectionException
1404
+	 * @throws InvalidArgumentException
1405
+	 * @throws InvalidInterfaceException
1406
+	 * @throws InvalidDataTypeException
1407
+	 * @throws EE_Error
1408
+	 */
1409
+	public function get_datetime($field_name, $date_format = '', $time_format = '')
1410
+	{
1411
+		return $this->_get_datetime($field_name, $date_format, $time_format);
1412
+	}
1413
+
1414
+
1415
+	/**
1416
+	 * @param string $field_name
1417
+	 * @param string $date_format
1418
+	 * @param string $time_format
1419
+	 * @throws ReflectionException
1420
+	 * @throws InvalidArgumentException
1421
+	 * @throws InvalidInterfaceException
1422
+	 * @throws InvalidDataTypeException
1423
+	 * @throws EE_Error
1424
+	 */
1425
+	public function e_datetime($field_name, $date_format = '', $time_format = '')
1426
+	{
1427
+		$this->_get_datetime($field_name, $date_format, $time_format, null, true);
1428
+	}
1429
+
1430
+
1431
+	/**
1432
+	 * Get the i8ln value for a date using the WordPress @param string $field_name The EE_Datetime_Field reference for
1433
+	 *                           the date being retrieved.
1434
+	 *
1435
+	 * @param string $format     PHP valid date/time string format.  If none is provided then the internal set format
1436
+	 *                           on the object will be used.
1437
+	 * @return string Date and time string in set locale or false if no field exists for the given
1438
+	 * @throws ReflectionException
1439
+	 * @throws InvalidArgumentException
1440
+	 * @throws InvalidInterfaceException
1441
+	 * @throws InvalidDataTypeException
1442
+	 * @throws EE_Error
1443
+	 *                           field name.
1444
+	 * @see date_i18n function.
1445
+	 */
1446
+	public function get_i18n_datetime(string $field_name, string $format = ''): string
1447
+	{
1448
+		$format = empty($format) ? $this->_dt_frmt . ' ' . $this->_tm_frmt : $format;
1449
+		return date_i18n(
1450
+			$format,
1451
+			EEH_DTT_Helper::get_timestamp_with_offset(
1452
+				$this->get_raw($field_name),
1453
+				$this->_timezone
1454
+			)
1455
+		);
1456
+	}
1457
+
1458
+
1459
+	/**
1460
+	 * This method validates whether the given field name is a valid field on the model object as well as it is of a
1461
+	 * type EE_Datetime_Field.  On success there will be returned the field settings.  On fail an EE_Error exception is
1462
+	 * thrown.
1463
+	 *
1464
+	 * @param string $field_name The field name being checked
1465
+	 * @return EE_Datetime_Field
1466
+	 * @throws InvalidArgumentException
1467
+	 * @throws InvalidInterfaceException
1468
+	 * @throws InvalidDataTypeException
1469
+	 * @throws EE_Error
1470
+	 * @throws ReflectionException
1471
+	 */
1472
+	protected function _get_dtt_field_settings($field_name)
1473
+	{
1474
+		$field = $this->get_model()->field_settings_for($field_name);
1475
+		// check if field is dtt
1476
+		if ($field instanceof EE_Datetime_Field) {
1477
+			return $field;
1478
+		}
1479
+		throw new EE_Error(
1480
+			sprintf(
1481
+				esc_html__(
1482
+					'The field name "%s" has been requested for the EE_Base_Class datetime functions and it is not a valid EE_Datetime_Field.  Please check the spelling of the field and make sure it has been setup as a EE_Datetime_Field in the %s model constructor',
1483
+					'event_espresso'
1484
+				),
1485
+				$field_name,
1486
+				self::_get_model_classname(get_class($this))
1487
+			)
1488
+		);
1489
+	}
1490
+
1491
+
1492
+
1493
+
1494
+	/**
1495
+	 * NOTE ABOUT BELOW:
1496
+	 * These convenience date and time setters are for setting date and time independently.  In other words you might
1497
+	 * want to change the time on a datetime_field but leave the date the same (or vice versa). IF on the other hand
1498
+	 * you want to set both date and time at the same time, you can just use the models default set($fieldname,$value)
1499
+	 * method and make sure you send the entire datetime value for setting.
1500
+	 */
1501
+	/**
1502
+	 * sets the time on a datetime property
1503
+	 *
1504
+	 * @access protected
1505
+	 * @param string|Datetime $time      a valid time string for php datetime functions (or DateTime object)
1506
+	 * @param string          $fieldname the name of the field the time is being set on (must match a EE_Datetime_Field)
1507
+	 * @throws ReflectionException
1508
+	 * @throws InvalidArgumentException
1509
+	 * @throws InvalidInterfaceException
1510
+	 * @throws InvalidDataTypeException
1511
+	 * @throws EE_Error
1512
+	 */
1513
+	protected function _set_time_for($time, $fieldname)
1514
+	{
1515
+		$this->_set_date_time('T', $time, $fieldname);
1516
+	}
1517
+
1518
+
1519
+	/**
1520
+	 * sets the date on a datetime property
1521
+	 *
1522
+	 * @access protected
1523
+	 * @param string|DateTime $date      a valid date string for php datetime functions ( or DateTime object)
1524
+	 * @param string          $fieldname the name of the field the date is being set on (must match a EE_Datetime_Field)
1525
+	 * @throws ReflectionException
1526
+	 * @throws InvalidArgumentException
1527
+	 * @throws InvalidInterfaceException
1528
+	 * @throws InvalidDataTypeException
1529
+	 * @throws EE_Error
1530
+	 */
1531
+	protected function _set_date_for($date, $fieldname)
1532
+	{
1533
+		$this->_set_date_time('D', $date, $fieldname);
1534
+	}
1535
+
1536
+
1537
+	/**
1538
+	 * This takes care of setting a date or time independently on a given model object property. This method also
1539
+	 * verifies that the given field_name matches a model object property and is for a EE_Datetime_Field field
1540
+	 *
1541
+	 * @access protected
1542
+	 * @param string          $what           "T" for time, 'B' for both, 'D' for Date.
1543
+	 * @param string|DateTime $datetime_value A valid Date or Time string (or DateTime object)
1544
+	 * @param string          $field_name     the name of the field the date OR time is being set on (must match a
1545
+	 *                                        EE_Datetime_Field property)
1546
+	 * @throws ReflectionException
1547
+	 * @throws InvalidArgumentException
1548
+	 * @throws InvalidInterfaceException
1549
+	 * @throws InvalidDataTypeException
1550
+	 * @throws EE_Error
1551
+	 */
1552
+	protected function _set_date_time(string $what, $datetime_value, string $field_name)
1553
+	{
1554
+		$field = $this->_get_dtt_field_settings($field_name);
1555
+		$field->set_timezone($this->_timezone);
1556
+		$field->set_date_format($this->_dt_frmt);
1557
+		$field->set_time_format($this->_tm_frmt);
1558
+		switch ($what) {
1559
+			case 'T':
1560
+				$this->_fields[ $field_name ] = $field->prepare_for_set_with_new_time(
1561
+					$datetime_value,
1562
+					$this->_fields[ $field_name ]
1563
+				);
1564
+				$this->_has_changes           = true;
1565
+				break;
1566
+			case 'D':
1567
+				$this->_fields[ $field_name ] = $field->prepare_for_set_with_new_date(
1568
+					$datetime_value,
1569
+					$this->_fields[ $field_name ]
1570
+				);
1571
+				$this->_has_changes           = true;
1572
+				break;
1573
+			case 'B':
1574
+				$this->_fields[ $field_name ] = $field->prepare_for_set($datetime_value);
1575
+				$this->_has_changes           = true;
1576
+				break;
1577
+		}
1578
+		$this->_clear_cached_property($field_name);
1579
+	}
1580
+
1581
+
1582
+	/**
1583
+	 * This will return a timestamp for the website timezone but ONLY when the current website timezone is different
1584
+	 * than the timezone set for the website. NOTE, this currently only works well with methods that return values.  If
1585
+	 * you use it with methods that echo values the $_timestamp property may not get reset to its original value and
1586
+	 * that could lead to some unexpected results!
1587
+	 *
1588
+	 * @access public
1589
+	 * @param string $field_name               This is the name of the field on the object that contains the date/time
1590
+	 *                                         value being returned.
1591
+	 * @param string $callback                 must match a valid method in this class (defaults to get_datetime)
1592
+	 * @param mixed (array|string) $args       This is the arguments that will be passed to the callback.
1593
+	 * @param string $prepend                  You can include something to prepend on the timestamp
1594
+	 * @param string $append                   You can include something to append on the timestamp
1595
+	 * @return string timestamp
1596
+	 * @throws ReflectionException
1597
+	 * @throws InvalidArgumentException
1598
+	 * @throws InvalidInterfaceException
1599
+	 * @throws InvalidDataTypeException
1600
+	 * @throws EE_Error
1601
+	 */
1602
+	public function display_in_my_timezone(
1603
+		$field_name,
1604
+		$callback = 'get_datetime',
1605
+		$args = null,
1606
+		$prepend = '',
1607
+		$append = ''
1608
+	) {
1609
+		$timezone = EEH_DTT_Helper::get_timezone();
1610
+		if ($timezone === $this->_timezone) {
1611
+			return '';
1612
+		}
1613
+		$original_timezone = $this->_timezone;
1614
+		$this->set_timezone($timezone);
1615
+		$fn   = (array) $field_name;
1616
+		$args = array_merge($fn, (array) $args);
1617
+		if (! method_exists($this, $callback)) {
1618
+			throw new EE_Error(
1619
+				sprintf(
1620
+					esc_html__(
1621
+						'The method named "%s" given as the callback param in "display_in_my_timezone" does not exist.  Please check your spelling',
1622
+						'event_espresso'
1623
+					),
1624
+					$callback
1625
+				)
1626
+			);
1627
+		}
1628
+		$args   = (array) $args;
1629
+		$return = $prepend . call_user_func_array([$this, $callback], $args) . $append;
1630
+		$this->set_timezone($original_timezone);
1631
+		return $return;
1632
+	}
1633
+
1634
+
1635
+	/**
1636
+	 * Deletes this model object.
1637
+	 * This calls the `EE_Base_Class::_delete` method.  Child classes wishing to change default behaviour should
1638
+	 * override
1639
+	 * `EE_Base_Class::_delete` NOT this class.
1640
+	 *
1641
+	 * @return int
1642
+	 * @throws ReflectionException
1643
+	 * @throws InvalidArgumentException
1644
+	 * @throws InvalidInterfaceException
1645
+	 * @throws InvalidDataTypeException
1646
+	 * @throws EE_Error
1647
+	 */
1648
+	public function delete()
1649
+	{
1650
+		/**
1651
+		 * Called just before the `EE_Base_Class::_delete` method call.
1652
+		 * Note:
1653
+		 * `EE_Base_Class::_delete` might be overridden by child classes so any client code hooking into these actions
1654
+		 * should be aware that `_delete` may not always result in a permanent delete.
1655
+		 * For example, `EE_Soft_Delete_Base_Class::_delete`
1656
+		 * soft deletes (trash) the object and does not permanently delete it.
1657
+		 *
1658
+		 * @param EE_Base_Class $model_object about to be 'deleted'
1659
+		 */
1660
+		do_action('AHEE__EE_Base_Class__delete__before', $this);
1661
+		$deleted = $this->_delete();
1662
+		/**
1663
+		 * Called just after the `EE_Base_Class::_delete` method call.
1664
+		 * Note:
1665
+		 * `EE_Base_Class::_delete` might be overridden by child classes so any client code hooking into these actions
1666
+		 * should be aware that `_delete` may not always result in a permanent delete.
1667
+		 * For example `EE_Soft_Base_Class::_delete`
1668
+		 * soft deletes (trash) the object and does not permanently delete it.
1669
+		 *
1670
+		 * @param EE_Base_Class $model_object that was just 'deleted'
1671
+		 * @param boolean       $deleted
1672
+		 */
1673
+		do_action('AHEE__EE_Base_Class__delete__end', $this, $deleted);
1674
+		return $deleted;
1675
+	}
1676
+
1677
+
1678
+	/**
1679
+	 * Calls the specific delete method for the instantiated class.
1680
+	 * This method is called by the public `EE_Base_Class::delete` method.  Any child classes desiring to override
1681
+	 * default functionality for "delete" (which is to call `permanently_delete`) should override this method NOT
1682
+	 * `EE_Base_Class::delete`
1683
+	 *
1684
+	 * @return int
1685
+	 * @throws ReflectionException
1686
+	 * @throws InvalidArgumentException
1687
+	 * @throws InvalidInterfaceException
1688
+	 * @throws InvalidDataTypeException
1689
+	 * @throws EE_Error
1690
+	 */
1691
+	protected function _delete(): int
1692
+	{
1693
+		return $this->delete_permanently();
1694
+	}
1695
+
1696
+
1697
+	/**
1698
+	 * Deletes this model object permanently from db
1699
+	 * (but keep in mind related models may block the delete and return an error)
1700
+	 *
1701
+	 * @return int
1702
+	 * @throws ReflectionException
1703
+	 * @throws InvalidArgumentException
1704
+	 * @throws InvalidInterfaceException
1705
+	 * @throws InvalidDataTypeException
1706
+	 * @throws EE_Error
1707
+	 */
1708
+	public function delete_permanently(): int
1709
+	{
1710
+		/**
1711
+		 * Called just before HARD deleting a model object
1712
+		 *
1713
+		 * @param EE_Base_Class $model_object about to be 'deleted'
1714
+		 */
1715
+		do_action('AHEE__EE_Base_Class__delete_permanently__before', $this);
1716
+		$model  = $this->get_model();
1717
+		$result = $model->delete_permanently_by_ID($this->ID());
1718
+		$this->refresh_cache_of_related_objects();
1719
+		/**
1720
+		 * Called just after HARD deleting a model object
1721
+		 *
1722
+		 * @param EE_Base_Class $model_object that was just 'deleted'
1723
+		 * @param boolean       $result
1724
+		 */
1725
+		do_action('AHEE__EE_Base_Class__delete_permanently__end', $this, $result);
1726
+		return $result;
1727
+	}
1728
+
1729
+
1730
+	/**
1731
+	 * When this model object is deleted, it may still be cached on related model objects. This clears the cache of
1732
+	 * related model objects
1733
+	 *
1734
+	 * @throws ReflectionException
1735
+	 * @throws InvalidArgumentException
1736
+	 * @throws InvalidInterfaceException
1737
+	 * @throws InvalidDataTypeException
1738
+	 * @throws EE_Error
1739
+	 */
1740
+	public function refresh_cache_of_related_objects()
1741
+	{
1742
+		$model = $this->get_model();
1743
+		foreach ($model->relation_settings() as $relation_name => $relation_obj) {
1744
+			if (! empty($this->_model_relations[ $relation_name ])) {
1745
+				$related_objects = $this->_model_relations[ $relation_name ];
1746
+				if ($relation_obj instanceof EE_Belongs_To_Relation) {
1747
+					// this relation only stores a single model object, not an array
1748
+					// but let's make it consistent
1749
+					$related_objects = [$related_objects];
1750
+				}
1751
+				foreach ($related_objects as $related_object) {
1752
+					// only refresh their cache if they're in memory
1753
+					if ($related_object instanceof EE_Base_Class) {
1754
+						$related_object->clear_cache(
1755
+							$model->get_this_model_name(),
1756
+							$this
1757
+						);
1758
+					}
1759
+				}
1760
+			}
1761
+		}
1762
+	}
1763
+
1764
+
1765
+	/**
1766
+	 *        Saves this object to the database. An array may be supplied to set some values on this
1767
+	 * object just before saving.
1768
+	 *
1769
+	 * @access public
1770
+	 * @param array $set_cols_n_values keys are field names, values are their new values,
1771
+	 *                                 if provided during the save() method (often client code will change the fields'
1772
+	 *                                 values before calling save)
1773
+	 * @return bool|int|string         1 on a successful update
1774
+	 *                                 the ID of the new entry on insert
1775
+	 *                                 0 on failure or if the model object isn't allowed to persist
1776
+	 *                                 (as determined by EE_Base_Class::allow_persist())
1777
+	 * @throws InvalidInterfaceException
1778
+	 * @throws InvalidDataTypeException
1779
+	 * @throws EE_Error
1780
+	 * @throws InvalidArgumentException
1781
+	 * @throws ReflectionException
1782
+	 */
1783
+	public function save($set_cols_n_values = [])
1784
+	{
1785
+		$model = $this->get_model();
1786
+		/**
1787
+		 * Filters the fields we're about to save on the model object
1788
+		 *
1789
+		 * @param array         $set_cols_n_values
1790
+		 * @param EE_Base_Class $model_object
1791
+		 */
1792
+		$set_cols_n_values = (array) apply_filters(
1793
+			'FHEE__EE_Base_Class__save__set_cols_n_values',
1794
+			$set_cols_n_values,
1795
+			$this
1796
+		);
1797
+		// set attributes as provided in $set_cols_n_values
1798
+		foreach ($set_cols_n_values as $column => $value) {
1799
+			$this->set($column, $value);
1800
+		}
1801
+		// no changes ? then don't do anything
1802
+		if (! $this->_has_changes && $this->ID() && $model->get_primary_key_field()->is_auto_increment()) {
1803
+			return 0;
1804
+		}
1805
+		/**
1806
+		 * Saving a model object.
1807
+		 * Before we perform a save, this action is fired.
1808
+		 *
1809
+		 * @param EE_Base_Class $model_object the model object about to be saved.
1810
+		 */
1811
+		do_action('AHEE__EE_Base_Class__save__begin', $this);
1812
+		if (! $this->allow_persist()) {
1813
+			return 0;
1814
+		}
1815
+		// now get current attribute values
1816
+		$save_cols_n_values = $this->_fields;
1817
+		// if the object already has an ID, update it. Otherwise, insert it
1818
+		// also: change the assumption about values passed to the model NOT being prepare dby the model object.
1819
+		// They have been
1820
+		$old_assumption_concerning_value_preparation = $model
1821
+			->get_assumption_concerning_values_already_prepared_by_model_object();
1822
+		$model->assume_values_already_prepared_by_model_object(true);
1823
+		// does this model have an autoincrement PK?
1824
+		if ($model->has_primary_key_field()) {
1825
+			if ($model->get_primary_key_field()->is_auto_increment()) {
1826
+				// ok check if it's set, if so: update; if not, insert
1827
+				if (! empty($save_cols_n_values[ $model->primary_key_name() ])) {
1828
+					$results = $model->update_by_ID($save_cols_n_values, $this->ID());
1829
+				} else {
1830
+					unset($save_cols_n_values[ $model->primary_key_name() ]);
1831
+					$results = $model->insert($save_cols_n_values);
1832
+					if ($results) {
1833
+						// if successful, set the primary key
1834
+						// but don't use the normal SET method, because it will check if
1835
+						// an item with the same ID exists in the mapper & db, then
1836
+						// will find it in the db (because we just added it) and THAT object
1837
+						// will get added to the mapper before we can add this one!
1838
+						// but if we just avoid using the SET method, all that headache can be avoided
1839
+						$pk_field_name                   = $model->primary_key_name();
1840
+						$this->_fields[ $pk_field_name ] = $results;
1841
+						$this->_clear_cached_property($pk_field_name);
1842
+						$model->add_to_entity_map($this);
1843
+						$this->_update_cached_related_model_objs_fks();
1844
+					}
1845
+				}
1846
+			} else {// PK is NOT auto-increment
1847
+				// so check if one like it already exists in the db
1848
+				if ($model->exists_by_ID($this->ID())) {
1849
+					if (WP_DEBUG && ! $this->in_entity_map()) {
1850
+						throw new EE_Error(
1851
+							sprintf(
1852
+								esc_html__(
1853
+									'Using a model object %1$s that is NOT in the entity map, can lead to unexpected errors. You should either: %4$s 1. Put it in the entity mapper by calling %2$s %4$s 2. Discard this model object and use what is in the entity mapper %4$s 3. Fetch from the database using %3$s',
1854
+									'event_espresso'
1855
+								),
1856
+								get_class($this),
1857
+								get_class($model) . '::instance()->add_to_entity_map()',
1858
+								get_class($model) . '::instance()->get_one_by_ID()',
1859
+								'<br />'
1860
+							)
1861
+						);
1862
+					}
1863
+					$results = $model->update_by_ID($save_cols_n_values, $this->ID());
1864
+				} else {
1865
+					$results = $model->insert($save_cols_n_values);
1866
+					$this->_update_cached_related_model_objs_fks();
1867
+				}
1868
+			}
1869
+		} else {// there is NO primary key
1870
+			$already_in_db = false;
1871
+			foreach ($model->unique_indexes() as $index) {
1872
+				$uniqueness_where_params = array_intersect_key($save_cols_n_values, $index->fields());
1873
+				if ($model->exists([$uniqueness_where_params])) {
1874
+					$already_in_db = true;
1875
+				}
1876
+			}
1877
+			if ($already_in_db) {
1878
+				$combined_pk_fields_n_values = array_intersect_key(
1879
+					$save_cols_n_values,
1880
+					$model->get_combined_primary_key_fields()
1881
+				);
1882
+				$results                     = $model->update(
1883
+					$save_cols_n_values,
1884
+					$combined_pk_fields_n_values
1885
+				);
1886
+			} else {
1887
+				$results = $model->insert($save_cols_n_values);
1888
+			}
1889
+		}
1890
+		// restore the old assumption about values being prepared by the model object
1891
+		$model->assume_values_already_prepared_by_model_object(
1892
+			$old_assumption_concerning_value_preparation
1893
+		);
1894
+		/**
1895
+		 * After saving the model object this action is called
1896
+		 *
1897
+		 * @param EE_Base_Class $model_object which was just saved
1898
+		 * @param boolean|int   $results      if it were updated, TRUE or FALSE; if it were newly inserted
1899
+		 *                                    the new ID (or 0 if an error occurred and it wasn't updated)
1900
+		 */
1901
+		do_action('AHEE__EE_Base_Class__save__end', $this, $results);
1902
+		$this->_has_changes = false;
1903
+		return $results;
1904
+	}
1905
+
1906
+
1907
+	/**
1908
+	 * Updates the foreign key on related models objects pointing to this to have this model object's ID
1909
+	 * as their foreign key.  If the cached related model objects already exist in the db, saves them (so that the DB
1910
+	 * is consistent) Especially useful in case we JUST added this model object ot the database and we want to let its
1911
+	 * cached relations with foreign keys to it know about that change. Eg: we've created a transaction but haven't
1912
+	 * saved it to the db. We also create a registration and don't save it to the DB, but we DO cache it on the
1913
+	 * transaction. Now, when we save the transaction, the registration's TXN_ID will be automatically updated, whether
1914
+	 * or not they exist in the DB (if they do, their DB records will be automatically updated)
1915
+	 *
1916
+	 * @return void
1917
+	 * @throws ReflectionException
1918
+	 * @throws InvalidArgumentException
1919
+	 * @throws InvalidInterfaceException
1920
+	 * @throws InvalidDataTypeException
1921
+	 * @throws EE_Error
1922
+	 */
1923
+	protected function _update_cached_related_model_objs_fks()
1924
+	{
1925
+		$model = $this->get_model();
1926
+		foreach ($model->relation_settings() as $relation_name => $relation_obj) {
1927
+			if ($relation_obj instanceof EE_Has_Many_Relation) {
1928
+				foreach ($this->get_all_from_cache($relation_name) as $related_model_obj_in_cache) {
1929
+					$fk_to_this = $related_model_obj_in_cache->get_model()->get_foreign_key_to(
1930
+						$model->get_this_model_name()
1931
+					);
1932
+					$related_model_obj_in_cache->set($fk_to_this->get_name(), $this->ID());
1933
+					if ($related_model_obj_in_cache->ID()) {
1934
+						$related_model_obj_in_cache->save();
1935
+					}
1936
+				}
1937
+			}
1938
+		}
1939
+	}
1940
+
1941
+
1942
+	/**
1943
+	 * Saves this model object and its NEW cached relations to the database.
1944
+	 * (Meaning, for now, IT DOES NOT WORK if the cached items already exist in the DB.
1945
+	 * In order for that to work, we would need to mark model objects as dirty/clean...
1946
+	 * because otherwise, there's a potential for infinite looping of saving
1947
+	 * Saves the cached related model objects, and ensures the relation between them
1948
+	 * and this object and properly setup
1949
+	 *
1950
+	 * @return int ID of new model object on save; 0 on failure+
1951
+	 * @throws ReflectionException
1952
+	 * @throws InvalidArgumentException
1953
+	 * @throws InvalidInterfaceException
1954
+	 * @throws InvalidDataTypeException
1955
+	 * @throws EE_Error
1956
+	 */
1957
+	public function save_new_cached_related_model_objs()
1958
+	{
1959
+		// make sure this has been saved
1960
+		if (! $this->ID()) {
1961
+			$id = $this->save();
1962
+		} else {
1963
+			$id = $this->ID();
1964
+		}
1965
+		// now save all the NEW cached model objects  (ie they don't exist in the DB)
1966
+		foreach ($this->get_model()->relation_settings() as $relation_name => $relationObj) {
1967
+			if ($this->_model_relations[ $relation_name ]) {
1968
+				// is this a relation where we should expect just ONE related object (ie, EE_Belongs_To_relation)
1969
+				// or MANY related objects (ie, EE_HABTM_Relation or EE_Has_Many_Relation)?
1970
+				/* @var $related_model_obj EE_Base_Class */
1971
+				if ($relationObj instanceof EE_Belongs_To_Relation) {
1972
+					// add a relation to that relation type (which saves the appropriate thing in the process)
1973
+					// but ONLY if it DOES NOT exist in the DB
1974
+					$related_model_obj = $this->_model_relations[ $relation_name ];
1975
+					// if( ! $related_model_obj->ID()){
1976
+					$this->_add_relation_to($related_model_obj, $relation_name);
1977
+					$related_model_obj->save_new_cached_related_model_objs();
1978
+					// }
1979
+				} else {
1980
+					foreach ($this->_model_relations[ $relation_name ] as $related_model_obj) {
1981
+						// add a relation to that relation type (which saves the appropriate thing in the process)
1982
+						// but ONLY if it DOES NOT exist in the DB
1983
+						// if( ! $related_model_obj->ID()){
1984
+						$this->_add_relation_to($related_model_obj, $relation_name);
1985
+						$related_model_obj->save_new_cached_related_model_objs();
1986
+						// }
1987
+					}
1988
+				}
1989
+			}
1990
+		}
1991
+		return $id;
1992
+	}
1993
+
1994
+
1995
+	/**
1996
+	 * for getting a model while instantiated.
1997
+	 *
1998
+	 * @return EEM_Base | EEM_CPT_Base
1999
+	 * @throws ReflectionException
2000
+	 * @throws InvalidArgumentException
2001
+	 * @throws InvalidInterfaceException
2002
+	 * @throws InvalidDataTypeException
2003
+	 * @throws EE_Error
2004
+	 */
2005
+	public function get_model()
2006
+	{
2007
+		if (! $this->_model) {
2008
+			$modelName    = self::_get_model_classname(get_class($this));
2009
+			$this->_model = self::_get_model_instance_with_name($modelName, $this->_timezone);
2010
+		} else {
2011
+			$this->_model->set_timezone($this->_timezone);
2012
+		}
2013
+		return $this->_model;
2014
+	}
2015
+
2016
+
2017
+	/**
2018
+	 * @param $props_n_values
2019
+	 * @param $classname
2020
+	 * @return mixed bool|EE_Base_Class|EEM_CPT_Base
2021
+	 * @throws ReflectionException
2022
+	 * @throws InvalidArgumentException
2023
+	 * @throws InvalidInterfaceException
2024
+	 * @throws InvalidDataTypeException
2025
+	 * @throws EE_Error
2026
+	 */
2027
+	protected static function _get_object_from_entity_mapper($props_n_values, $classname)
2028
+	{
2029
+		// TODO: will not work for Term_Relationships because they have no PK!
2030
+		$primary_id_ref = self::_get_primary_key_name($classname);
2031
+		if (
2032
+			array_key_exists($primary_id_ref, $props_n_values)
2033
+			&& ! empty($props_n_values[ $primary_id_ref ])
2034
+		) {
2035
+			$id = $props_n_values[ $primary_id_ref ];
2036
+			return self::_get_model($classname)->get_from_entity_map($id);
2037
+		}
2038
+		return false;
2039
+	}
2040
+
2041
+
2042
+	/**
2043
+	 * This is called by child static "new_instance" method and we'll check to see if there is an existing db entry for
2044
+	 * the primary key (if present in incoming values). If there is a key in the incoming array that matches the
2045
+	 * primary key for the model AND it is not null, then we check the db. If there's a an object we return it.  If not
2046
+	 * we return false.
2047
+	 *
2048
+	 * @param array  $props_n_values    incoming array of properties and their values
2049
+	 * @param string $classname         the classname of the child class
2050
+	 * @param null   $timezone
2051
+	 * @param array  $date_formats      incoming date_formats in an array where the first value is the
2052
+	 *                                  date_format and the second value is the time format
2053
+	 * @return mixed (EE_Base_Class|bool)
2054
+	 * @throws InvalidArgumentException
2055
+	 * @throws InvalidInterfaceException
2056
+	 * @throws InvalidDataTypeException
2057
+	 * @throws EE_Error
2058
+	 * @throws ReflectionException
2059
+	 */
2060
+	protected static function _check_for_object($props_n_values, $classname, $timezone = '', $date_formats = [])
2061
+	{
2062
+		$existing = null;
2063
+		$model    = self::_get_model($classname, $timezone);
2064
+		if ($model->has_primary_key_field()) {
2065
+			$primary_id_ref = self::_get_primary_key_name($classname);
2066
+			if (
2067
+				array_key_exists($primary_id_ref, $props_n_values)
2068
+				&& ! empty($props_n_values[ $primary_id_ref ])
2069
+			) {
2070
+				$existing = $model->get_one_by_ID(
2071
+					$props_n_values[ $primary_id_ref ]
2072
+				);
2073
+			}
2074
+		} elseif ($model->has_all_combined_primary_key_fields($props_n_values)) {
2075
+			// no primary key on this model, but there's still a matching item in the DB
2076
+			$existing = self::_get_model($classname, $timezone)->get_one_by_ID(
2077
+				self::_get_model($classname, $timezone)
2078
+					->get_index_primary_key_string($props_n_values)
2079
+			);
2080
+		}
2081
+		if ($existing) {
2082
+			// set date formats if present before setting values
2083
+			if (! empty($date_formats) && is_array($date_formats)) {
2084
+				$existing->set_date_format($date_formats[0]);
2085
+				$existing->set_time_format($date_formats[1]);
2086
+			} else {
2087
+				// set default formats for date and time
2088
+				$existing->set_date_format(get_option('date_format'));
2089
+				$existing->set_time_format(get_option('time_format'));
2090
+			}
2091
+			foreach ($props_n_values as $property => $field_value) {
2092
+				$existing->set($property, $field_value);
2093
+			}
2094
+			return $existing;
2095
+		}
2096
+		return false;
2097
+	}
2098
+
2099
+
2100
+	/**
2101
+	 * Gets the EEM_*_Model for this class
2102
+	 *
2103
+	 * @access public now, as this is more convenient
2104
+	 * @param      $classname
2105
+	 * @param null $timezone
2106
+	 * @return EEM_Base
2107
+	 * @throws InvalidArgumentException
2108
+	 * @throws InvalidInterfaceException
2109
+	 * @throws InvalidDataTypeException
2110
+	 * @throws EE_Error
2111
+	 * @throws ReflectionException
2112
+	 */
2113
+	protected static function _get_model($classname, $timezone = '')
2114
+	{
2115
+		// find model for this class
2116
+		if (! $classname) {
2117
+			throw new EE_Error(
2118
+				sprintf(
2119
+					esc_html__(
2120
+						'What were you thinking calling _get_model(%s)?? You need to specify the class name',
2121
+						'event_espresso'
2122
+					),
2123
+					$classname
2124
+				)
2125
+			);
2126
+		}
2127
+		$modelName = self::_get_model_classname($classname);
2128
+		return self::_get_model_instance_with_name($modelName, $timezone);
2129
+	}
2130
+
2131
+
2132
+	/**
2133
+	 * Gets the model instance (eg instance of EEM_Attendee) given its classname (eg EE_Attendee)
2134
+	 *
2135
+	 * @param string $model_classname
2136
+	 * @param null   $timezone
2137
+	 * @return EEM_Base
2138
+	 * @throws ReflectionException
2139
+	 * @throws InvalidArgumentException
2140
+	 * @throws InvalidInterfaceException
2141
+	 * @throws InvalidDataTypeException
2142
+	 * @throws EE_Error
2143
+	 */
2144
+	protected static function _get_model_instance_with_name($model_classname, $timezone = '')
2145
+	{
2146
+		$model_classname = str_replace('EEM_', '', $model_classname);
2147
+		$model           = EE_Registry::instance()->load_model($model_classname);
2148
+		$model->set_timezone($timezone);
2149
+		return $model;
2150
+	}
2151
+
2152
+
2153
+	/**
2154
+	 * If a model name is provided (eg Registration), gets the model classname for that model.
2155
+	 * Also works if a model class's classname is provided (eg EE_Registration).
2156
+	 *
2157
+	 * @param string|null $model_name
2158
+	 * @return string like EEM_Attendee
2159
+	 */
2160
+	private static function _get_model_classname($model_name = '')
2161
+	{
2162
+		return strpos((string) $model_name, 'EE_') === 0
2163
+			? str_replace('EE_', 'EEM_', $model_name)
2164
+			: 'EEM_' . $model_name;
2165
+	}
2166
+
2167
+
2168
+	/**
2169
+	 * returns the name of the primary key attribute
2170
+	 *
2171
+	 * @param null $classname
2172
+	 * @return string
2173
+	 * @throws InvalidArgumentException
2174
+	 * @throws InvalidInterfaceException
2175
+	 * @throws InvalidDataTypeException
2176
+	 * @throws EE_Error
2177
+	 * @throws ReflectionException
2178
+	 */
2179
+	protected static function _get_primary_key_name($classname = null)
2180
+	{
2181
+		if (! $classname) {
2182
+			throw new EE_Error(
2183
+				sprintf(
2184
+					esc_html__('What were you thinking calling _get_primary_key_name(%s)', 'event_espresso'),
2185
+					$classname
2186
+				)
2187
+			);
2188
+		}
2189
+		return self::_get_model($classname)->get_primary_key_field()->get_name();
2190
+	}
2191
+
2192
+
2193
+	/**
2194
+	 * Gets the value of the primary key.
2195
+	 * If the object hasn't yet been saved, it should be whatever the model field's default was
2196
+	 * (eg, if this were the EE_Event class, look at the primary key field on EEM_Event and see what its default value
2197
+	 * is. Usually defaults for integer primary keys are 0; string primary keys are usually NULL).
2198
+	 *
2199
+	 * @return mixed, if the primary key is of type INT it'll be an int. Otherwise it could be a string
2200
+	 * @throws ReflectionException
2201
+	 * @throws InvalidArgumentException
2202
+	 * @throws InvalidInterfaceException
2203
+	 * @throws InvalidDataTypeException
2204
+	 * @throws EE_Error
2205
+	 */
2206
+	public function ID()
2207
+	{
2208
+		$model = $this->get_model();
2209
+		// now that we know the name of the variable, use a variable variable to get its value and return its
2210
+		if ($model->has_primary_key_field()) {
2211
+			return $this->_fields[ $model->primary_key_name() ];
2212
+		}
2213
+		return $model->get_index_primary_key_string($this->_fields);
2214
+	}
2215
+
2216
+
2217
+	/**
2218
+	 * @param EE_Base_Class|int|string $otherModelObjectOrID
2219
+	 * @param string                   $relation_name
2220
+	 * @return bool
2221
+	 * @throws EE_Error
2222
+	 * @throws ReflectionException
2223
+	 * @since   5.0.0.p
2224
+	 */
2225
+	public function hasRelation($otherModelObjectOrID, string $relation_name): bool
2226
+	{
2227
+		$other_model = self::_get_model_instance_with_name(
2228
+			self::_get_model_classname($relation_name),
2229
+			$this->_timezone
2230
+		);
2231
+		$primary_key = $other_model->primary_key_name();
2232
+		/** @var EE_Base_Class $otherModelObject */
2233
+		$otherModelObject = $other_model->ensure_is_obj($otherModelObjectOrID, $relation_name);
2234
+		return $this->count_related($relation_name, [[$primary_key => $otherModelObject->ID()]]) > 0;
2235
+	}
2236
+
2237
+
2238
+	/**
2239
+	 * Adds a relationship to the specified EE_Base_Class object, given the relationship's name. Eg, if the current
2240
+	 * model is related to a group of events, the $relation_name should be 'Event', and should be a key in the EE
2241
+	 * Model's $_model_relations array. If this model object doesn't exist in the DB, just caches the related thing
2242
+	 *
2243
+	 * @param mixed  $otherObjectModelObjectOrID       EE_Base_Class or the ID of the other object
2244
+	 * @param string $relation_name                    eg 'Events','Question',etc.
2245
+	 *                                                 an attendee to a group, you also want to specify which role they
2246
+	 *                                                 will have in that group. So you would use this parameter to
2247
+	 *                                                 specify array('role-column-name'=>'role-id')
2248
+	 * @param array  $extra_join_model_fields_n_values You can optionally include an array of key=>value pairs that
2249
+	 *                                                 allow you to further constrict the relation to being added.
2250
+	 *                                                 However, keep in mind that the columns (keys) given must match a
2251
+	 *                                                 column on the JOIN table and currently only the HABTM models
2252
+	 *                                                 accept these additional conditions.  Also remember that if an
2253
+	 *                                                 exact match isn't found for these extra cols/val pairs, then a
2254
+	 *                                                 NEW row is created in the join table.
2255
+	 * @param null   $cache_id
2256
+	 * @return EE_Base_Class the object the relation was added to
2257
+	 * @throws ReflectionException
2258
+	 * @throws InvalidArgumentException
2259
+	 * @throws InvalidInterfaceException
2260
+	 * @throws InvalidDataTypeException
2261
+	 * @throws EE_Error
2262
+	 */
2263
+	public function _add_relation_to(
2264
+		$otherObjectModelObjectOrID,
2265
+		$relation_name,
2266
+		$extra_join_model_fields_n_values = [],
2267
+		$cache_id = null
2268
+	) {
2269
+		$model = $this->get_model();
2270
+		// if this thing exists in the DB, save the relation to the DB
2271
+		if ($this->ID()) {
2272
+			$otherObject = $model->add_relationship_to(
2273
+				$this,
2274
+				$otherObjectModelObjectOrID,
2275
+				$relation_name,
2276
+				$extra_join_model_fields_n_values
2277
+			);
2278
+			// clear cache so future get_many_related and get_first_related() return new results.
2279
+			$this->clear_cache($relation_name, $otherObject, true);
2280
+			if ($otherObject instanceof EE_Base_Class) {
2281
+				$otherObject->clear_cache($model->get_this_model_name(), $this);
2282
+			}
2283
+		} else {
2284
+			// this thing doesn't exist in the DB,  so just cache it
2285
+			if (! $otherObjectModelObjectOrID instanceof EE_Base_Class) {
2286
+				throw new EE_Error(
2287
+					sprintf(
2288
+						esc_html__(
2289
+							'Before a model object is saved to the database, calls to _add_relation_to must be passed an actual object, not just an ID. You provided %s as the model object to a %s',
2290
+							'event_espresso'
2291
+						),
2292
+						$otherObjectModelObjectOrID,
2293
+						get_class($this)
2294
+					)
2295
+				);
2296
+			}
2297
+			$otherObject = $otherObjectModelObjectOrID;
2298
+			$this->cache($relation_name, $otherObjectModelObjectOrID, $cache_id);
2299
+		}
2300
+		if ($otherObject instanceof EE_Base_Class) {
2301
+			// fix the reciprocal relation too
2302
+			if ($otherObject->ID()) {
2303
+				// its saved so assumed relations exist in the DB, so we can just
2304
+				// clear the cache so future queries use the updated info in the DB
2305
+				$otherObject->clear_cache(
2306
+					$model->get_this_model_name(),
2307
+					null,
2308
+					true
2309
+				);
2310
+			} else {
2311
+				// it's not saved, so it caches relations like this
2312
+				$otherObject->cache($model->get_this_model_name(), $this);
2313
+			}
2314
+		}
2315
+		return $otherObject;
2316
+	}
2317
+
2318
+
2319
+	/**
2320
+	 * Removes a relationship to the specified EE_Base_Class object, given the relationships' name. Eg, if the current
2321
+	 * model is related to a group of events, the $relation_name should be 'Events', and should be a key in the EE
2322
+	 * Model's $_model_relations array. If this model object doesn't exist in the DB, just removes the related thing
2323
+	 * from the cache
2324
+	 *
2325
+	 * @param mixed  $otherObjectModelObjectOrID
2326
+	 *                EE_Base_Class or the ID of the other object, OR an array key into the cache if this isn't saved
2327
+	 *                to the DB yet
2328
+	 * @param string $relation_name
2329
+	 * @param array  $where_query
2330
+	 *                You can optionally include an array of key=>value pairs that allow you to further constrict the
2331
+	 *                relation to being added. However, keep in mind that the columns (keys) given must match a column
2332
+	 *                on the JOIN table and currently only the HABTM models accept these additional conditions. Also
2333
+	 *                remember that if an exact match isn't found for these extra cols/val pairs, then no row is
2334
+	 *                deleted.
2335
+	 * @return EE_Base_Class|bool   the related entity that was removed
2336
+	 *                              or true if multiple entities removed
2337
+	 *                              or false if nothing was cached
2338
+	 * @throws ReflectionException
2339
+	 * @throws InvalidArgumentException
2340
+	 * @throws InvalidInterfaceException
2341
+	 * @throws InvalidDataTypeException
2342
+	 * @throws EE_Error
2343
+	 */
2344
+	public function _remove_relation_to($otherObjectModelObjectOrID, $relation_name, $where_query = [])
2345
+	{
2346
+		if ($this->ID()) {
2347
+			// if this exists in the DB, save the relation change to the DB too
2348
+			$otherObject = $this->get_model()->remove_relationship_to(
2349
+				$this,
2350
+				$otherObjectModelObjectOrID,
2351
+				$relation_name,
2352
+				$where_query
2353
+			);
2354
+			$this->clear_cache(
2355
+				$relation_name,
2356
+				$otherObject
2357
+			);
2358
+		} else {
2359
+			// this doesn't exist in the DB, just remove it from the cache
2360
+			$otherObject = $this->clear_cache(
2361
+				$relation_name,
2362
+				$otherObjectModelObjectOrID
2363
+			);
2364
+		}
2365
+		if ($otherObject instanceof EE_Base_Class) {
2366
+			$otherObject->clear_cache(
2367
+				$this->get_model()->get_this_model_name(),
2368
+				$this
2369
+			);
2370
+		}
2371
+		return $otherObject;
2372
+	}
2373
+
2374
+
2375
+	/**
2376
+	 * Removes ALL the related things for the $relation_name.
2377
+	 *
2378
+	 * @param string $relation_name
2379
+	 * @param array  $where_query_params @see
2380
+	 *                                   https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
2381
+	 * @return EE_Base_Class
2382
+	 * @throws ReflectionException
2383
+	 * @throws InvalidArgumentException
2384
+	 * @throws InvalidInterfaceException
2385
+	 * @throws InvalidDataTypeException
2386
+	 * @throws EE_Error
2387
+	 */
2388
+	public function _remove_relations($relation_name, $where_query_params = [])
2389
+	{
2390
+		if ($this->ID()) {
2391
+			// if this exists in the DB, save the relation change to the DB too
2392
+			$otherObjects = $this->get_model()->remove_relations(
2393
+				$this,
2394
+				$relation_name,
2395
+				$where_query_params
2396
+			);
2397
+			$this->clear_cache(
2398
+				$relation_name,
2399
+				null,
2400
+				true
2401
+			);
2402
+		} else {
2403
+			// this doesn't exist in the DB, just remove it from the cache
2404
+			$otherObjects = $this->clear_cache(
2405
+				$relation_name,
2406
+				null,
2407
+				true
2408
+			);
2409
+		}
2410
+		if (is_array($otherObjects)) {
2411
+			foreach ($otherObjects as $otherObject) {
2412
+				$otherObject->clear_cache(
2413
+					$this->get_model()->get_this_model_name(),
2414
+					$this
2415
+				);
2416
+			}
2417
+		}
2418
+		return $otherObjects;
2419
+	}
2420
+
2421
+
2422
+	/**
2423
+	 * Gets all the related model objects of the specified type. Eg, if the current class if
2424
+	 * EE_Event, you could call $this->get_many_related('Registration') to get an array of all the
2425
+	 * EE_Registration objects which related to this event. Note: by default, we remove the "default query params"
2426
+	 * because we want to get even deleted items etc.
2427
+	 *
2428
+	 * @param string      $relation_name key in the model's _model_relations array
2429
+	 * @param array|null  $query_params  @see
2430
+	 *                              https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
2431
+	 * @return EE_Base_Class[]     Results not necessarily indexed by IDs, because some results might not have primary
2432
+	 *                              keys or might not be saved yet. Consider using EEM_Base::get_IDs() on these
2433
+	 *                              results if you want IDs
2434
+	 * @throws ReflectionException
2435
+	 * @throws InvalidArgumentException
2436
+	 * @throws InvalidInterfaceException
2437
+	 * @throws InvalidDataTypeException
2438
+	 * @throws EE_Error
2439
+	 */
2440
+	public function get_many_related($relation_name, $query_params = [])
2441
+	{
2442
+		if ($this->ID()) {
2443
+			// this exists in the DB, so get the related things from either the cache or the DB
2444
+			// if there are query parameters, forget about caching the related model objects.
2445
+			if ($query_params) {
2446
+				$related_model_objects = $this->get_model()->get_all_related(
2447
+					$this,
2448
+					$relation_name,
2449
+					$query_params
2450
+				);
2451
+			} else {
2452
+				// did we already cache the result of this query?
2453
+				$cached_results = $this->get_all_from_cache($relation_name);
2454
+				if (! $cached_results) {
2455
+					$related_model_objects = $this->get_model()->get_all_related(
2456
+						$this,
2457
+						$relation_name,
2458
+						$query_params
2459
+					);
2460
+					// if no query parameters were passed, then we got all the related model objects
2461
+					// for that relation. We can cache them then.
2462
+					foreach ($related_model_objects as $related_model_object) {
2463
+						$this->cache($relation_name, $related_model_object);
2464
+					}
2465
+				} else {
2466
+					$related_model_objects = $cached_results;
2467
+				}
2468
+			}
2469
+		} else {
2470
+			// this doesn't exist in the DB, so just get the related things from the cache
2471
+			$related_model_objects = $this->get_all_from_cache($relation_name);
2472
+		}
2473
+		return $related_model_objects;
2474
+	}
2475
+
2476
+
2477
+	/**
2478
+	 * Instead of getting the related model objects, simply counts them. Ignores default_where_conditions by default,
2479
+	 * unless otherwise specified in the $query_params
2480
+	 *
2481
+	 * @param string $relation_name  model_name like 'Event', or 'Registration'
2482
+	 * @param array  $query_params   @see
2483
+	 *                               https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2484
+	 * @param string $field_to_count name of field to count by. By default, uses primary key
2485
+	 * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2486
+	 *                               that by the setting $distinct to TRUE;
2487
+	 * @return int
2488
+	 * @throws ReflectionException
2489
+	 * @throws InvalidArgumentException
2490
+	 * @throws InvalidInterfaceException
2491
+	 * @throws InvalidDataTypeException
2492
+	 * @throws EE_Error
2493
+	 */
2494
+	public function count_related($relation_name, $query_params = [], $field_to_count = null, $distinct = false)
2495
+	{
2496
+		return $this->get_model()->count_related(
2497
+			$this,
2498
+			$relation_name,
2499
+			$query_params,
2500
+			$field_to_count,
2501
+			$distinct
2502
+		);
2503
+	}
2504
+
2505
+
2506
+	/**
2507
+	 * Instead of getting the related model objects, simply sums up the values of the specified field.
2508
+	 * Note: ignores default_where_conditions by default, unless otherwise specified in the $query_params
2509
+	 *
2510
+	 * @param string $relation_name model_name like 'Event', or 'Registration'
2511
+	 * @param array  $query_params  @see
2512
+	 *                              https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2513
+	 * @param string $field_to_sum  name of field to count by.
2514
+	 *                              By default, uses primary key
2515
+	 *                              (which doesn't make much sense, so you should probably change it)
2516
+	 * @return int
2517
+	 * @throws ReflectionException
2518
+	 * @throws InvalidArgumentException
2519
+	 * @throws InvalidInterfaceException
2520
+	 * @throws InvalidDataTypeException
2521
+	 * @throws EE_Error
2522
+	 */
2523
+	public function sum_related($relation_name, $query_params = [], $field_to_sum = null)
2524
+	{
2525
+		return $this->get_model()->sum_related(
2526
+			$this,
2527
+			$relation_name,
2528
+			$query_params,
2529
+			$field_to_sum
2530
+		);
2531
+	}
2532
+
2533
+
2534
+	/**
2535
+	 * Gets the first (ie, one) related model object of the specified type.
2536
+	 *
2537
+	 * @param string $relation_name key in the model's _model_relations array
2538
+	 * @param array  $query_params  @see
2539
+	 *                              https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2540
+	 * @return EE_Base_Class|null (not an array, a single object)
2541
+	 * @throws ReflectionException
2542
+	 * @throws InvalidArgumentException
2543
+	 * @throws InvalidInterfaceException
2544
+	 * @throws InvalidDataTypeException
2545
+	 * @throws EE_Error
2546
+	 */
2547
+	public function get_first_related(string $relation_name, array $query_params = []): ?EE_Base_Class
2548
+	{
2549
+		$model = $this->get_model();
2550
+		if ($this->ID()) {// this exists in the DB, get from the cache OR the DB
2551
+			// if they've provided some query parameters, don't bother trying to cache the result
2552
+			// also make sure we're not caching the result of get_first_related
2553
+			// on a relation which should have an array of objects (because the cache might have an array of objects)
2554
+			if (
2555
+				$query_params
2556
+				|| ! $model->related_settings_for($relation_name) instanceof EE_Belongs_To_Relation
2557
+			) {
2558
+				$related_model_object = $model->get_first_related(
2559
+					$this,
2560
+					$relation_name,
2561
+					$query_params
2562
+				);
2563
+			} else {
2564
+				// first, check if we've already cached the result of this query
2565
+				$cached_result = $this->get_one_from_cache($relation_name);
2566
+				if (! $cached_result) {
2567
+					$related_model_object = $model->get_first_related(
2568
+						$this,
2569
+						$relation_name,
2570
+						$query_params
2571
+					);
2572
+					$this->cache($relation_name, $related_model_object);
2573
+				} else {
2574
+					$related_model_object = $cached_result;
2575
+				}
2576
+			}
2577
+		} else {
2578
+			$related_model_object = null;
2579
+			// this doesn't exist in the Db,
2580
+			// but maybe the relation is of type belongs to, and so the related thing might
2581
+			if ($model->related_settings_for($relation_name) instanceof EE_Belongs_To_Relation) {
2582
+				$related_model_object = $model->get_first_related(
2583
+					$this,
2584
+					$relation_name,
2585
+					$query_params
2586
+				);
2587
+			}
2588
+			// this doesn't exist in the DB and apparently the thing it belongs to doesn't either,
2589
+			// just get what's cached on this object
2590
+			if (! $related_model_object) {
2591
+				$related_model_object = $this->get_one_from_cache($relation_name);
2592
+			}
2593
+		}
2594
+		return $related_model_object;
2595
+	}
2596
+
2597
+
2598
+	/**
2599
+	 * Does a delete on all related objects of type $relation_name and removes
2600
+	 * the current model object's relation to them. If they can't be deleted (because
2601
+	 * of blocking related model objects) does nothing. If the related model objects are
2602
+	 * soft-deletable, they will be soft-deleted regardless of related blocking model objects.
2603
+	 * If this model object doesn't exist yet in the DB, just removes its related things
2604
+	 *
2605
+	 * @param string $relation_name
2606
+	 * @param array  $query_params @see
2607
+	 *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2608
+	 * @return int how many deleted
2609
+	 * @throws ReflectionException
2610
+	 * @throws InvalidArgumentException
2611
+	 * @throws InvalidInterfaceException
2612
+	 * @throws InvalidDataTypeException
2613
+	 * @throws EE_Error
2614
+	 */
2615
+	public function delete_related($relation_name, $query_params = [])
2616
+	{
2617
+		if ($this->ID()) {
2618
+			$count = $this->get_model()->delete_related(
2619
+				$this,
2620
+				$relation_name,
2621
+				$query_params
2622
+			);
2623
+		} else {
2624
+			$count = count($this->get_all_from_cache($relation_name));
2625
+			$this->clear_cache($relation_name, null, true);
2626
+		}
2627
+		return $count;
2628
+	}
2629
+
2630
+
2631
+	/**
2632
+	 * Does a hard delete (ie, removes the DB row) on all related objects of type $relation_name and removes
2633
+	 * the current model object's relation to them. If they can't be deleted (because
2634
+	 * of blocking related model objects) just does a soft delete on it instead, if possible.
2635
+	 * If the related thing isn't a soft-deletable model object, this function is identical
2636
+	 * to delete_related(). If this model object doesn't exist in the DB, just remove its related things
2637
+	 *
2638
+	 * @param string $relation_name
2639
+	 * @param array  $query_params @see
2640
+	 *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2641
+	 * @return int how many deleted (including those soft deleted)
2642
+	 * @throws ReflectionException
2643
+	 * @throws InvalidArgumentException
2644
+	 * @throws InvalidInterfaceException
2645
+	 * @throws InvalidDataTypeException
2646
+	 * @throws EE_Error
2647
+	 */
2648
+	public function delete_related_permanently($relation_name, $query_params = [])
2649
+	{
2650
+		$count = $this->ID()
2651
+			? $this->get_model()->delete_related_permanently(
2652
+				$this,
2653
+				$relation_name,
2654
+				$query_params
2655
+			)
2656
+			: count($this->get_all_from_cache($relation_name));
2657
+
2658
+		$this->clear_cache($relation_name, null, true);
2659
+		return $count;
2660
+	}
2661
+
2662
+
2663
+	/**
2664
+	 * is_set
2665
+	 * Just a simple utility function children can use for checking if property exists
2666
+	 *
2667
+	 * @access  public
2668
+	 * @param string $field_name property to check
2669
+	 * @return bool                              TRUE if existing,FALSE if not.
2670
+	 */
2671
+	public function is_set($field_name)
2672
+	{
2673
+		return isset($this->_fields[ $field_name ]);
2674
+	}
2675
+
2676
+
2677
+	/**
2678
+	 * Just a simple utility function children can use for checking if property (or properties) exists and throwing an
2679
+	 * EE_Error exception if they don't
2680
+	 *
2681
+	 * @param mixed (string|array) $properties properties to check
2682
+	 * @return bool                              TRUE if existing, throw EE_Error if not.
2683
+	 * @throws EE_Error
2684
+	 */
2685
+	protected function _property_exists($properties)
2686
+	{
2687
+		foreach ((array) $properties as $property_name) {
2688
+			// first make sure this property exists
2689
+			if (! $this->_fields[ $property_name ]) {
2690
+				throw new EE_Error(
2691
+					sprintf(
2692
+						esc_html__(
2693
+							'Trying to retrieve a non-existent property (%s).  Double check the spelling please',
2694
+							'event_espresso'
2695
+						),
2696
+						$property_name
2697
+					)
2698
+				);
2699
+			}
2700
+		}
2701
+		return true;
2702
+	}
2703
+
2704
+
2705
+	/**
2706
+	 * This simply returns an array of model fields for this object
2707
+	 *
2708
+	 * @return array
2709
+	 * @throws ReflectionException
2710
+	 * @throws InvalidArgumentException
2711
+	 * @throws InvalidInterfaceException
2712
+	 * @throws InvalidDataTypeException
2713
+	 * @throws EE_Error
2714
+	 */
2715
+	public function model_field_array()
2716
+	{
2717
+		$fields     = $this->get_model()->field_settings(false);
2718
+		$properties = [];
2719
+		// remove prepended underscore
2720
+		foreach ($fields as $field_name => $settings) {
2721
+			$properties[ $field_name ] = $this->get($field_name);
2722
+		}
2723
+		return $properties;
2724
+	}
2725
+
2726
+
2727
+	/**
2728
+	 * Very handy general function to allow for plugins to extend any child of EE_Base_Class.
2729
+	 * If a method is called on a child of EE_Base_Class that doesn't exist, this function is called
2730
+	 * (http://www.garfieldtech.com/blog/php-magic-call) and passed the method's name and arguments.
2731
+	 * Instead of requiring a plugin to extend the EE_Base_Class
2732
+	 * (which works fine is there's only 1 plugin, but when will that happen?)
2733
+	 * they can add a hook onto 'filters_hook_espresso__{className}__{methodName}'
2734
+	 * (eg, filters_hook_espresso__EE_Answer__my_great_function)
2735
+	 * and accepts 2 arguments: the object on which the function was called,
2736
+	 * and an array of the original arguments passed to the function.
2737
+	 * Whatever their callback function returns will be returned by this function.
2738
+	 * Example: in functions.php (or in a plugin):
2739
+	 *      add_filter('FHEE__EE_Answer__my_callback','my_callback',10,3);
2740
+	 *      function my_callback($previousReturnValue,EE_Base_Class $object,$argsArray){
2741
+	 *          $returnString= "you called my_callback! and passed args:".implode(",",$argsArray);
2742
+	 *          return $previousReturnValue.$returnString;
2743
+	 *      }
2744
+	 * require('EE_Answer.class.php');
2745
+	 * echo EE_Answer::new_instance(['REG_ID' => 2,'QST_ID' => 3,'ANS_value' => The answer is 42'])
2746
+	 *      ->my_callback('monkeys',100);
2747
+	 * // will output "you called my_callback! and passed args:monkeys,100"
2748
+	 *
2749
+	 * @param string $methodName name of method which was called on a child of EE_Base_Class, but which
2750
+	 * @param array  $args       array of original arguments passed to the function
2751
+	 * @return mixed whatever the plugin which calls add_filter decides
2752
+	 * @throws EE_Error
2753
+	 */
2754
+	public function __call($methodName, $args)
2755
+	{
2756
+		$className = get_class($this);
2757
+		$tagName   = "FHEE__{$className}__{$methodName}";
2758
+		if (! has_filter($tagName)) {
2759
+			throw new EE_Error(
2760
+				sprintf(
2761
+					esc_html__(
2762
+						"Method %s on class %s does not exist! You can create one with the following code in functions.php or in a plugin: add_filter('%s','my_callback',10,3);function my_callback(\$previousReturnValue,EE_Base_Class \$object, \$argsArray){/*function body*/return \$whatever;}",
2763
+						'event_espresso'
2764
+					),
2765
+					$methodName,
2766
+					$className,
2767
+					$tagName
2768
+				)
2769
+			);
2770
+		}
2771
+		return apply_filters($tagName, null, $this, $args);
2772
+	}
2773
+
2774
+
2775
+	/**
2776
+	 * Similar to insert_post_meta, adds a record in the Extra_Meta model's table with the given key and value.
2777
+	 * A $previous_value can be specified in case there are many meta rows with the same key
2778
+	 *
2779
+	 * @param string $meta_key
2780
+	 * @param mixed  $meta_value
2781
+	 * @param mixed  $previous_value
2782
+	 * @return bool|int # of records updated (or BOOLEAN if we actually ended up inserting the extra meta row)
2783
+	 *                  NOTE: if the values haven't changed, returns 0
2784
+	 * @throws InvalidArgumentException
2785
+	 * @throws InvalidInterfaceException
2786
+	 * @throws InvalidDataTypeException
2787
+	 * @throws EE_Error
2788
+	 * @throws ReflectionException
2789
+	 */
2790
+	public function update_extra_meta(string $meta_key, $meta_value, $previous_value = null)
2791
+	{
2792
+		$query_params = [
2793
+			[
2794
+				'EXM_key'  => $meta_key,
2795
+				'OBJ_ID'   => $this->ID(),
2796
+				'EXM_type' => $this->get_model()->get_this_model_name(),
2797
+			],
2798
+		];
2799
+		if ($previous_value !== null) {
2800
+			$query_params[0]['EXM_value'] = $previous_value;
2801
+		}
2802
+		$existing_rows_like_that = EEM_Extra_Meta::instance()->get_all($query_params);
2803
+		if (! $existing_rows_like_that) {
2804
+			return $this->add_extra_meta($meta_key, $meta_value);
2805
+		}
2806
+		foreach ($existing_rows_like_that as $existing_row) {
2807
+			$existing_row->save(['EXM_value' => $meta_value]);
2808
+		}
2809
+		return count($existing_rows_like_that);
2810
+	}
2811
+
2812
+
2813
+	/**
2814
+	 * Adds a new extra meta record. If $unique is set to TRUE, we'll first double-check
2815
+	 * no other extra meta for this model object have the same key. Returns TRUE if the
2816
+	 * extra meta row was entered, false if not
2817
+	 *
2818
+	 * @param string $meta_key
2819
+	 * @param mixed  $meta_value
2820
+	 * @param bool   $unique
2821
+	 * @return bool
2822
+	 * @throws InvalidArgumentException
2823
+	 * @throws InvalidInterfaceException
2824
+	 * @throws InvalidDataTypeException
2825
+	 * @throws EE_Error
2826
+	 * @throws ReflectionException
2827
+	 */
2828
+	public function add_extra_meta(string $meta_key, $meta_value, bool $unique = false): bool
2829
+	{
2830
+		if ($unique) {
2831
+			$existing_extra_meta = EEM_Extra_Meta::instance()->get_one(
2832
+				[
2833
+					[
2834
+						'EXM_key'  => $meta_key,
2835
+						'OBJ_ID'   => $this->ID(),
2836
+						'EXM_type' => $this->get_model()->get_this_model_name(),
2837
+					],
2838
+				]
2839
+			);
2840
+			if ($existing_extra_meta) {
2841
+				return false;
2842
+			}
2843
+		}
2844
+		$new_extra_meta = EE_Extra_Meta::new_instance(
2845
+			[
2846
+				'EXM_key'   => $meta_key,
2847
+				'EXM_value' => $meta_value,
2848
+				'OBJ_ID'    => $this->ID(),
2849
+				'EXM_type'  => $this->get_model()->get_this_model_name(),
2850
+			]
2851
+		);
2852
+		$new_extra_meta->save();
2853
+		return true;
2854
+	}
2855
+
2856
+
2857
+	/**
2858
+	 * Deletes all the extra meta rows for this record as specified by key. If $meta_value
2859
+	 * is specified, only deletes extra meta records with that value.
2860
+	 *
2861
+	 * @param string $meta_key
2862
+	 * @param mixed  $meta_value
2863
+	 * @return int number of extra meta rows deleted
2864
+	 * @throws InvalidArgumentException
2865
+	 * @throws InvalidInterfaceException
2866
+	 * @throws InvalidDataTypeException
2867
+	 * @throws EE_Error
2868
+	 * @throws ReflectionException
2869
+	 */
2870
+	public function delete_extra_meta(string $meta_key, $meta_value = null)
2871
+	{
2872
+		$query_params = [
2873
+			[
2874
+				'EXM_key'  => $meta_key,
2875
+				'OBJ_ID'   => $this->ID(),
2876
+				'EXM_type' => $this->get_model()->get_this_model_name(),
2877
+			],
2878
+		];
2879
+		if ($meta_value !== null) {
2880
+			$query_params[0]['EXM_value'] = $meta_value;
2881
+		}
2882
+		return EEM_Extra_Meta::instance()->delete($query_params);
2883
+	}
2884
+
2885
+
2886
+	/**
2887
+	 * Gets the extra meta with the given meta key. If you specify "single" we just return 1, otherwise
2888
+	 * an array of everything found. Requires that this model actually have a relation of type EE_Has_Many_Any_Relation.
2889
+	 * You can specify $default is case you haven't found the extra meta
2890
+	 *
2891
+	 * @param string     $meta_key
2892
+	 * @param bool       $single
2893
+	 * @param mixed      $default if we don't find anything, what should we return?
2894
+	 * @param array|null $extra_where
2895
+	 * @return mixed single value if $single; array if ! $single
2896
+	 * @throws ReflectionException
2897
+	 * @throws EE_Error
2898
+	 */
2899
+	public function get_extra_meta(string $meta_key, bool $single = false, $default = null, ?array $extra_where = [])
2900
+	{
2901
+		$query_params = [$extra_where + ['EXM_key' => $meta_key]];
2902
+		if ($single) {
2903
+			$result = $this->get_first_related('Extra_Meta', $query_params);
2904
+			if ($result instanceof EE_Extra_Meta) {
2905
+				return $result->value();
2906
+			}
2907
+		} else {
2908
+			$results = $this->get_many_related('Extra_Meta', $query_params);
2909
+			if ($results) {
2910
+				$values = [];
2911
+				foreach ($results as $result) {
2912
+					if ($result instanceof EE_Extra_Meta) {
2913
+						$values[ $result->ID() ] = $result->value();
2914
+					}
2915
+				}
2916
+				return $values;
2917
+			}
2918
+		}
2919
+		// if nothing discovered yet return default.
2920
+		return apply_filters(
2921
+			'FHEE__EE_Base_Class__get_extra_meta__default_value',
2922
+			$default,
2923
+			$meta_key,
2924
+			$single,
2925
+			$this
2926
+		);
2927
+	}
2928
+
2929
+
2930
+	/**
2931
+	 * Returns a simple array of all the extra meta associated with this model object.
2932
+	 * If $one_of_each_key is true (Default), it will be an array of simple key-value pairs, keys being the
2933
+	 * extra meta's key, and teh value being its value. However, if there are duplicate extra meta rows with
2934
+	 * the same key, only one will be used. (eg array('foo'=>'bar','monkey'=>123))
2935
+	 * If $one_of_each_key is false, it will return an array with the top-level keys being
2936
+	 * the extra meta keys, but their values are also arrays, which have the extra-meta's ID as their sub-key, and
2937
+	 * finally the extra meta's value as each sub-value. (eg
2938
+	 * array('foo'=>array(1=>'bar',2=>'bill'),'monkey'=>array(3=>123)))
2939
+	 *
2940
+	 * @param bool $one_of_each_key
2941
+	 * @return array
2942
+	 * @throws ReflectionException
2943
+	 * @throws InvalidArgumentException
2944
+	 * @throws InvalidInterfaceException
2945
+	 * @throws InvalidDataTypeException
2946
+	 * @throws EE_Error
2947
+	 */
2948
+	public function all_extra_meta_array(bool $one_of_each_key = true): array
2949
+	{
2950
+		$return_array = [];
2951
+		if ($one_of_each_key) {
2952
+			$extra_meta_objs = $this->get_many_related(
2953
+				'Extra_Meta',
2954
+				['group_by' => 'EXM_key']
2955
+			);
2956
+			foreach ($extra_meta_objs as $extra_meta_obj) {
2957
+				if ($extra_meta_obj instanceof EE_Extra_Meta) {
2958
+					$return_array[ $extra_meta_obj->key() ] = $extra_meta_obj->value();
2959
+				}
2960
+			}
2961
+		} else {
2962
+			$extra_meta_objs = $this->get_many_related('Extra_Meta');
2963
+			foreach ($extra_meta_objs as $extra_meta_obj) {
2964
+				if ($extra_meta_obj instanceof EE_Extra_Meta) {
2965
+					if (! isset($return_array[ $extra_meta_obj->key() ])) {
2966
+						$return_array[ $extra_meta_obj->key() ] = [];
2967
+					}
2968
+					$return_array[ $extra_meta_obj->key() ][ $extra_meta_obj->ID() ] = $extra_meta_obj->value();
2969
+				}
2970
+			}
2971
+		}
2972
+		return $return_array;
2973
+	}
2974
+
2975
+
2976
+	/**
2977
+	 * Gets a pretty nice displayable nice for this model object. Often overridden
2978
+	 *
2979
+	 * @return string
2980
+	 * @throws ReflectionException
2981
+	 * @throws InvalidArgumentException
2982
+	 * @throws InvalidInterfaceException
2983
+	 * @throws InvalidDataTypeException
2984
+	 * @throws EE_Error
2985
+	 */
2986
+	public function name()
2987
+	{
2988
+		// find a field that's not a text field
2989
+		$field_we_can_use = $this->get_model()->get_a_field_of_type('EE_Text_Field_Base');
2990
+		if ($field_we_can_use) {
2991
+			return $this->get($field_we_can_use->get_name());
2992
+		}
2993
+		$first_few_properties = $this->model_field_array();
2994
+		$first_few_properties = array_slice($first_few_properties, 0, 3);
2995
+		$name_parts           = [];
2996
+		foreach ($first_few_properties as $name => $value) {
2997
+			$name_parts[] = "$name:$value";
2998
+		}
2999
+		return implode(',', $name_parts);
3000
+	}
3001
+
3002
+
3003
+	/**
3004
+	 * in_entity_map
3005
+	 * Checks if this model object has been proven to already be in the entity map
3006
+	 *
3007
+	 * @return boolean
3008
+	 * @throws ReflectionException
3009
+	 * @throws InvalidArgumentException
3010
+	 * @throws InvalidInterfaceException
3011
+	 * @throws InvalidDataTypeException
3012
+	 * @throws EE_Error
3013
+	 */
3014
+	public function in_entity_map()
3015
+	{
3016
+		// well, if we looked, did we find it in the entity map?
3017
+		return $this->ID() && $this->get_model()->get_from_entity_map($this->ID()) === $this;
3018
+	}
3019
+
3020
+
3021
+	/**
3022
+	 * refresh_from_db
3023
+	 * Makes sure the fields and values on this model object are in-sync with what's in the database.
3024
+	 *
3025
+	 * @throws ReflectionException
3026
+	 * @throws InvalidArgumentException
3027
+	 * @throws InvalidInterfaceException
3028
+	 * @throws InvalidDataTypeException
3029
+	 * @throws EE_Error if this model object isn't in the entity mapper (because then you should
3030
+	 * just use what's in the entity mapper and refresh it) and WP_DEBUG is TRUE
3031
+	 */
3032
+	public function refresh_from_db()
3033
+	{
3034
+		if ($this->ID() && $this->in_entity_map()) {
3035
+			$this->get_model()->refresh_entity_map_from_db($this->ID());
3036
+		} else {
3037
+			// if it doesn't have ID, you shouldn't be asking to refresh it from teh database (because its not in the database)
3038
+			// if it has an ID but it's not in the map, and you're asking me to refresh it
3039
+			// that's kinda dangerous. You should just use what's in the entity map, or add this to the entity map if there's
3040
+			// absolutely nothing in it for this ID
3041
+			if (WP_DEBUG) {
3042
+				throw new EE_Error(
3043
+					sprintf(
3044
+						esc_html__(
3045
+							'Trying to refresh a model object with ID "%1$s" that\'s not in the entity map? First off: you should put it in the entity map by calling %2$s. Second off, if you want what\'s in the database right now, you should just call %3$s yourself and discard this model object.',
3046
+							'event_espresso'
3047
+						),
3048
+						$this->ID(),
3049
+						get_class($this->get_model()) . '::instance()->add_to_entity_map()',
3050
+						get_class($this->get_model()) . '::instance()->refresh_entity_map()'
3051
+					)
3052
+				);
3053
+			}
3054
+		}
3055
+	}
3056
+
3057
+
3058
+	/**
3059
+	 * Change $fields' values to $new_value_sql (which is a string of raw SQL)
3060
+	 *
3061
+	 * @param EE_Model_Field_Base[] $fields
3062
+	 * @param string                $new_value_sql
3063
+	 *          example: 'column_name=123',
3064
+	 *          or 'column_name=column_name+1',
3065
+	 *          or 'column_name= CASE
3066
+	 *          WHEN (`column_name` + `other_column` + 5) <= `yet_another_column`
3067
+	 *          THEN `column_name` + 5
3068
+	 *          ELSE `column_name`
3069
+	 *          END'
3070
+	 *          Also updates $field on this model object with the latest value from the database.
3071
+	 * @return bool
3072
+	 * @throws EE_Error
3073
+	 * @throws InvalidArgumentException
3074
+	 * @throws InvalidDataTypeException
3075
+	 * @throws InvalidInterfaceException
3076
+	 * @throws ReflectionException
3077
+	 * @since 4.9.80.p
3078
+	 */
3079
+	protected function updateFieldsInDB($fields, $new_value_sql)
3080
+	{
3081
+		// First make sure this model object actually exists in the DB. It would be silly to try to update it in the DB
3082
+		// if it wasn't even there to start off.
3083
+		if (! $this->ID()) {
3084
+			$this->save();
3085
+		}
3086
+		global $wpdb;
3087
+		if (empty($fields)) {
3088
+			throw new InvalidArgumentException(
3089
+				esc_html__(
3090
+					'EE_Base_Class::updateFieldsInDB was passed an empty array of fields.',
3091
+					'event_espresso'
3092
+				)
3093
+			);
3094
+		}
3095
+		$first_field = reset($fields);
3096
+		$table_alias = $first_field->get_table_alias();
3097
+		foreach ($fields as $field) {
3098
+			if ($table_alias !== $field->get_table_alias()) {
3099
+				throw new InvalidArgumentException(
3100
+					sprintf(
3101
+						esc_html__(
3102
+						// @codingStandardsIgnoreStart
3103
+							'EE_Base_Class::updateFieldsInDB was passed fields for different tables ("%1$s" and "%2$s"), which is not supported. Instead, please call the method multiple times.',
3104
+							// @codingStandardsIgnoreEnd
3105
+							'event_espresso'
3106
+						),
3107
+						$table_alias,
3108
+						$field->get_table_alias()
3109
+					)
3110
+				);
3111
+			}
3112
+		}
3113
+		// Ok the fields are now known to all be for the same table. Proceed with creating the SQL to update it.
3114
+		$table_obj      = $this->get_model()->get_table_obj_by_alias($table_alias);
3115
+		$table_pk_value = $this->ID();
3116
+		$table_name     = $table_obj->get_table_name();
3117
+		if ($table_obj instanceof EE_Secondary_Table) {
3118
+			$table_pk_field_name = $table_obj->get_fk_on_table();
3119
+		} else {
3120
+			$table_pk_field_name = $table_obj->get_pk_column();
3121
+		}
3122
+
3123
+		$query  =
3124
+			"UPDATE `{$table_name}`
3125 3125
             SET "
3126
-            . $new_value_sql
3127
-            . $wpdb->prepare(
3128
-                "
3126
+			. $new_value_sql
3127
+			. $wpdb->prepare(
3128
+				"
3129 3129
             WHERE `{$table_pk_field_name}` = %d;",
3130
-                $table_pk_value
3131
-            );
3132
-        $result = $wpdb->query($query);
3133
-        foreach ($fields as $field) {
3134
-            // If it was successful, we'd like to know the new value.
3135
-            // If it failed, we'd also like to know the new value.
3136
-            $new_value = $this->get_model()->get_var(
3137
-                $this->get_model()->alter_query_params_to_restrict_by_ID(
3138
-                    $this->get_model()->get_index_primary_key_string(
3139
-                        $this->model_field_array()
3140
-                    ),
3141
-                    [
3142
-                        'default_where_conditions' => 'minimum',
3143
-                    ]
3144
-                ),
3145
-                $field->get_name()
3146
-            );
3147
-            $this->set_from_db(
3148
-                $field->get_name(),
3149
-                $new_value
3150
-            );
3151
-        }
3152
-        return (bool) $result;
3153
-    }
3154
-
3155
-
3156
-    /**
3157
-     * Nudges $field_name's value by $quantity, without any conditionals (in comparison to bumpConditionally()).
3158
-     * Does not allow negative values, however.
3159
-     *
3160
-     * @param array $fields_n_quantities keys are the field names, and values are the amount by which to bump them
3161
-     *                                   (positive or negative). One important gotcha: all these values must be
3162
-     *                                   on the same table (eg don't pass in one field for the posts table and
3163
-     *                                   another for the event meta table.)
3164
-     * @return bool
3165
-     * @throws EE_Error
3166
-     * @throws InvalidArgumentException
3167
-     * @throws InvalidDataTypeException
3168
-     * @throws InvalidInterfaceException
3169
-     * @throws ReflectionException
3170
-     * @since 4.9.80.p
3171
-     */
3172
-    public function adjustNumericFieldsInDb(array $fields_n_quantities)
3173
-    {
3174
-        global $wpdb;
3175
-        if (empty($fields_n_quantities)) {
3176
-            // No fields to update? Well sure, we updated them to that value just fine.
3177
-            return true;
3178
-        }
3179
-        $fields             = [];
3180
-        $set_sql_statements = [];
3181
-        foreach ($fields_n_quantities as $field_name => $quantity) {
3182
-            $field       = $this->get_model()->field_settings_for($field_name, true);
3183
-            $fields[]    = $field;
3184
-            $column_name = $field->get_table_column();
3185
-
3186
-            $abs_qty = absint($quantity);
3187
-            if ($quantity > 0) {
3188
-                // don't let the value be negative as often these fields are unsigned
3189
-                $set_sql_statements[] = $wpdb->prepare(
3190
-                    "`{$column_name}` = `{$column_name}` + %d",
3191
-                    $abs_qty
3192
-                );
3193
-            } else {
3194
-                $set_sql_statements[] = $wpdb->prepare(
3195
-                    "`{$column_name}` = CASE
3130
+				$table_pk_value
3131
+			);
3132
+		$result = $wpdb->query($query);
3133
+		foreach ($fields as $field) {
3134
+			// If it was successful, we'd like to know the new value.
3135
+			// If it failed, we'd also like to know the new value.
3136
+			$new_value = $this->get_model()->get_var(
3137
+				$this->get_model()->alter_query_params_to_restrict_by_ID(
3138
+					$this->get_model()->get_index_primary_key_string(
3139
+						$this->model_field_array()
3140
+					),
3141
+					[
3142
+						'default_where_conditions' => 'minimum',
3143
+					]
3144
+				),
3145
+				$field->get_name()
3146
+			);
3147
+			$this->set_from_db(
3148
+				$field->get_name(),
3149
+				$new_value
3150
+			);
3151
+		}
3152
+		return (bool) $result;
3153
+	}
3154
+
3155
+
3156
+	/**
3157
+	 * Nudges $field_name's value by $quantity, without any conditionals (in comparison to bumpConditionally()).
3158
+	 * Does not allow negative values, however.
3159
+	 *
3160
+	 * @param array $fields_n_quantities keys are the field names, and values are the amount by which to bump them
3161
+	 *                                   (positive or negative). One important gotcha: all these values must be
3162
+	 *                                   on the same table (eg don't pass in one field for the posts table and
3163
+	 *                                   another for the event meta table.)
3164
+	 * @return bool
3165
+	 * @throws EE_Error
3166
+	 * @throws InvalidArgumentException
3167
+	 * @throws InvalidDataTypeException
3168
+	 * @throws InvalidInterfaceException
3169
+	 * @throws ReflectionException
3170
+	 * @since 4.9.80.p
3171
+	 */
3172
+	public function adjustNumericFieldsInDb(array $fields_n_quantities)
3173
+	{
3174
+		global $wpdb;
3175
+		if (empty($fields_n_quantities)) {
3176
+			// No fields to update? Well sure, we updated them to that value just fine.
3177
+			return true;
3178
+		}
3179
+		$fields             = [];
3180
+		$set_sql_statements = [];
3181
+		foreach ($fields_n_quantities as $field_name => $quantity) {
3182
+			$field       = $this->get_model()->field_settings_for($field_name, true);
3183
+			$fields[]    = $field;
3184
+			$column_name = $field->get_table_column();
3185
+
3186
+			$abs_qty = absint($quantity);
3187
+			if ($quantity > 0) {
3188
+				// don't let the value be negative as often these fields are unsigned
3189
+				$set_sql_statements[] = $wpdb->prepare(
3190
+					"`{$column_name}` = `{$column_name}` + %d",
3191
+					$abs_qty
3192
+				);
3193
+			} else {
3194
+				$set_sql_statements[] = $wpdb->prepare(
3195
+					"`{$column_name}` = CASE
3196 3196
                        WHEN (`{$column_name}` >= %d)
3197 3197
                        THEN `{$column_name}` - %d
3198 3198
                        ELSE 0
3199 3199
                     END",
3200
-                    $abs_qty,
3201
-                    $abs_qty
3202
-                );
3203
-            }
3204
-        }
3205
-        return $this->updateFieldsInDB(
3206
-            $fields,
3207
-            implode(', ', $set_sql_statements)
3208
-        );
3209
-    }
3210
-
3211
-
3212
-    /**
3213
-     * Increases the value of the field $field_name_to_bump by $quantity, but only if the values of
3214
-     * $field_name_to_bump plus $field_name_affecting_total and $quantity won't exceed $limit_field_name's value.
3215
-     * For example, this is useful when bumping the value of TKT_reserved, TKT_sold, DTT_reserved or DTT_sold.
3216
-     * Returns true if the value was successfully bumped, and updates the value on this model object.
3217
-     * Otherwise returns false.
3218
-     *
3219
-     * @param string $field_name_to_bump
3220
-     * @param string $field_name_affecting_total
3221
-     * @param string $limit_field_name
3222
-     * @param int    $quantity
3223
-     * @return bool
3224
-     * @throws EE_Error
3225
-     * @throws InvalidArgumentException
3226
-     * @throws InvalidDataTypeException
3227
-     * @throws InvalidInterfaceException
3228
-     * @throws ReflectionException
3229
-     * @since 4.9.80.p
3230
-     */
3231
-    public function incrementFieldConditionallyInDb(
3232
-        $field_name_to_bump,
3233
-        $field_name_affecting_total,
3234
-        $limit_field_name,
3235
-        $quantity
3236
-    ) {
3237
-        global $wpdb;
3238
-        $field       = $this->get_model()->field_settings_for($field_name_to_bump, true);
3239
-        $column_name = $field->get_table_column();
3240
-
3241
-        $field_affecting_total  = $this->get_model()->field_settings_for($field_name_affecting_total, true);
3242
-        $column_affecting_total = $field_affecting_total->get_table_column();
3243
-
3244
-        $limiting_field  = $this->get_model()->field_settings_for($limit_field_name, true);
3245
-        $limiting_column = $limiting_field->get_table_column();
3246
-        return $this->updateFieldsInDB(
3247
-            [$field],
3248
-            $wpdb->prepare(
3249
-                "`{$column_name}` =
3200
+					$abs_qty,
3201
+					$abs_qty
3202
+				);
3203
+			}
3204
+		}
3205
+		return $this->updateFieldsInDB(
3206
+			$fields,
3207
+			implode(', ', $set_sql_statements)
3208
+		);
3209
+	}
3210
+
3211
+
3212
+	/**
3213
+	 * Increases the value of the field $field_name_to_bump by $quantity, but only if the values of
3214
+	 * $field_name_to_bump plus $field_name_affecting_total and $quantity won't exceed $limit_field_name's value.
3215
+	 * For example, this is useful when bumping the value of TKT_reserved, TKT_sold, DTT_reserved or DTT_sold.
3216
+	 * Returns true if the value was successfully bumped, and updates the value on this model object.
3217
+	 * Otherwise returns false.
3218
+	 *
3219
+	 * @param string $field_name_to_bump
3220
+	 * @param string $field_name_affecting_total
3221
+	 * @param string $limit_field_name
3222
+	 * @param int    $quantity
3223
+	 * @return bool
3224
+	 * @throws EE_Error
3225
+	 * @throws InvalidArgumentException
3226
+	 * @throws InvalidDataTypeException
3227
+	 * @throws InvalidInterfaceException
3228
+	 * @throws ReflectionException
3229
+	 * @since 4.9.80.p
3230
+	 */
3231
+	public function incrementFieldConditionallyInDb(
3232
+		$field_name_to_bump,
3233
+		$field_name_affecting_total,
3234
+		$limit_field_name,
3235
+		$quantity
3236
+	) {
3237
+		global $wpdb;
3238
+		$field       = $this->get_model()->field_settings_for($field_name_to_bump, true);
3239
+		$column_name = $field->get_table_column();
3240
+
3241
+		$field_affecting_total  = $this->get_model()->field_settings_for($field_name_affecting_total, true);
3242
+		$column_affecting_total = $field_affecting_total->get_table_column();
3243
+
3244
+		$limiting_field  = $this->get_model()->field_settings_for($limit_field_name, true);
3245
+		$limiting_column = $limiting_field->get_table_column();
3246
+		return $this->updateFieldsInDB(
3247
+			[$field],
3248
+			$wpdb->prepare(
3249
+				"`{$column_name}` =
3250 3250
             CASE
3251 3251
                WHEN ((`{$column_name}` + `{$column_affecting_total}` + %d) <= `{$limiting_column}`) OR `{$limiting_column}` = %d
3252 3252
                THEN `{$column_name}` + %d
3253 3253
                ELSE `{$column_name}`
3254 3254
             END",
3255
-                $quantity,
3256
-                EE_INF_IN_DB,
3257
-                $quantity
3258
-            )
3259
-        );
3260
-    }
3261
-
3262
-
3263
-    /**
3264
-     * Because some other plugins, like Advanced Cron Manager, expect all objects to have this method
3265
-     * (probably a bad assumption they have made, oh well)
3266
-     *
3267
-     * @return string
3268
-     */
3269
-    public function __toString()
3270
-    {
3271
-        try {
3272
-            return sprintf('%s (%s)', $this->name(), $this->ID());
3273
-        } catch (Exception $e) {
3274
-            EE_Error::add_error($e->getMessage(), __FILE__, __FUNCTION__, __LINE__);
3275
-            return '';
3276
-        }
3277
-    }
3278
-
3279
-
3280
-    /**
3281
-     * Clear related model objects if they're already in the DB, because otherwise when we
3282
-     * UN-serialize this model object we'll need to be careful to add them to the entity map.
3283
-     * This means if we have made changes to those related model objects, and want to unserialize
3284
-     * the this model object on a subsequent request, changes to those related model objects will be lost.
3285
-     * Instead, those related model objects should be directly serialized and stored.
3286
-     * Eg, the following won't work:
3287
-     * $reg = EEM_Registration::instance()->get_one_by_ID( 123 );
3288
-     * $att = $reg->attendee();
3289
-     * $att->set( 'ATT_fname', 'Dirk' );
3290
-     * update_option( 'my_option', serialize( $reg ) );
3291
-     * //END REQUEST
3292
-     * //START NEXT REQUEST
3293
-     * $reg = get_option( 'my_option' );
3294
-     * $reg->attendee()->save();
3295
-     * And would need to be replace with:
3296
-     * $reg = EEM_Registration::instance()->get_one_by_ID( 123 );
3297
-     * $att = $reg->attendee();
3298
-     * $att->set( 'ATT_fname', 'Dirk' );
3299
-     * update_option( 'my_option', serialize( $reg ) );
3300
-     * //END REQUEST
3301
-     * //START NEXT REQUEST
3302
-     * $att = get_option( 'my_option' );
3303
-     * $att->save();
3304
-     *
3305
-     * @return array
3306
-     * @throws ReflectionException
3307
-     * @throws InvalidArgumentException
3308
-     * @throws InvalidInterfaceException
3309
-     * @throws InvalidDataTypeException
3310
-     * @throws EE_Error
3311
-     */
3312
-    public function __sleep()
3313
-    {
3314
-        $model = $this->get_model();
3315
-        foreach ($model->relation_settings() as $relation_name => $relation_obj) {
3316
-            if ($relation_obj instanceof EE_Belongs_To_Relation) {
3317
-                $classname = 'EE_' . $model->get_this_model_name();
3318
-                if (
3319
-                    $this->get_one_from_cache($relation_name) instanceof $classname
3320
-                    && $this->get_one_from_cache($relation_name)->ID()
3321
-                ) {
3322
-                    $this->clear_cache(
3323
-                        $relation_name,
3324
-                        $this->get_one_from_cache($relation_name)->ID()
3325
-                    );
3326
-                }
3327
-            }
3328
-        }
3329
-        $this->_props_n_values_provided_in_constructor = [];
3330
-        $properties_to_serialize                       = get_object_vars($this);
3331
-        // don't serialize the model. It's big and that risks recursion
3332
-        unset($properties_to_serialize['_model']);
3333
-        return array_keys($properties_to_serialize);
3334
-    }
3335
-
3336
-
3337
-    /**
3338
-     * restore _props_n_values_provided_in_constructor
3339
-     * PLZ NOTE: this will reset the array to whatever fields values were present prior to serialization,
3340
-     * and therefore should NOT be used to determine if state change has occurred since initial construction.
3341
-     * At best, you would only be able to detect if state change has occurred during THIS request.
3342
-     */
3343
-    public function __wakeup()
3344
-    {
3345
-        $this->_props_n_values_provided_in_constructor = $this->_fields;
3346
-    }
3347
-
3348
-
3349
-    /**
3350
-     * Usage of this magic method is to ensure any internally cached references to object instances that must remain
3351
-     * distinct with the clone host instance are also cloned.
3352
-     */
3353
-    public function __clone()
3354
-    {
3355
-        // handle DateTimes (this is handled in here because there's no one specific child class that uses datetimes).
3356
-        foreach ($this->_fields as $field => $value) {
3357
-            if ($value instanceof DateTime) {
3358
-                $this->_fields[ $field ] = clone $value;
3359
-            }
3360
-        }
3361
-    }
3362
-
3363
-
3364
-    public function debug()
3365
-    {
3366
-        $this->echoProperty(get_class($this), get_object_vars($this));
3367
-        echo "\n\n";
3368
-    }
3369
-
3370
-
3371
-    private function echoProperty($field, $value, int $indent = 0)
3372
-    {
3373
-        $bullets = str_repeat(' -', $indent) . ' ';
3374
-        $field = strpos($field, '_') === 0 ? substr($field, 1) : $field;
3375
-        echo "\n$bullets$field: ";
3376
-        if ($value instanceof EEM_Base) {
3377
-            $value = get_class($value);
3378
-        } elseif (is_object($value)) {
3379
-            $value = get_object_vars($value);
3380
-        }
3381
-        if (is_array($value)) {
3382
-            foreach ($value as $f => $v) {
3383
-                $this->echoProperty($f, $v, $indent + 1);
3384
-            }
3385
-            return;
3386
-        }
3387
-        ob_start();
3388
-        var_dump($value);
3389
-        echo rtrim(ob_get_clean(), "\n");
3390
-    }
3255
+				$quantity,
3256
+				EE_INF_IN_DB,
3257
+				$quantity
3258
+			)
3259
+		);
3260
+	}
3261
+
3262
+
3263
+	/**
3264
+	 * Because some other plugins, like Advanced Cron Manager, expect all objects to have this method
3265
+	 * (probably a bad assumption they have made, oh well)
3266
+	 *
3267
+	 * @return string
3268
+	 */
3269
+	public function __toString()
3270
+	{
3271
+		try {
3272
+			return sprintf('%s (%s)', $this->name(), $this->ID());
3273
+		} catch (Exception $e) {
3274
+			EE_Error::add_error($e->getMessage(), __FILE__, __FUNCTION__, __LINE__);
3275
+			return '';
3276
+		}
3277
+	}
3278
+
3279
+
3280
+	/**
3281
+	 * Clear related model objects if they're already in the DB, because otherwise when we
3282
+	 * UN-serialize this model object we'll need to be careful to add them to the entity map.
3283
+	 * This means if we have made changes to those related model objects, and want to unserialize
3284
+	 * the this model object on a subsequent request, changes to those related model objects will be lost.
3285
+	 * Instead, those related model objects should be directly serialized and stored.
3286
+	 * Eg, the following won't work:
3287
+	 * $reg = EEM_Registration::instance()->get_one_by_ID( 123 );
3288
+	 * $att = $reg->attendee();
3289
+	 * $att->set( 'ATT_fname', 'Dirk' );
3290
+	 * update_option( 'my_option', serialize( $reg ) );
3291
+	 * //END REQUEST
3292
+	 * //START NEXT REQUEST
3293
+	 * $reg = get_option( 'my_option' );
3294
+	 * $reg->attendee()->save();
3295
+	 * And would need to be replace with:
3296
+	 * $reg = EEM_Registration::instance()->get_one_by_ID( 123 );
3297
+	 * $att = $reg->attendee();
3298
+	 * $att->set( 'ATT_fname', 'Dirk' );
3299
+	 * update_option( 'my_option', serialize( $reg ) );
3300
+	 * //END REQUEST
3301
+	 * //START NEXT REQUEST
3302
+	 * $att = get_option( 'my_option' );
3303
+	 * $att->save();
3304
+	 *
3305
+	 * @return array
3306
+	 * @throws ReflectionException
3307
+	 * @throws InvalidArgumentException
3308
+	 * @throws InvalidInterfaceException
3309
+	 * @throws InvalidDataTypeException
3310
+	 * @throws EE_Error
3311
+	 */
3312
+	public function __sleep()
3313
+	{
3314
+		$model = $this->get_model();
3315
+		foreach ($model->relation_settings() as $relation_name => $relation_obj) {
3316
+			if ($relation_obj instanceof EE_Belongs_To_Relation) {
3317
+				$classname = 'EE_' . $model->get_this_model_name();
3318
+				if (
3319
+					$this->get_one_from_cache($relation_name) instanceof $classname
3320
+					&& $this->get_one_from_cache($relation_name)->ID()
3321
+				) {
3322
+					$this->clear_cache(
3323
+						$relation_name,
3324
+						$this->get_one_from_cache($relation_name)->ID()
3325
+					);
3326
+				}
3327
+			}
3328
+		}
3329
+		$this->_props_n_values_provided_in_constructor = [];
3330
+		$properties_to_serialize                       = get_object_vars($this);
3331
+		// don't serialize the model. It's big and that risks recursion
3332
+		unset($properties_to_serialize['_model']);
3333
+		return array_keys($properties_to_serialize);
3334
+	}
3335
+
3336
+
3337
+	/**
3338
+	 * restore _props_n_values_provided_in_constructor
3339
+	 * PLZ NOTE: this will reset the array to whatever fields values were present prior to serialization,
3340
+	 * and therefore should NOT be used to determine if state change has occurred since initial construction.
3341
+	 * At best, you would only be able to detect if state change has occurred during THIS request.
3342
+	 */
3343
+	public function __wakeup()
3344
+	{
3345
+		$this->_props_n_values_provided_in_constructor = $this->_fields;
3346
+	}
3347
+
3348
+
3349
+	/**
3350
+	 * Usage of this magic method is to ensure any internally cached references to object instances that must remain
3351
+	 * distinct with the clone host instance are also cloned.
3352
+	 */
3353
+	public function __clone()
3354
+	{
3355
+		// handle DateTimes (this is handled in here because there's no one specific child class that uses datetimes).
3356
+		foreach ($this->_fields as $field => $value) {
3357
+			if ($value instanceof DateTime) {
3358
+				$this->_fields[ $field ] = clone $value;
3359
+			}
3360
+		}
3361
+	}
3362
+
3363
+
3364
+	public function debug()
3365
+	{
3366
+		$this->echoProperty(get_class($this), get_object_vars($this));
3367
+		echo "\n\n";
3368
+	}
3369
+
3370
+
3371
+	private function echoProperty($field, $value, int $indent = 0)
3372
+	{
3373
+		$bullets = str_repeat(' -', $indent) . ' ';
3374
+		$field = strpos($field, '_') === 0 ? substr($field, 1) : $field;
3375
+		echo "\n$bullets$field: ";
3376
+		if ($value instanceof EEM_Base) {
3377
+			$value = get_class($value);
3378
+		} elseif (is_object($value)) {
3379
+			$value = get_object_vars($value);
3380
+		}
3381
+		if (is_array($value)) {
3382
+			foreach ($value as $f => $v) {
3383
+				$this->echoProperty($f, $v, $indent + 1);
3384
+			}
3385
+			return;
3386
+		}
3387
+		ob_start();
3388
+		var_dump($value);
3389
+		echo rtrim(ob_get_clean(), "\n");
3390
+	}
3391 3391
 }
Please login to merge, or discard this patch.
Spacing   +117 added lines, -117 removed lines patch added patch discarded remove patch
@@ -132,7 +132,7 @@  discard block
 block discarded – undo
132 132
         $fieldValues = is_array($fieldValues) ? $fieldValues : [$fieldValues];
133 133
         // verify client code has not passed any invalid field names
134 134
         foreach ($fieldValues as $field_name => $field_value) {
135
-            if (! isset($model_fields[ $field_name ])) {
135
+            if ( ! isset($model_fields[$field_name])) {
136 136
                 throw new EE_Error(
137 137
                     sprintf(
138 138
                         esc_html__(
@@ -150,7 +150,7 @@  discard block
 block discarded – undo
150 150
         $date_format     = null;
151 151
         $time_format     = null;
152 152
         $this->_timezone = EEH_DTT_Helper::get_valid_timezone_string($timezone);
153
-        if (! empty($date_formats) && is_array($date_formats)) {
153
+        if ( ! empty($date_formats) && is_array($date_formats)) {
154 154
             [$date_format, $time_format] = $date_formats;
155 155
         }
156 156
         $this->set_date_format($date_format);
@@ -161,14 +161,14 @@  discard block
 block discarded – undo
161 161
                 // client code has indicated these field values are from the database
162 162
                 $this->set_from_db(
163 163
                     $fieldName,
164
-                    $fieldValues[ $fieldName ] ?? null
164
+                    $fieldValues[$fieldName] ?? null
165 165
                 );
166 166
             } else {
167 167
                 // we're constructing a brand new instance of the model object.
168 168
                 // Generally, this means we'll need to do more field validation
169 169
                 $this->set(
170 170
                     $fieldName,
171
-                    $fieldValues[ $fieldName ] ?? null,
171
+                    $fieldValues[$fieldName] ?? null,
172 172
                     true
173 173
                 );
174 174
             }
@@ -176,15 +176,15 @@  discard block
 block discarded – undo
176 176
         // remember what values were passed to this constructor
177 177
         $this->_props_n_values_provided_in_constructor = $fieldValues;
178 178
         // remember in entity mapper
179
-        if (! $bydb && $model->has_primary_key_field() && $this->ID()) {
179
+        if ( ! $bydb && $model->has_primary_key_field() && $this->ID()) {
180 180
             $model->add_to_entity_map($this);
181 181
         }
182 182
         // setup all the relations
183 183
         foreach ($model->relation_settings() as $relation_name => $relation_obj) {
184 184
             if ($relation_obj instanceof EE_Belongs_To_Relation) {
185
-                $this->_model_relations[ $relation_name ] = null;
185
+                $this->_model_relations[$relation_name] = null;
186 186
             } else {
187
-                $this->_model_relations[ $relation_name ] = [];
187
+                $this->_model_relations[$relation_name] = [];
188 188
             }
189 189
         }
190 190
         /**
@@ -236,10 +236,10 @@  discard block
 block discarded – undo
236 236
     public function get_original($field_name)
237 237
     {
238 238
         if (
239
-            isset($this->_props_n_values_provided_in_constructor[ $field_name ])
239
+            isset($this->_props_n_values_provided_in_constructor[$field_name])
240 240
             && $field_settings = $this->get_model()->field_settings_for($field_name)
241 241
         ) {
242
-            return $field_settings->prepare_for_get($this->_props_n_values_provided_in_constructor[ $field_name ]);
242
+            return $field_settings->prepare_for_get($this->_props_n_values_provided_in_constructor[$field_name]);
243 243
         }
244 244
         return null;
245 245
     }
@@ -275,7 +275,7 @@  discard block
 block discarded – undo
275 275
         // then don't do anything
276 276
         if (
277 277
             ! $use_default
278
-            && $this->_fields[ $field_name ] === $field_value
278
+            && $this->_fields[$field_name] === $field_value
279 279
             && $this->ID()
280 280
         ) {
281 281
             return;
@@ -283,7 +283,7 @@  discard block
 block discarded – undo
283 283
         $model              = $this->get_model();
284 284
         $this->_has_changes = true;
285 285
         $field_obj          = $model->field_settings_for($field_name);
286
-        if (! $field_obj instanceof EE_Model_Field_Base) {
286
+        if ( ! $field_obj instanceof EE_Model_Field_Base) {
287 287
             throw new EE_Error(
288 288
                 sprintf(
289 289
                     esc_html__(
@@ -306,7 +306,7 @@  discard block
 block discarded – undo
306 306
             ? $field_obj->get_default_value()
307 307
             : $field_value;
308 308
 
309
-        $this->_fields[ $field_name ] = $field_obj->prepare_for_set($value);
309
+        $this->_fields[$field_name] = $field_obj->prepare_for_set($value);
310 310
 
311 311
         // if we're not in the constructor...
312 312
         // now check if what we set was a primary key
@@ -368,7 +368,7 @@  discard block
 block discarded – undo
368 368
             } else {
369 369
                 $field_value = $field_obj->prepare_for_set_from_db($field_value_from_db);
370 370
             }
371
-            $this->_fields[ $field_name ] = $field_value;
371
+            $this->_fields[$field_name] = $field_value;
372 372
             $this->_clear_cached_property($field_name);
373 373
         }
374 374
     }
@@ -394,7 +394,7 @@  discard block
 block discarded – undo
394 394
      */
395 395
     public function getCustomSelect($alias)
396 396
     {
397
-        return $this->custom_selection_results[ $alias ] ?? null;
397
+        return $this->custom_selection_results[$alias] ?? null;
398 398
     }
399 399
 
400 400
 
@@ -481,8 +481,8 @@  discard block
 block discarded – undo
481 481
         foreach ($model_fields as $field_name => $field_obj) {
482 482
             if ($field_obj instanceof EE_Datetime_Field) {
483 483
                 $field_obj->set_timezone($this->_timezone);
484
-                if (isset($this->_fields[ $field_name ]) && $this->_fields[ $field_name ] instanceof DateTime) {
485
-                    EEH_DTT_Helper::setTimezone($this->_fields[ $field_name ], new DateTimeZone($this->_timezone));
484
+                if (isset($this->_fields[$field_name]) && $this->_fields[$field_name] instanceof DateTime) {
485
+                    EEH_DTT_Helper::setTimezone($this->_fields[$field_name], new DateTimeZone($this->_timezone));
486 486
                 }
487 487
             }
488 488
         }
@@ -540,7 +540,7 @@  discard block
 block discarded – undo
540 540
      */
541 541
     public function get_format($full = true)
542 542
     {
543
-        return $full ? $this->_dt_frmt . ' ' . $this->_tm_frmt : [$this->_dt_frmt, $this->_tm_frmt];
543
+        return $full ? $this->_dt_frmt.' '.$this->_tm_frmt : [$this->_dt_frmt, $this->_tm_frmt];
544 544
     }
545 545
 
546 546
 
@@ -566,11 +566,11 @@  discard block
 block discarded – undo
566 566
     public function cache($relation_name = '', $object_to_cache = null, $cache_id = null)
567 567
     {
568 568
         // its entirely possible that there IS no related object yet in which case there is nothing to cache.
569
-        if (! $object_to_cache instanceof EE_Base_Class) {
569
+        if ( ! $object_to_cache instanceof EE_Base_Class) {
570 570
             return false;
571 571
         }
572 572
         // also get "how" the object is related, or throw an error
573
-        if (! $relationship_to_model = $this->get_model()->related_settings_for($relation_name)) {
573
+        if ( ! $relationship_to_model = $this->get_model()->related_settings_for($relation_name)) {
574 574
             throw new EE_Error(
575 575
                 sprintf(
576 576
                     esc_html__('There is no relationship to %s on a %s. Cannot cache it', 'event_espresso'),
@@ -584,38 +584,38 @@  discard block
 block discarded – undo
584 584
             // if it's a "belongs to" relationship, then there's only one related model object
585 585
             // eg, if this is a registration, there's only 1 attendee for it
586 586
             // so for these model objects just set it to be cached
587
-            $this->_model_relations[ $relation_name ] = $object_to_cache;
587
+            $this->_model_relations[$relation_name] = $object_to_cache;
588 588
             $return                                   = true;
589 589
         } else {
590 590
             // otherwise, this is the "many" side of a one to many relationship,
591 591
             // so we'll add the object to the array of related objects for that type.
592 592
             // eg: if this is an event, there are many registrations for that event,
593 593
             // so we cache the registrations in an array
594
-            if (! is_array($this->_model_relations[ $relation_name ])) {
594
+            if ( ! is_array($this->_model_relations[$relation_name])) {
595 595
                 // if for some reason, the cached item is a model object,
596 596
                 // then stick that in the array, otherwise start with an empty array
597
-                $this->_model_relations[ $relation_name ] =
598
-                    $this->_model_relations[ $relation_name ] instanceof EE_Base_Class
599
-                        ? [$this->_model_relations[ $relation_name ]]
597
+                $this->_model_relations[$relation_name] =
598
+                    $this->_model_relations[$relation_name] instanceof EE_Base_Class
599
+                        ? [$this->_model_relations[$relation_name]]
600 600
                         : [];
601 601
             }
602 602
             // first check for a cache_id which is normally empty
603
-            if (! empty($cache_id)) {
603
+            if ( ! empty($cache_id)) {
604 604
                 // if the cache_id exists, then it means we are purposely trying to cache this
605 605
                 // with a known key that can then be used to retrieve the object later on
606
-                $this->_model_relations[ $relation_name ][ $cache_id ] = $object_to_cache;
606
+                $this->_model_relations[$relation_name][$cache_id] = $object_to_cache;
607 607
                 $return                                                = $cache_id;
608 608
             } elseif ($object_to_cache->ID()) {
609 609
                 // OR the cached object originally came from the db, so let's just use it's PK for an ID
610
-                $this->_model_relations[ $relation_name ][ $object_to_cache->ID() ] = $object_to_cache;
610
+                $this->_model_relations[$relation_name][$object_to_cache->ID()] = $object_to_cache;
611 611
                 $return                                                             = $object_to_cache->ID();
612 612
             } else {
613 613
                 // OR it's a new object with no ID, so just throw it in the array with an auto-incremented ID
614
-                $this->_model_relations[ $relation_name ][] = $object_to_cache;
614
+                $this->_model_relations[$relation_name][] = $object_to_cache;
615 615
                 // move the internal pointer to the end of the array
616
-                end($this->_model_relations[ $relation_name ]);
616
+                end($this->_model_relations[$relation_name]);
617 617
                 // and grab the key so that we can return it
618
-                $return = key($this->_model_relations[ $relation_name ]);
618
+                $return = key($this->_model_relations[$relation_name]);
619 619
             }
620 620
         }
621 621
         return $return;
@@ -642,7 +642,7 @@  discard block
 block discarded – undo
642 642
         $this->get_model()->field_settings_for($fieldname);
643 643
         $cache_type = empty($cache_type) ? 'standard' : $cache_type;
644 644
 
645
-        $this->_cached_properties[ $fieldname ][ $cache_type ] = $value;
645
+        $this->_cached_properties[$fieldname][$cache_type] = $value;
646 646
     }
647 647
 
648 648
 
@@ -671,9 +671,9 @@  discard block
 block discarded – undo
671 671
         $model = $this->get_model();
672 672
         $model->field_settings_for($fieldname);
673 673
         $cache_type = $pretty ? 'pretty' : 'standard';
674
-        $cache_type .= ! empty($extra_cache_ref) ? '_' . $extra_cache_ref : '';
675
-        if (isset($this->_cached_properties[ $fieldname ][ $cache_type ])) {
676
-            return $this->_cached_properties[ $fieldname ][ $cache_type ];
674
+        $cache_type .= ! empty($extra_cache_ref) ? '_'.$extra_cache_ref : '';
675
+        if (isset($this->_cached_properties[$fieldname][$cache_type])) {
676
+            return $this->_cached_properties[$fieldname][$cache_type];
677 677
         }
678 678
         $value = $this->_get_fresh_property($fieldname, $pretty, $extra_cache_ref);
679 679
         $this->_set_cached_property($fieldname, $value, $cache_type);
@@ -701,12 +701,12 @@  discard block
 block discarded – undo
701 701
         if ($field_obj instanceof EE_Datetime_Field) {
702 702
             $this->_prepare_datetime_field($field_obj, $pretty, $extra_cache_ref);
703 703
         }
704
-        if (! isset($this->_fields[ $fieldname ])) {
705
-            $this->_fields[ $fieldname ] = null;
704
+        if ( ! isset($this->_fields[$fieldname])) {
705
+            $this->_fields[$fieldname] = null;
706 706
         }
707 707
         return $pretty
708
-            ? $field_obj->prepare_for_pretty_echoing($this->_fields[ $fieldname ], $extra_cache_ref)
709
-            : $field_obj->prepare_for_get($this->_fields[ $fieldname ]);
708
+            ? $field_obj->prepare_for_pretty_echoing($this->_fields[$fieldname], $extra_cache_ref)
709
+            : $field_obj->prepare_for_get($this->_fields[$fieldname]);
710 710
     }
711 711
 
712 712
 
@@ -762,8 +762,8 @@  discard block
 block discarded – undo
762 762
      */
763 763
     protected function _clear_cached_property($property_name)
764 764
     {
765
-        if (isset($this->_cached_properties[ $property_name ])) {
766
-            unset($this->_cached_properties[ $property_name ]);
765
+        if (isset($this->_cached_properties[$property_name])) {
766
+            unset($this->_cached_properties[$property_name]);
767 767
         }
768 768
     }
769 769
 
@@ -816,7 +816,7 @@  discard block
 block discarded – undo
816 816
     {
817 817
         $relationship_to_model = $this->get_model()->related_settings_for($relation_name);
818 818
         $index_in_cache        = '';
819
-        if (! $relationship_to_model) {
819
+        if ( ! $relationship_to_model) {
820 820
             throw new EE_Error(
821 821
                 sprintf(
822 822
                     esc_html__('There is no relationship to %s on a %s. Cannot clear that cache', 'event_espresso'),
@@ -827,10 +827,10 @@  discard block
 block discarded – undo
827 827
         }
828 828
         if ($clear_all) {
829 829
             $obj_removed                              = true;
830
-            $this->_model_relations[ $relation_name ] = null;
830
+            $this->_model_relations[$relation_name] = null;
831 831
         } elseif ($relationship_to_model instanceof EE_Belongs_To_Relation) {
832
-            $obj_removed                              = $this->_model_relations[ $relation_name ];
833
-            $this->_model_relations[ $relation_name ] = null;
832
+            $obj_removed                              = $this->_model_relations[$relation_name];
833
+            $this->_model_relations[$relation_name] = null;
834 834
         } else {
835 835
             if (
836 836
                 $object_to_remove_or_index_into_array instanceof EE_Base_Class
@@ -838,12 +838,12 @@  discard block
 block discarded – undo
838 838
             ) {
839 839
                 $index_in_cache = $object_to_remove_or_index_into_array->ID();
840 840
                 if (
841
-                    is_array($this->_model_relations[ $relation_name ])
842
-                    && ! isset($this->_model_relations[ $relation_name ][ $index_in_cache ])
841
+                    is_array($this->_model_relations[$relation_name])
842
+                    && ! isset($this->_model_relations[$relation_name][$index_in_cache])
843 843
                 ) {
844 844
                     $index_found_at = null;
845 845
                     // find this object in the array even though it has a different key
846
-                    foreach ($this->_model_relations[ $relation_name ] as $index => $obj) {
846
+                    foreach ($this->_model_relations[$relation_name] as $index => $obj) {
847 847
                         /** @noinspection TypeUnsafeComparisonInspection */
848 848
                         if (
849 849
                             $obj instanceof EE_Base_Class
@@ -877,9 +877,9 @@  discard block
 block discarded – undo
877 877
             }
878 878
             // supposedly we've found it. But it could just be that the client code
879 879
             // provided a bad index/object
880
-            if (isset($this->_model_relations[ $relation_name ][ $index_in_cache ])) {
881
-                $obj_removed = $this->_model_relations[ $relation_name ][ $index_in_cache ];
882
-                unset($this->_model_relations[ $relation_name ][ $index_in_cache ]);
880
+            if (isset($this->_model_relations[$relation_name][$index_in_cache])) {
881
+                $obj_removed = $this->_model_relations[$relation_name][$index_in_cache];
882
+                unset($this->_model_relations[$relation_name][$index_in_cache]);
883 883
             } else {
884 884
                 // that thing was never cached anyway.
885 885
                 $obj_removed = false;
@@ -910,7 +910,7 @@  discard block
 block discarded – undo
910 910
         $current_cache_id = ''
911 911
     ) {
912 912
         // verify that incoming object is of the correct type
913
-        $obj_class = 'EE_' . $relation_name;
913
+        $obj_class = 'EE_'.$relation_name;
914 914
         if ($newly_saved_object instanceof $obj_class) {
915 915
             /* @type EE_Base_Class $newly_saved_object */
916 916
             // now get the type of relation
@@ -918,18 +918,18 @@  discard block
 block discarded – undo
918 918
             // if this is a 1:1 relationship
919 919
             if ($relationship_to_model instanceof EE_Belongs_To_Relation) {
920 920
                 // then just replace the cached object with the newly saved object
921
-                $this->_model_relations[ $relation_name ] = $newly_saved_object;
921
+                $this->_model_relations[$relation_name] = $newly_saved_object;
922 922
                 return true;
923 923
                 // or if it's some kind of sordid feral polyamorous relationship...
924 924
             }
925 925
             if (
926
-                is_array($this->_model_relations[ $relation_name ])
927
-                && isset($this->_model_relations[ $relation_name ][ $current_cache_id ])
926
+                is_array($this->_model_relations[$relation_name])
927
+                && isset($this->_model_relations[$relation_name][$current_cache_id])
928 928
             ) {
929 929
                 // then remove the current cached item
930
-                unset($this->_model_relations[ $relation_name ][ $current_cache_id ]);
930
+                unset($this->_model_relations[$relation_name][$current_cache_id]);
931 931
                 // and cache the newly saved object using it's new ID
932
-                $this->_model_relations[ $relation_name ][ $newly_saved_object->ID() ] = $newly_saved_object;
932
+                $this->_model_relations[$relation_name][$newly_saved_object->ID()] = $newly_saved_object;
933 933
                 return true;
934 934
             }
935 935
         }
@@ -946,7 +946,7 @@  discard block
 block discarded – undo
946 946
      */
947 947
     public function get_one_from_cache($relation_name)
948 948
     {
949
-        $cached_array_or_object = $this->_model_relations[ $relation_name ] ?? null;
949
+        $cached_array_or_object = $this->_model_relations[$relation_name] ?? null;
950 950
         if (is_array($cached_array_or_object)) {
951 951
             return array_shift($cached_array_or_object);
952 952
         }
@@ -968,7 +968,7 @@  discard block
 block discarded – undo
968 968
      */
969 969
     public function get_all_from_cache($relation_name)
970 970
     {
971
-        $objects = $this->_model_relations[ $relation_name ] ?? [];
971
+        $objects = $this->_model_relations[$relation_name] ?? [];
972 972
         // if the result is not an array, but exists, make it an array
973 973
         $objects = is_array($objects)
974 974
             ? $objects
@@ -1160,9 +1160,9 @@  discard block
 block discarded – undo
1160 1160
     public function get_raw($field_name)
1161 1161
     {
1162 1162
         $field_settings = $this->get_model()->field_settings_for($field_name);
1163
-        return $field_settings instanceof EE_Datetime_Field && $this->_fields[ $field_name ] instanceof DateTime
1164
-            ? $this->_fields[ $field_name ]->format('U')
1165
-            : $this->_fields[ $field_name ];
1163
+        return $field_settings instanceof EE_Datetime_Field && $this->_fields[$field_name] instanceof DateTime
1164
+            ? $this->_fields[$field_name]->format('U')
1165
+            : $this->_fields[$field_name];
1166 1166
     }
1167 1167
 
1168 1168
 
@@ -1184,7 +1184,7 @@  discard block
 block discarded – undo
1184 1184
     public function get_DateTime_object($field_name)
1185 1185
     {
1186 1186
         $field_settings = $this->get_model()->field_settings_for($field_name);
1187
-        if (! $field_settings instanceof EE_Datetime_Field) {
1187
+        if ( ! $field_settings instanceof EE_Datetime_Field) {
1188 1188
             EE_Error::add_error(
1189 1189
                 sprintf(
1190 1190
                     esc_html__(
@@ -1199,8 +1199,8 @@  discard block
 block discarded – undo
1199 1199
             );
1200 1200
             return false;
1201 1201
         }
1202
-        return isset($this->_fields[ $field_name ]) && $this->_fields[ $field_name ] instanceof DateTime
1203
-            ? clone $this->_fields[ $field_name ]
1202
+        return isset($this->_fields[$field_name]) && $this->_fields[$field_name] instanceof DateTime
1203
+            ? clone $this->_fields[$field_name]
1204 1204
             : null;
1205 1205
     }
1206 1206
 
@@ -1445,7 +1445,7 @@  discard block
 block discarded – undo
1445 1445
      */
1446 1446
     public function get_i18n_datetime(string $field_name, string $format = ''): string
1447 1447
     {
1448
-        $format = empty($format) ? $this->_dt_frmt . ' ' . $this->_tm_frmt : $format;
1448
+        $format = empty($format) ? $this->_dt_frmt.' '.$this->_tm_frmt : $format;
1449 1449
         return date_i18n(
1450 1450
             $format,
1451 1451
             EEH_DTT_Helper::get_timestamp_with_offset(
@@ -1557,21 +1557,21 @@  discard block
 block discarded – undo
1557 1557
         $field->set_time_format($this->_tm_frmt);
1558 1558
         switch ($what) {
1559 1559
             case 'T':
1560
-                $this->_fields[ $field_name ] = $field->prepare_for_set_with_new_time(
1560
+                $this->_fields[$field_name] = $field->prepare_for_set_with_new_time(
1561 1561
                     $datetime_value,
1562
-                    $this->_fields[ $field_name ]
1562
+                    $this->_fields[$field_name]
1563 1563
                 );
1564 1564
                 $this->_has_changes           = true;
1565 1565
                 break;
1566 1566
             case 'D':
1567
-                $this->_fields[ $field_name ] = $field->prepare_for_set_with_new_date(
1567
+                $this->_fields[$field_name] = $field->prepare_for_set_with_new_date(
1568 1568
                     $datetime_value,
1569
-                    $this->_fields[ $field_name ]
1569
+                    $this->_fields[$field_name]
1570 1570
                 );
1571 1571
                 $this->_has_changes           = true;
1572 1572
                 break;
1573 1573
             case 'B':
1574
-                $this->_fields[ $field_name ] = $field->prepare_for_set($datetime_value);
1574
+                $this->_fields[$field_name] = $field->prepare_for_set($datetime_value);
1575 1575
                 $this->_has_changes           = true;
1576 1576
                 break;
1577 1577
         }
@@ -1614,7 +1614,7 @@  discard block
 block discarded – undo
1614 1614
         $this->set_timezone($timezone);
1615 1615
         $fn   = (array) $field_name;
1616 1616
         $args = array_merge($fn, (array) $args);
1617
-        if (! method_exists($this, $callback)) {
1617
+        if ( ! method_exists($this, $callback)) {
1618 1618
             throw new EE_Error(
1619 1619
                 sprintf(
1620 1620
                     esc_html__(
@@ -1626,7 +1626,7 @@  discard block
 block discarded – undo
1626 1626
             );
1627 1627
         }
1628 1628
         $args   = (array) $args;
1629
-        $return = $prepend . call_user_func_array([$this, $callback], $args) . $append;
1629
+        $return = $prepend.call_user_func_array([$this, $callback], $args).$append;
1630 1630
         $this->set_timezone($original_timezone);
1631 1631
         return $return;
1632 1632
     }
@@ -1741,8 +1741,8 @@  discard block
 block discarded – undo
1741 1741
     {
1742 1742
         $model = $this->get_model();
1743 1743
         foreach ($model->relation_settings() as $relation_name => $relation_obj) {
1744
-            if (! empty($this->_model_relations[ $relation_name ])) {
1745
-                $related_objects = $this->_model_relations[ $relation_name ];
1744
+            if ( ! empty($this->_model_relations[$relation_name])) {
1745
+                $related_objects = $this->_model_relations[$relation_name];
1746 1746
                 if ($relation_obj instanceof EE_Belongs_To_Relation) {
1747 1747
                     // this relation only stores a single model object, not an array
1748 1748
                     // but let's make it consistent
@@ -1799,7 +1799,7 @@  discard block
 block discarded – undo
1799 1799
             $this->set($column, $value);
1800 1800
         }
1801 1801
         // no changes ? then don't do anything
1802
-        if (! $this->_has_changes && $this->ID() && $model->get_primary_key_field()->is_auto_increment()) {
1802
+        if ( ! $this->_has_changes && $this->ID() && $model->get_primary_key_field()->is_auto_increment()) {
1803 1803
             return 0;
1804 1804
         }
1805 1805
         /**
@@ -1809,7 +1809,7 @@  discard block
 block discarded – undo
1809 1809
          * @param EE_Base_Class $model_object the model object about to be saved.
1810 1810
          */
1811 1811
         do_action('AHEE__EE_Base_Class__save__begin', $this);
1812
-        if (! $this->allow_persist()) {
1812
+        if ( ! $this->allow_persist()) {
1813 1813
             return 0;
1814 1814
         }
1815 1815
         // now get current attribute values
@@ -1824,10 +1824,10 @@  discard block
 block discarded – undo
1824 1824
         if ($model->has_primary_key_field()) {
1825 1825
             if ($model->get_primary_key_field()->is_auto_increment()) {
1826 1826
                 // ok check if it's set, if so: update; if not, insert
1827
-                if (! empty($save_cols_n_values[ $model->primary_key_name() ])) {
1827
+                if ( ! empty($save_cols_n_values[$model->primary_key_name()])) {
1828 1828
                     $results = $model->update_by_ID($save_cols_n_values, $this->ID());
1829 1829
                 } else {
1830
-                    unset($save_cols_n_values[ $model->primary_key_name() ]);
1830
+                    unset($save_cols_n_values[$model->primary_key_name()]);
1831 1831
                     $results = $model->insert($save_cols_n_values);
1832 1832
                     if ($results) {
1833 1833
                         // if successful, set the primary key
@@ -1837,7 +1837,7 @@  discard block
 block discarded – undo
1837 1837
                         // will get added to the mapper before we can add this one!
1838 1838
                         // but if we just avoid using the SET method, all that headache can be avoided
1839 1839
                         $pk_field_name                   = $model->primary_key_name();
1840
-                        $this->_fields[ $pk_field_name ] = $results;
1840
+                        $this->_fields[$pk_field_name] = $results;
1841 1841
                         $this->_clear_cached_property($pk_field_name);
1842 1842
                         $model->add_to_entity_map($this);
1843 1843
                         $this->_update_cached_related_model_objs_fks();
@@ -1854,8 +1854,8 @@  discard block
 block discarded – undo
1854 1854
                                     'event_espresso'
1855 1855
                                 ),
1856 1856
                                 get_class($this),
1857
-                                get_class($model) . '::instance()->add_to_entity_map()',
1858
-                                get_class($model) . '::instance()->get_one_by_ID()',
1857
+                                get_class($model).'::instance()->add_to_entity_map()',
1858
+                                get_class($model).'::instance()->get_one_by_ID()',
1859 1859
                                 '<br />'
1860 1860
                             )
1861 1861
                         );
@@ -1879,7 +1879,7 @@  discard block
 block discarded – undo
1879 1879
                     $save_cols_n_values,
1880 1880
                     $model->get_combined_primary_key_fields()
1881 1881
                 );
1882
-                $results                     = $model->update(
1882
+                $results = $model->update(
1883 1883
                     $save_cols_n_values,
1884 1884
                     $combined_pk_fields_n_values
1885 1885
                 );
@@ -1957,27 +1957,27 @@  discard block
 block discarded – undo
1957 1957
     public function save_new_cached_related_model_objs()
1958 1958
     {
1959 1959
         // make sure this has been saved
1960
-        if (! $this->ID()) {
1960
+        if ( ! $this->ID()) {
1961 1961
             $id = $this->save();
1962 1962
         } else {
1963 1963
             $id = $this->ID();
1964 1964
         }
1965 1965
         // now save all the NEW cached model objects  (ie they don't exist in the DB)
1966 1966
         foreach ($this->get_model()->relation_settings() as $relation_name => $relationObj) {
1967
-            if ($this->_model_relations[ $relation_name ]) {
1967
+            if ($this->_model_relations[$relation_name]) {
1968 1968
                 // is this a relation where we should expect just ONE related object (ie, EE_Belongs_To_relation)
1969 1969
                 // or MANY related objects (ie, EE_HABTM_Relation or EE_Has_Many_Relation)?
1970 1970
                 /* @var $related_model_obj EE_Base_Class */
1971 1971
                 if ($relationObj instanceof EE_Belongs_To_Relation) {
1972 1972
                     // add a relation to that relation type (which saves the appropriate thing in the process)
1973 1973
                     // but ONLY if it DOES NOT exist in the DB
1974
-                    $related_model_obj = $this->_model_relations[ $relation_name ];
1974
+                    $related_model_obj = $this->_model_relations[$relation_name];
1975 1975
                     // if( ! $related_model_obj->ID()){
1976 1976
                     $this->_add_relation_to($related_model_obj, $relation_name);
1977 1977
                     $related_model_obj->save_new_cached_related_model_objs();
1978 1978
                     // }
1979 1979
                 } else {
1980
-                    foreach ($this->_model_relations[ $relation_name ] as $related_model_obj) {
1980
+                    foreach ($this->_model_relations[$relation_name] as $related_model_obj) {
1981 1981
                         // add a relation to that relation type (which saves the appropriate thing in the process)
1982 1982
                         // but ONLY if it DOES NOT exist in the DB
1983 1983
                         // if( ! $related_model_obj->ID()){
@@ -2004,7 +2004,7 @@  discard block
 block discarded – undo
2004 2004
      */
2005 2005
     public function get_model()
2006 2006
     {
2007
-        if (! $this->_model) {
2007
+        if ( ! $this->_model) {
2008 2008
             $modelName    = self::_get_model_classname(get_class($this));
2009 2009
             $this->_model = self::_get_model_instance_with_name($modelName, $this->_timezone);
2010 2010
         } else {
@@ -2030,9 +2030,9 @@  discard block
 block discarded – undo
2030 2030
         $primary_id_ref = self::_get_primary_key_name($classname);
2031 2031
         if (
2032 2032
             array_key_exists($primary_id_ref, $props_n_values)
2033
-            && ! empty($props_n_values[ $primary_id_ref ])
2033
+            && ! empty($props_n_values[$primary_id_ref])
2034 2034
         ) {
2035
-            $id = $props_n_values[ $primary_id_ref ];
2035
+            $id = $props_n_values[$primary_id_ref];
2036 2036
             return self::_get_model($classname)->get_from_entity_map($id);
2037 2037
         }
2038 2038
         return false;
@@ -2065,10 +2065,10 @@  discard block
 block discarded – undo
2065 2065
             $primary_id_ref = self::_get_primary_key_name($classname);
2066 2066
             if (
2067 2067
                 array_key_exists($primary_id_ref, $props_n_values)
2068
-                && ! empty($props_n_values[ $primary_id_ref ])
2068
+                && ! empty($props_n_values[$primary_id_ref])
2069 2069
             ) {
2070 2070
                 $existing = $model->get_one_by_ID(
2071
-                    $props_n_values[ $primary_id_ref ]
2071
+                    $props_n_values[$primary_id_ref]
2072 2072
                 );
2073 2073
             }
2074 2074
         } elseif ($model->has_all_combined_primary_key_fields($props_n_values)) {
@@ -2080,7 +2080,7 @@  discard block
 block discarded – undo
2080 2080
         }
2081 2081
         if ($existing) {
2082 2082
             // set date formats if present before setting values
2083
-            if (! empty($date_formats) && is_array($date_formats)) {
2083
+            if ( ! empty($date_formats) && is_array($date_formats)) {
2084 2084
                 $existing->set_date_format($date_formats[0]);
2085 2085
                 $existing->set_time_format($date_formats[1]);
2086 2086
             } else {
@@ -2113,7 +2113,7 @@  discard block
 block discarded – undo
2113 2113
     protected static function _get_model($classname, $timezone = '')
2114 2114
     {
2115 2115
         // find model for this class
2116
-        if (! $classname) {
2116
+        if ( ! $classname) {
2117 2117
             throw new EE_Error(
2118 2118
                 sprintf(
2119 2119
                     esc_html__(
@@ -2161,7 +2161,7 @@  discard block
 block discarded – undo
2161 2161
     {
2162 2162
         return strpos((string) $model_name, 'EE_') === 0
2163 2163
             ? str_replace('EE_', 'EEM_', $model_name)
2164
-            : 'EEM_' . $model_name;
2164
+            : 'EEM_'.$model_name;
2165 2165
     }
2166 2166
 
2167 2167
 
@@ -2178,7 +2178,7 @@  discard block
 block discarded – undo
2178 2178
      */
2179 2179
     protected static function _get_primary_key_name($classname = null)
2180 2180
     {
2181
-        if (! $classname) {
2181
+        if ( ! $classname) {
2182 2182
             throw new EE_Error(
2183 2183
                 sprintf(
2184 2184
                     esc_html__('What were you thinking calling _get_primary_key_name(%s)', 'event_espresso'),
@@ -2208,7 +2208,7 @@  discard block
 block discarded – undo
2208 2208
         $model = $this->get_model();
2209 2209
         // now that we know the name of the variable, use a variable variable to get its value and return its
2210 2210
         if ($model->has_primary_key_field()) {
2211
-            return $this->_fields[ $model->primary_key_name() ];
2211
+            return $this->_fields[$model->primary_key_name()];
2212 2212
         }
2213 2213
         return $model->get_index_primary_key_string($this->_fields);
2214 2214
     }
@@ -2282,7 +2282,7 @@  discard block
 block discarded – undo
2282 2282
             }
2283 2283
         } else {
2284 2284
             // this thing doesn't exist in the DB,  so just cache it
2285
-            if (! $otherObjectModelObjectOrID instanceof EE_Base_Class) {
2285
+            if ( ! $otherObjectModelObjectOrID instanceof EE_Base_Class) {
2286 2286
                 throw new EE_Error(
2287 2287
                     sprintf(
2288 2288
                         esc_html__(
@@ -2451,7 +2451,7 @@  discard block
 block discarded – undo
2451 2451
             } else {
2452 2452
                 // did we already cache the result of this query?
2453 2453
                 $cached_results = $this->get_all_from_cache($relation_name);
2454
-                if (! $cached_results) {
2454
+                if ( ! $cached_results) {
2455 2455
                     $related_model_objects = $this->get_model()->get_all_related(
2456 2456
                         $this,
2457 2457
                         $relation_name,
@@ -2563,7 +2563,7 @@  discard block
 block discarded – undo
2563 2563
             } else {
2564 2564
                 // first, check if we've already cached the result of this query
2565 2565
                 $cached_result = $this->get_one_from_cache($relation_name);
2566
-                if (! $cached_result) {
2566
+                if ( ! $cached_result) {
2567 2567
                     $related_model_object = $model->get_first_related(
2568 2568
                         $this,
2569 2569
                         $relation_name,
@@ -2587,7 +2587,7 @@  discard block
 block discarded – undo
2587 2587
             }
2588 2588
             // this doesn't exist in the DB and apparently the thing it belongs to doesn't either,
2589 2589
             // just get what's cached on this object
2590
-            if (! $related_model_object) {
2590
+            if ( ! $related_model_object) {
2591 2591
                 $related_model_object = $this->get_one_from_cache($relation_name);
2592 2592
             }
2593 2593
         }
@@ -2670,7 +2670,7 @@  discard block
 block discarded – undo
2670 2670
      */
2671 2671
     public function is_set($field_name)
2672 2672
     {
2673
-        return isset($this->_fields[ $field_name ]);
2673
+        return isset($this->_fields[$field_name]);
2674 2674
     }
2675 2675
 
2676 2676
 
@@ -2686,7 +2686,7 @@  discard block
 block discarded – undo
2686 2686
     {
2687 2687
         foreach ((array) $properties as $property_name) {
2688 2688
             // first make sure this property exists
2689
-            if (! $this->_fields[ $property_name ]) {
2689
+            if ( ! $this->_fields[$property_name]) {
2690 2690
                 throw new EE_Error(
2691 2691
                     sprintf(
2692 2692
                         esc_html__(
@@ -2718,7 +2718,7 @@  discard block
 block discarded – undo
2718 2718
         $properties = [];
2719 2719
         // remove prepended underscore
2720 2720
         foreach ($fields as $field_name => $settings) {
2721
-            $properties[ $field_name ] = $this->get($field_name);
2721
+            $properties[$field_name] = $this->get($field_name);
2722 2722
         }
2723 2723
         return $properties;
2724 2724
     }
@@ -2755,7 +2755,7 @@  discard block
 block discarded – undo
2755 2755
     {
2756 2756
         $className = get_class($this);
2757 2757
         $tagName   = "FHEE__{$className}__{$methodName}";
2758
-        if (! has_filter($tagName)) {
2758
+        if ( ! has_filter($tagName)) {
2759 2759
             throw new EE_Error(
2760 2760
                 sprintf(
2761 2761
                     esc_html__(
@@ -2800,7 +2800,7 @@  discard block
 block discarded – undo
2800 2800
             $query_params[0]['EXM_value'] = $previous_value;
2801 2801
         }
2802 2802
         $existing_rows_like_that = EEM_Extra_Meta::instance()->get_all($query_params);
2803
-        if (! $existing_rows_like_that) {
2803
+        if ( ! $existing_rows_like_that) {
2804 2804
             return $this->add_extra_meta($meta_key, $meta_value);
2805 2805
         }
2806 2806
         foreach ($existing_rows_like_that as $existing_row) {
@@ -2910,7 +2910,7 @@  discard block
 block discarded – undo
2910 2910
                 $values = [];
2911 2911
                 foreach ($results as $result) {
2912 2912
                     if ($result instanceof EE_Extra_Meta) {
2913
-                        $values[ $result->ID() ] = $result->value();
2913
+                        $values[$result->ID()] = $result->value();
2914 2914
                     }
2915 2915
                 }
2916 2916
                 return $values;
@@ -2955,17 +2955,17 @@  discard block
 block discarded – undo
2955 2955
             );
2956 2956
             foreach ($extra_meta_objs as $extra_meta_obj) {
2957 2957
                 if ($extra_meta_obj instanceof EE_Extra_Meta) {
2958
-                    $return_array[ $extra_meta_obj->key() ] = $extra_meta_obj->value();
2958
+                    $return_array[$extra_meta_obj->key()] = $extra_meta_obj->value();
2959 2959
                 }
2960 2960
             }
2961 2961
         } else {
2962 2962
             $extra_meta_objs = $this->get_many_related('Extra_Meta');
2963 2963
             foreach ($extra_meta_objs as $extra_meta_obj) {
2964 2964
                 if ($extra_meta_obj instanceof EE_Extra_Meta) {
2965
-                    if (! isset($return_array[ $extra_meta_obj->key() ])) {
2966
-                        $return_array[ $extra_meta_obj->key() ] = [];
2965
+                    if ( ! isset($return_array[$extra_meta_obj->key()])) {
2966
+                        $return_array[$extra_meta_obj->key()] = [];
2967 2967
                     }
2968
-                    $return_array[ $extra_meta_obj->key() ][ $extra_meta_obj->ID() ] = $extra_meta_obj->value();
2968
+                    $return_array[$extra_meta_obj->key()][$extra_meta_obj->ID()] = $extra_meta_obj->value();
2969 2969
                 }
2970 2970
             }
2971 2971
         }
@@ -3046,8 +3046,8 @@  discard block
 block discarded – undo
3046 3046
                             'event_espresso'
3047 3047
                         ),
3048 3048
                         $this->ID(),
3049
-                        get_class($this->get_model()) . '::instance()->add_to_entity_map()',
3050
-                        get_class($this->get_model()) . '::instance()->refresh_entity_map()'
3049
+                        get_class($this->get_model()).'::instance()->add_to_entity_map()',
3050
+                        get_class($this->get_model()).'::instance()->refresh_entity_map()'
3051 3051
                     )
3052 3052
                 );
3053 3053
             }
@@ -3080,7 +3080,7 @@  discard block
 block discarded – undo
3080 3080
     {
3081 3081
         // First make sure this model object actually exists in the DB. It would be silly to try to update it in the DB
3082 3082
         // if it wasn't even there to start off.
3083
-        if (! $this->ID()) {
3083
+        if ( ! $this->ID()) {
3084 3084
             $this->save();
3085 3085
         }
3086 3086
         global $wpdb;
@@ -3120,7 +3120,7 @@  discard block
 block discarded – undo
3120 3120
             $table_pk_field_name = $table_obj->get_pk_column();
3121 3121
         }
3122 3122
 
3123
-        $query  =
3123
+        $query =
3124 3124
             "UPDATE `{$table_name}`
3125 3125
             SET "
3126 3126
             . $new_value_sql
@@ -3314,7 +3314,7 @@  discard block
 block discarded – undo
3314 3314
         $model = $this->get_model();
3315 3315
         foreach ($model->relation_settings() as $relation_name => $relation_obj) {
3316 3316
             if ($relation_obj instanceof EE_Belongs_To_Relation) {
3317
-                $classname = 'EE_' . $model->get_this_model_name();
3317
+                $classname = 'EE_'.$model->get_this_model_name();
3318 3318
                 if (
3319 3319
                     $this->get_one_from_cache($relation_name) instanceof $classname
3320 3320
                     && $this->get_one_from_cache($relation_name)->ID()
@@ -3355,7 +3355,7 @@  discard block
 block discarded – undo
3355 3355
         // handle DateTimes (this is handled in here because there's no one specific child class that uses datetimes).
3356 3356
         foreach ($this->_fields as $field => $value) {
3357 3357
             if ($value instanceof DateTime) {
3358
-                $this->_fields[ $field ] = clone $value;
3358
+                $this->_fields[$field] = clone $value;
3359 3359
             }
3360 3360
         }
3361 3361
     }
@@ -3370,7 +3370,7 @@  discard block
 block discarded – undo
3370 3370
 
3371 3371
     private function echoProperty($field, $value, int $indent = 0)
3372 3372
     {
3373
-        $bullets = str_repeat(' -', $indent) . ' ';
3373
+        $bullets = str_repeat(' -', $indent).' ';
3374 3374
         $field = strpos($field, '_') === 0 ? substr($field, 1) : $field;
3375 3375
         echo "\n$bullets$field: ";
3376 3376
         if ($value instanceof EEM_Base) {
Please login to merge, or discard this patch.
core/services/request/CurrentPage.php 2 patches
Indentation   +295 added lines, -295 removed lines patch added patch discarded remove patch
@@ -22,299 +22,299 @@
 block discarded – undo
22 22
  */
23 23
 class CurrentPage
24 24
 {
25
-    private EE_CPT_Strategy $cpt_strategy;
26
-
27
-    private bool $initialized;
28
-
29
-    private ?bool $is_espresso_page = null;
30
-
31
-    private int $post_id          = 0;
32
-
33
-    private string $post_name        = '';
34
-
35
-    private array $post_type        = [];
36
-
37
-    private RequestInterface $request;
38
-
39
-
40
-    /**
41
-     * CurrentPage constructor.
42
-     *
43
-     * @param EE_CPT_Strategy  $cpt_strategy
44
-     * @param RequestInterface $request
45
-     */
46
-    public function __construct(EE_CPT_Strategy $cpt_strategy, RequestInterface $request)
47
-    {
48
-        $this->cpt_strategy = $cpt_strategy;
49
-        $this->request      = $request;
50
-        $this->initialized  = is_admin();
51
-        // analyse the incoming WP request
52
-        add_action('parse_request', [$this, 'parseQueryVars'], 2);
53
-    }
54
-
55
-
56
-    /**
57
-     * @param WP|null $WP
58
-     * @return void
59
-     */
60
-    public function parseQueryVars(WP $WP = null)
61
-    {
62
-        if ($this->initialized) {
63
-            return;
64
-        }
65
-        // if somebody forgot to provide us with WP, that's ok because its global
66
-        if (! $WP instanceof WP) {
67
-            global $WP;
68
-        }
69
-        $this->post_id   = $this->getPostId($WP);
70
-        $this->post_name = $this->getPostName($WP);
71
-        $this->post_type = $this->getPostType($WP);
72
-        // true or false ? is this page being used by EE ?
73
-        $this->setEspressoPage();
74
-        remove_action('parse_request', [$this, 'parseQueryVars'], 2);
75
-        $this->initialized = true;
76
-    }
77
-
78
-
79
-    /**
80
-     * Just a helper method for getting the url for the displayed page.
81
-     *
82
-     * @param WP|null $WP
83
-     * @return string
84
-     */
85
-    public function getPermalink(WP $WP = null): string
86
-    {
87
-        $post_id = $this->post_id ?: $this->getPostId($WP);
88
-        if ($post_id) {
89
-            return get_permalink($post_id);
90
-        }
91
-        if (! $WP instanceof WP) {
92
-            global $WP;
93
-        }
94
-        if ($WP instanceof WP && $WP->request) {
95
-            return site_url($WP->request);
96
-        }
97
-        return esc_url_raw(site_url($_SERVER['REQUEST_URI']));
98
-    }
99
-
100
-
101
-    /**
102
-     * @return array
103
-     */
104
-    public function espressoPostType(): array
105
-    {
106
-        return array_filter(
107
-            $this->post_type,
108
-            function ($post_type) {
109
-                return strpos($post_type, 'espresso_') === 0;
110
-            }
111
-        );
112
-    }
113
-
114
-
115
-    /**
116
-     * pokes and prods the WP object query_vars in an attempt to shake out a page/post ID
117
-     *
118
-     * @param WP|null $WP $WP
119
-     * @return int
120
-     */
121
-    private function getPostId(WP $WP = null): ?int
122
-    {
123
-        $post_id = 0;
124
-        if ($WP instanceof WP) {
125
-            // look for the post ID in the aptly named 'p' query var
126
-            if (isset($WP->query_vars['p'])) {
127
-                $post_id = $WP->query_vars['p'];
128
-            }
129
-            // not a post? what about a page?
130
-            if (! $post_id && isset($WP->query_vars['page_id'])) {
131
-                $post_id = $WP->query_vars['page_id'];
132
-            }
133
-            // ok... maybe pretty permalinks are off and the ID is set in the raw request...
134
-            // but hasn't been processed yet ie: this method is being called too early :\
135
-            if (! $post_id && $WP->request !== null && is_numeric(basename($WP->request))) {
136
-                $post_id = basename($WP->request);
137
-            }
138
-        }
139
-        // none of the above? ok what about an explicit "post_id" URL parameter?
140
-        if (! $post_id && $this->request->requestParamIsSet('post_id')) {
141
-            $post_id = $this->request->getRequestParam('post_id', 0, DataType::INT);
142
-        }
143
-        return (int) $post_id;
144
-    }
145
-
146
-
147
-    /**
148
-     * similar to getPostId() above but attempts to obtain the "name" for the current page/post
149
-     *
150
-     * @param WP|null $WP $WP
151
-     * @return string|null
152
-     */
153
-    private function getPostName(WP $WP = null): ?string
154
-    {
155
-        global $wpdb;
156
-        $post_name = '';
157
-        if ($WP instanceof WP) {
158
-            // if this is a post, then is the post name set?
159
-            if (isset($WP->query_vars['name']) && ! empty($WP->query_vars['name'])) {
160
-                $post_name = is_array($WP->query_vars['name']) ? $WP->query_vars['name'][0] : $WP->query_vars['name'];
161
-            }
162
-            // what about the page name?
163
-            if (! $post_name && isset($WP->query_vars['pagename']) && ! empty($WP->query_vars['pagename'])) {
164
-                $post_name = is_array($WP->query_vars['pagename']) ? $WP->query_vars['pagename'][0]
165
-                    : $WP->query_vars['pagename'];
166
-            }
167
-            // this stinks but let's run a query to try and get the post name from the URL
168
-            // (assuming pretty permalinks are on)
169
-            if (! $post_name && ! empty($WP->request)) {
170
-                $possible_post_name = basename($WP->request);
171
-                if (! is_numeric($possible_post_name)) {
172
-                    $SQL                = "SELECT ID from $wpdb->posts";
173
-                    $SQL                .= " WHERE post_status NOT IN ('auto-draft', 'inherit', 'trash')";
174
-                    $SQL                .= ' AND post_name=%s';
175
-                    $possible_post_name = $wpdb->get_var($wpdb->prepare($SQL, $possible_post_name));
176
-                    if ($possible_post_name) {
177
-                        $post_name = $possible_post_name;
178
-                    }
179
-                }
180
-            }
181
-        }
182
-        // ug... ok... nothing yet... but do we have a post ID?
183
-        // if so then... sigh... run a query to get the post name :\
184
-        if (! $post_name && $this->post_id) {
185
-            $SQL                = "SELECT post_name from $wpdb->posts";
186
-            $SQL                .= " WHERE post_status NOT IN ('auto-draft', 'inherit', 'trash')";
187
-            $SQL                .= ' AND ID=%d';
188
-            $possible_post_name = $wpdb->get_var($wpdb->prepare($SQL, $this->post_id));
189
-            if ($possible_post_name) {
190
-                $post_name = $possible_post_name;
191
-            }
192
-        }
193
-        // still nothing? ok what about an explicit 'post_name' URL parameter?
194
-        if (! $post_name && $this->request->requestParamIsSet('post_name')) {
195
-            $post_name = $this->request->getRequestParam('post_name');
196
-        }
197
-        return $post_name ?? '';
198
-    }
199
-
200
-
201
-    /**
202
-     * also similar to getPostId() and getPostName() above but not as insane
203
-     *
204
-     * @param WP|null $WP $WP
205
-     * @return array
206
-     */
207
-    private function getPostType(WP $WP = null): array
208
-    {
209
-        $post_types = [];
210
-        if ($WP instanceof WP) {
211
-            $post_types = isset($WP->query_vars['post_type'])
212
-                ? (array) $WP->query_vars['post_type']
213
-                : [];
214
-        }
215
-        if (empty($post_types) && $this->request->requestParamIsSet('post_type')) {
216
-            $post_types = $this->request->getRequestParam('post_type', [], DataType::STRING, true);
217
-        }
218
-        return (array) $post_types;
219
-    }
220
-
221
-
222
-    /**
223
-     * if TRUE, then the current page is somehow utilizing EE logic
224
-     *
225
-     * @return bool
226
-     */
227
-    public function isEspressoPage(): bool
228
-    {
229
-        if ($this->is_espresso_page === null) {
230
-            $this->setEspressoPage();
231
-        }
232
-        return $this->is_espresso_page;
233
-    }
234
-
235
-
236
-    /**
237
-     * @return int
238
-     */
239
-    public function postId(): int
240
-    {
241
-        return $this->post_id;
242
-    }
243
-
244
-
245
-    /**
246
-     * @return string|null
247
-     */
248
-    public function postName(): ?string
249
-    {
250
-        return $this->post_name;
251
-    }
252
-
253
-
254
-    /**
255
-     * @return array
256
-     */
257
-    public function postType(): array
258
-    {
259
-        return $this->post_type;
260
-    }
261
-
262
-
263
-    /**
264
-     * for manually indicating the current page will utilize EE logic
265
-     *
266
-     * @param bool|int|string|null $value
267
-     * @return void
268
-     */
269
-    public function setEspressoPage($value = null)
270
-    {
271
-        $this->is_espresso_page = $value !== null
272
-            ? filter_var($value, FILTER_VALIDATE_BOOLEAN)
273
-            : $this->testForEspressoPage();
274
-    }
275
-
276
-
277
-    /**
278
-     * attempts to determine if the current page/post is an EE related page/post
279
-     * because it utilizes one of our CPT taxonomies, endpoints, or post types
280
-     *
281
-     * @return bool
282
-     */
283
-    private function testForEspressoPage(): bool
284
-    {
285
-        // in case it has already been set
286
-        if ($this->is_espresso_page) {
287
-            return true;
288
-        }
289
-        global $WP;
290
-        $espresso_CPT_taxonomies = $this->cpt_strategy->get_CPT_taxonomies();
291
-        if (is_array($espresso_CPT_taxonomies)) {
292
-            foreach ($espresso_CPT_taxonomies as $espresso_CPT_taxonomy => $details) {
293
-                if (isset($WP->query_vars, $WP->query_vars[ $espresso_CPT_taxonomy ])) {
294
-                    return true;
295
-                }
296
-            }
297
-        }
298
-        // load espresso CPT endpoints
299
-        $espresso_CPT_endpoints  = $this->cpt_strategy->get_CPT_endpoints();
300
-        $post_type_CPT_endpoints = array_flip($espresso_CPT_endpoints);
301
-        foreach ($this->post_type as $post_type) {
302
-            // was a post name passed ?
303
-            if (isset($post_type_CPT_endpoints[ $post_type ])) {
304
-                // kk we know this is an espresso page, but is it a specific post ?
305
-                if (! $this->post_name) {
306
-                    $espresso_post_type = $this->request->getRequestParam('post_type');
307
-                    // there's no specific post name set, so maybe it's one of our endpoints like www.domain.com/events
308
-                    // this essentially sets the post_name to "events" (or whatever EE CPT)
309
-                    $post_name = $post_type_CPT_endpoints[ $espresso_post_type ] ?? '';
310
-                    // if the post type matches one of ours then set the post name to the endpoint
311
-                    if ($post_name) {
312
-                        $this->post_name = $post_name;
313
-                    }
314
-                }
315
-                return true;
316
-            }
317
-        }
318
-        return false;
319
-    }
25
+	private EE_CPT_Strategy $cpt_strategy;
26
+
27
+	private bool $initialized;
28
+
29
+	private ?bool $is_espresso_page = null;
30
+
31
+	private int $post_id          = 0;
32
+
33
+	private string $post_name        = '';
34
+
35
+	private array $post_type        = [];
36
+
37
+	private RequestInterface $request;
38
+
39
+
40
+	/**
41
+	 * CurrentPage constructor.
42
+	 *
43
+	 * @param EE_CPT_Strategy  $cpt_strategy
44
+	 * @param RequestInterface $request
45
+	 */
46
+	public function __construct(EE_CPT_Strategy $cpt_strategy, RequestInterface $request)
47
+	{
48
+		$this->cpt_strategy = $cpt_strategy;
49
+		$this->request      = $request;
50
+		$this->initialized  = is_admin();
51
+		// analyse the incoming WP request
52
+		add_action('parse_request', [$this, 'parseQueryVars'], 2);
53
+	}
54
+
55
+
56
+	/**
57
+	 * @param WP|null $WP
58
+	 * @return void
59
+	 */
60
+	public function parseQueryVars(WP $WP = null)
61
+	{
62
+		if ($this->initialized) {
63
+			return;
64
+		}
65
+		// if somebody forgot to provide us with WP, that's ok because its global
66
+		if (! $WP instanceof WP) {
67
+			global $WP;
68
+		}
69
+		$this->post_id   = $this->getPostId($WP);
70
+		$this->post_name = $this->getPostName($WP);
71
+		$this->post_type = $this->getPostType($WP);
72
+		// true or false ? is this page being used by EE ?
73
+		$this->setEspressoPage();
74
+		remove_action('parse_request', [$this, 'parseQueryVars'], 2);
75
+		$this->initialized = true;
76
+	}
77
+
78
+
79
+	/**
80
+	 * Just a helper method for getting the url for the displayed page.
81
+	 *
82
+	 * @param WP|null $WP
83
+	 * @return string
84
+	 */
85
+	public function getPermalink(WP $WP = null): string
86
+	{
87
+		$post_id = $this->post_id ?: $this->getPostId($WP);
88
+		if ($post_id) {
89
+			return get_permalink($post_id);
90
+		}
91
+		if (! $WP instanceof WP) {
92
+			global $WP;
93
+		}
94
+		if ($WP instanceof WP && $WP->request) {
95
+			return site_url($WP->request);
96
+		}
97
+		return esc_url_raw(site_url($_SERVER['REQUEST_URI']));
98
+	}
99
+
100
+
101
+	/**
102
+	 * @return array
103
+	 */
104
+	public function espressoPostType(): array
105
+	{
106
+		return array_filter(
107
+			$this->post_type,
108
+			function ($post_type) {
109
+				return strpos($post_type, 'espresso_') === 0;
110
+			}
111
+		);
112
+	}
113
+
114
+
115
+	/**
116
+	 * pokes and prods the WP object query_vars in an attempt to shake out a page/post ID
117
+	 *
118
+	 * @param WP|null $WP $WP
119
+	 * @return int
120
+	 */
121
+	private function getPostId(WP $WP = null): ?int
122
+	{
123
+		$post_id = 0;
124
+		if ($WP instanceof WP) {
125
+			// look for the post ID in the aptly named 'p' query var
126
+			if (isset($WP->query_vars['p'])) {
127
+				$post_id = $WP->query_vars['p'];
128
+			}
129
+			// not a post? what about a page?
130
+			if (! $post_id && isset($WP->query_vars['page_id'])) {
131
+				$post_id = $WP->query_vars['page_id'];
132
+			}
133
+			// ok... maybe pretty permalinks are off and the ID is set in the raw request...
134
+			// but hasn't been processed yet ie: this method is being called too early :\
135
+			if (! $post_id && $WP->request !== null && is_numeric(basename($WP->request))) {
136
+				$post_id = basename($WP->request);
137
+			}
138
+		}
139
+		// none of the above? ok what about an explicit "post_id" URL parameter?
140
+		if (! $post_id && $this->request->requestParamIsSet('post_id')) {
141
+			$post_id = $this->request->getRequestParam('post_id', 0, DataType::INT);
142
+		}
143
+		return (int) $post_id;
144
+	}
145
+
146
+
147
+	/**
148
+	 * similar to getPostId() above but attempts to obtain the "name" for the current page/post
149
+	 *
150
+	 * @param WP|null $WP $WP
151
+	 * @return string|null
152
+	 */
153
+	private function getPostName(WP $WP = null): ?string
154
+	{
155
+		global $wpdb;
156
+		$post_name = '';
157
+		if ($WP instanceof WP) {
158
+			// if this is a post, then is the post name set?
159
+			if (isset($WP->query_vars['name']) && ! empty($WP->query_vars['name'])) {
160
+				$post_name = is_array($WP->query_vars['name']) ? $WP->query_vars['name'][0] : $WP->query_vars['name'];
161
+			}
162
+			// what about the page name?
163
+			if (! $post_name && isset($WP->query_vars['pagename']) && ! empty($WP->query_vars['pagename'])) {
164
+				$post_name = is_array($WP->query_vars['pagename']) ? $WP->query_vars['pagename'][0]
165
+					: $WP->query_vars['pagename'];
166
+			}
167
+			// this stinks but let's run a query to try and get the post name from the URL
168
+			// (assuming pretty permalinks are on)
169
+			if (! $post_name && ! empty($WP->request)) {
170
+				$possible_post_name = basename($WP->request);
171
+				if (! is_numeric($possible_post_name)) {
172
+					$SQL                = "SELECT ID from $wpdb->posts";
173
+					$SQL                .= " WHERE post_status NOT IN ('auto-draft', 'inherit', 'trash')";
174
+					$SQL                .= ' AND post_name=%s';
175
+					$possible_post_name = $wpdb->get_var($wpdb->prepare($SQL, $possible_post_name));
176
+					if ($possible_post_name) {
177
+						$post_name = $possible_post_name;
178
+					}
179
+				}
180
+			}
181
+		}
182
+		// ug... ok... nothing yet... but do we have a post ID?
183
+		// if so then... sigh... run a query to get the post name :\
184
+		if (! $post_name && $this->post_id) {
185
+			$SQL                = "SELECT post_name from $wpdb->posts";
186
+			$SQL                .= " WHERE post_status NOT IN ('auto-draft', 'inherit', 'trash')";
187
+			$SQL                .= ' AND ID=%d';
188
+			$possible_post_name = $wpdb->get_var($wpdb->prepare($SQL, $this->post_id));
189
+			if ($possible_post_name) {
190
+				$post_name = $possible_post_name;
191
+			}
192
+		}
193
+		// still nothing? ok what about an explicit 'post_name' URL parameter?
194
+		if (! $post_name && $this->request->requestParamIsSet('post_name')) {
195
+			$post_name = $this->request->getRequestParam('post_name');
196
+		}
197
+		return $post_name ?? '';
198
+	}
199
+
200
+
201
+	/**
202
+	 * also similar to getPostId() and getPostName() above but not as insane
203
+	 *
204
+	 * @param WP|null $WP $WP
205
+	 * @return array
206
+	 */
207
+	private function getPostType(WP $WP = null): array
208
+	{
209
+		$post_types = [];
210
+		if ($WP instanceof WP) {
211
+			$post_types = isset($WP->query_vars['post_type'])
212
+				? (array) $WP->query_vars['post_type']
213
+				: [];
214
+		}
215
+		if (empty($post_types) && $this->request->requestParamIsSet('post_type')) {
216
+			$post_types = $this->request->getRequestParam('post_type', [], DataType::STRING, true);
217
+		}
218
+		return (array) $post_types;
219
+	}
220
+
221
+
222
+	/**
223
+	 * if TRUE, then the current page is somehow utilizing EE logic
224
+	 *
225
+	 * @return bool
226
+	 */
227
+	public function isEspressoPage(): bool
228
+	{
229
+		if ($this->is_espresso_page === null) {
230
+			$this->setEspressoPage();
231
+		}
232
+		return $this->is_espresso_page;
233
+	}
234
+
235
+
236
+	/**
237
+	 * @return int
238
+	 */
239
+	public function postId(): int
240
+	{
241
+		return $this->post_id;
242
+	}
243
+
244
+
245
+	/**
246
+	 * @return string|null
247
+	 */
248
+	public function postName(): ?string
249
+	{
250
+		return $this->post_name;
251
+	}
252
+
253
+
254
+	/**
255
+	 * @return array
256
+	 */
257
+	public function postType(): array
258
+	{
259
+		return $this->post_type;
260
+	}
261
+
262
+
263
+	/**
264
+	 * for manually indicating the current page will utilize EE logic
265
+	 *
266
+	 * @param bool|int|string|null $value
267
+	 * @return void
268
+	 */
269
+	public function setEspressoPage($value = null)
270
+	{
271
+		$this->is_espresso_page = $value !== null
272
+			? filter_var($value, FILTER_VALIDATE_BOOLEAN)
273
+			: $this->testForEspressoPage();
274
+	}
275
+
276
+
277
+	/**
278
+	 * attempts to determine if the current page/post is an EE related page/post
279
+	 * because it utilizes one of our CPT taxonomies, endpoints, or post types
280
+	 *
281
+	 * @return bool
282
+	 */
283
+	private function testForEspressoPage(): bool
284
+	{
285
+		// in case it has already been set
286
+		if ($this->is_espresso_page) {
287
+			return true;
288
+		}
289
+		global $WP;
290
+		$espresso_CPT_taxonomies = $this->cpt_strategy->get_CPT_taxonomies();
291
+		if (is_array($espresso_CPT_taxonomies)) {
292
+			foreach ($espresso_CPT_taxonomies as $espresso_CPT_taxonomy => $details) {
293
+				if (isset($WP->query_vars, $WP->query_vars[ $espresso_CPT_taxonomy ])) {
294
+					return true;
295
+				}
296
+			}
297
+		}
298
+		// load espresso CPT endpoints
299
+		$espresso_CPT_endpoints  = $this->cpt_strategy->get_CPT_endpoints();
300
+		$post_type_CPT_endpoints = array_flip($espresso_CPT_endpoints);
301
+		foreach ($this->post_type as $post_type) {
302
+			// was a post name passed ?
303
+			if (isset($post_type_CPT_endpoints[ $post_type ])) {
304
+				// kk we know this is an espresso page, but is it a specific post ?
305
+				if (! $this->post_name) {
306
+					$espresso_post_type = $this->request->getRequestParam('post_type');
307
+					// there's no specific post name set, so maybe it's one of our endpoints like www.domain.com/events
308
+					// this essentially sets the post_name to "events" (or whatever EE CPT)
309
+					$post_name = $post_type_CPT_endpoints[ $espresso_post_type ] ?? '';
310
+					// if the post type matches one of ours then set the post name to the endpoint
311
+					if ($post_name) {
312
+						$this->post_name = $post_name;
313
+					}
314
+				}
315
+				return true;
316
+			}
317
+		}
318
+		return false;
319
+	}
320 320
 }
Please login to merge, or discard this patch.
Spacing   +18 added lines, -18 removed lines patch added patch discarded remove patch
@@ -28,11 +28,11 @@  discard block
 block discarded – undo
28 28
 
29 29
     private ?bool $is_espresso_page = null;
30 30
 
31
-    private int $post_id          = 0;
31
+    private int $post_id = 0;
32 32
 
33
-    private string $post_name        = '';
33
+    private string $post_name = '';
34 34
 
35
-    private array $post_type        = [];
35
+    private array $post_type = [];
36 36
 
37 37
     private RequestInterface $request;
38 38
 
@@ -63,7 +63,7 @@  discard block
 block discarded – undo
63 63
             return;
64 64
         }
65 65
         // if somebody forgot to provide us with WP, that's ok because its global
66
-        if (! $WP instanceof WP) {
66
+        if ( ! $WP instanceof WP) {
67 67
             global $WP;
68 68
         }
69 69
         $this->post_id   = $this->getPostId($WP);
@@ -88,7 +88,7 @@  discard block
 block discarded – undo
88 88
         if ($post_id) {
89 89
             return get_permalink($post_id);
90 90
         }
91
-        if (! $WP instanceof WP) {
91
+        if ( ! $WP instanceof WP) {
92 92
             global $WP;
93 93
         }
94 94
         if ($WP instanceof WP && $WP->request) {
@@ -105,7 +105,7 @@  discard block
 block discarded – undo
105 105
     {
106 106
         return array_filter(
107 107
             $this->post_type,
108
-            function ($post_type) {
108
+            function($post_type) {
109 109
                 return strpos($post_type, 'espresso_') === 0;
110 110
             }
111 111
         );
@@ -127,17 +127,17 @@  discard block
 block discarded – undo
127 127
                 $post_id = $WP->query_vars['p'];
128 128
             }
129 129
             // not a post? what about a page?
130
-            if (! $post_id && isset($WP->query_vars['page_id'])) {
130
+            if ( ! $post_id && isset($WP->query_vars['page_id'])) {
131 131
                 $post_id = $WP->query_vars['page_id'];
132 132
             }
133 133
             // ok... maybe pretty permalinks are off and the ID is set in the raw request...
134 134
             // but hasn't been processed yet ie: this method is being called too early :\
135
-            if (! $post_id && $WP->request !== null && is_numeric(basename($WP->request))) {
135
+            if ( ! $post_id && $WP->request !== null && is_numeric(basename($WP->request))) {
136 136
                 $post_id = basename($WP->request);
137 137
             }
138 138
         }
139 139
         // none of the above? ok what about an explicit "post_id" URL parameter?
140
-        if (! $post_id && $this->request->requestParamIsSet('post_id')) {
140
+        if ( ! $post_id && $this->request->requestParamIsSet('post_id')) {
141 141
             $post_id = $this->request->getRequestParam('post_id', 0, DataType::INT);
142 142
         }
143 143
         return (int) $post_id;
@@ -160,15 +160,15 @@  discard block
 block discarded – undo
160 160
                 $post_name = is_array($WP->query_vars['name']) ? $WP->query_vars['name'][0] : $WP->query_vars['name'];
161 161
             }
162 162
             // what about the page name?
163
-            if (! $post_name && isset($WP->query_vars['pagename']) && ! empty($WP->query_vars['pagename'])) {
163
+            if ( ! $post_name && isset($WP->query_vars['pagename']) && ! empty($WP->query_vars['pagename'])) {
164 164
                 $post_name = is_array($WP->query_vars['pagename']) ? $WP->query_vars['pagename'][0]
165 165
                     : $WP->query_vars['pagename'];
166 166
             }
167 167
             // this stinks but let's run a query to try and get the post name from the URL
168 168
             // (assuming pretty permalinks are on)
169
-            if (! $post_name && ! empty($WP->request)) {
169
+            if ( ! $post_name && ! empty($WP->request)) {
170 170
                 $possible_post_name = basename($WP->request);
171
-                if (! is_numeric($possible_post_name)) {
171
+                if ( ! is_numeric($possible_post_name)) {
172 172
                     $SQL                = "SELECT ID from $wpdb->posts";
173 173
                     $SQL                .= " WHERE post_status NOT IN ('auto-draft', 'inherit', 'trash')";
174 174
                     $SQL                .= ' AND post_name=%s';
@@ -181,7 +181,7 @@  discard block
 block discarded – undo
181 181
         }
182 182
         // ug... ok... nothing yet... but do we have a post ID?
183 183
         // if so then... sigh... run a query to get the post name :\
184
-        if (! $post_name && $this->post_id) {
184
+        if ( ! $post_name && $this->post_id) {
185 185
             $SQL                = "SELECT post_name from $wpdb->posts";
186 186
             $SQL                .= " WHERE post_status NOT IN ('auto-draft', 'inherit', 'trash')";
187 187
             $SQL                .= ' AND ID=%d';
@@ -191,7 +191,7 @@  discard block
 block discarded – undo
191 191
             }
192 192
         }
193 193
         // still nothing? ok what about an explicit 'post_name' URL parameter?
194
-        if (! $post_name && $this->request->requestParamIsSet('post_name')) {
194
+        if ( ! $post_name && $this->request->requestParamIsSet('post_name')) {
195 195
             $post_name = $this->request->getRequestParam('post_name');
196 196
         }
197 197
         return $post_name ?? '';
@@ -290,7 +290,7 @@  discard block
 block discarded – undo
290 290
         $espresso_CPT_taxonomies = $this->cpt_strategy->get_CPT_taxonomies();
291 291
         if (is_array($espresso_CPT_taxonomies)) {
292 292
             foreach ($espresso_CPT_taxonomies as $espresso_CPT_taxonomy => $details) {
293
-                if (isset($WP->query_vars, $WP->query_vars[ $espresso_CPT_taxonomy ])) {
293
+                if (isset($WP->query_vars, $WP->query_vars[$espresso_CPT_taxonomy])) {
294 294
                     return true;
295 295
                 }
296 296
             }
@@ -300,13 +300,13 @@  discard block
 block discarded – undo
300 300
         $post_type_CPT_endpoints = array_flip($espresso_CPT_endpoints);
301 301
         foreach ($this->post_type as $post_type) {
302 302
             // was a post name passed ?
303
-            if (isset($post_type_CPT_endpoints[ $post_type ])) {
303
+            if (isset($post_type_CPT_endpoints[$post_type])) {
304 304
                 // kk we know this is an espresso page, but is it a specific post ?
305
-                if (! $this->post_name) {
305
+                if ( ! $this->post_name) {
306 306
                     $espresso_post_type = $this->request->getRequestParam('post_type');
307 307
                     // there's no specific post name set, so maybe it's one of our endpoints like www.domain.com/events
308 308
                     // this essentially sets the post_name to "events" (or whatever EE CPT)
309
-                    $post_name = $post_type_CPT_endpoints[ $espresso_post_type ] ?? '';
309
+                    $post_name = $post_type_CPT_endpoints[$espresso_post_type] ?? '';
310 310
                     // if the post type matches one of ours then set the post name to the endpoint
311 311
                     if ($post_name) {
312 312
                         $this->post_name = $post_name;
Please login to merge, or discard this patch.
PaymentMethods/PayPalCommerce/PayPalCommerce.php 1 patch
Indentation   +65 added lines, -65 removed lines patch added patch discarded remove patch
@@ -21,76 +21,76 @@
 block discarded – undo
21 21
  */
22 22
 class PayPalCommerce
23 23
 {
24
-    public function __construct()
25
-    {
26
-    }
24
+	public function __construct()
25
+	{
26
+	}
27 27
 
28 28
 
29
-    /**
30
-     * Register with EE and load this payment method dependencies.
31
-     *
32
-     * @return void
33
-     * @throws EE_Error
34
-     */
35
-    public function initialize()
36
-    {
37
-        $this->registerDependencies();
38
-        // Register payment method through a legacy manager.
39
-        add_filter(
40
-            'FHEE__EE_Payment_Method_Manager__register_payment_methods__payment_methods_to_register',
41
-            [__CLASS__, 'injectPaymentMethod']
42
-        );
43
-        // Load modules.
44
-        /** @var LegacyModulesManager $legacy_modules_manager */
45
-        $legacy_modules_manager = LoaderFactory::getShared(LegacyModulesManager::class);
46
-        $legacy_modules_manager->registerModule(__DIR__ . '/modules/EED_PayPalCommerce.module.php');
47
-        $legacy_modules_manager->registerModule(__DIR__ . '/modules/EED_PayPalOnboard.module.php');
29
+	/**
30
+	 * Register with EE and load this payment method dependencies.
31
+	 *
32
+	 * @return void
33
+	 * @throws EE_Error
34
+	 */
35
+	public function initialize()
36
+	{
37
+		$this->registerDependencies();
38
+		// Register payment method through a legacy manager.
39
+		add_filter(
40
+			'FHEE__EE_Payment_Method_Manager__register_payment_methods__payment_methods_to_register',
41
+			[__CLASS__, 'injectPaymentMethod']
42
+		);
43
+		// Load modules.
44
+		/** @var LegacyModulesManager $legacy_modules_manager */
45
+		$legacy_modules_manager = LoaderFactory::getShared(LegacyModulesManager::class);
46
+		$legacy_modules_manager->registerModule(__DIR__ . '/modules/EED_PayPalCommerce.module.php');
47
+		$legacy_modules_manager->registerModule(__DIR__ . '/modules/EED_PayPalOnboard.module.php');
48 48
 
49
-        // Setup auto loaders.
50
-        EEH_Autoloader::instance()->register_autoloader([
51
-            'SettingsForm'   => EEP_PAYPAL_COMMERCE_PATH . 'forms/SettingsForm.php',
52
-            'OnboardingForm' => EEP_PAYPAL_COMMERCE_PATH . 'forms/OnboardingForm.php',
53
-            'BillingForm'    => EEP_PAYPAL_COMMERCE_PATH . 'forms/BillingForm.php',
54
-        ]);
55
-    }
49
+		// Setup auto loaders.
50
+		EEH_Autoloader::instance()->register_autoloader([
51
+			'SettingsForm'   => EEP_PAYPAL_COMMERCE_PATH . 'forms/SettingsForm.php',
52
+			'OnboardingForm' => EEP_PAYPAL_COMMERCE_PATH . 'forms/OnboardingForm.php',
53
+			'BillingForm'    => EEP_PAYPAL_COMMERCE_PATH . 'forms/BillingForm.php',
54
+		]);
55
+	}
56 56
 
57 57
 
58
-    /**
59
-     * Add this payment to the list of PMs to be registered.
60
-     *
61
-     * @param array $pms_to_register
62
-     * @return array
63
-     */
64
-    public static function injectPaymentMethod(array $pms_to_register): array
65
-    {
66
-        $pms_to_register[] = EEP_PAYPAL_COMMERCE_PATH;
67
-        return $pms_to_register;
68
-    }
58
+	/**
59
+	 * Add this payment to the list of PMs to be registered.
60
+	 *
61
+	 * @param array $pms_to_register
62
+	 * @return array
63
+	 */
64
+	public static function injectPaymentMethod(array $pms_to_register): array
65
+	{
66
+		$pms_to_register[] = EEP_PAYPAL_COMMERCE_PATH;
67
+		return $pms_to_register;
68
+	}
69 69
 
70 70
 
71
-    /**
72
-     * Register class dependencies.
73
-     *
74
-     * @return void
75
-     * @since 5.0.22.p
76
-     */
77
-    protected function registerDependencies(): void
78
-    {
79
-        EE_Dependency_Map::instance()->registerDependencies(
80
-            'EventEspresso\PaymentMethods\PayPalCommerce\api\orders\CreateOrder',
81
-            [
82
-                null,
83
-                null,
84
-                null,
85
-                'EventEspresso\core\domain\services\capabilities\FeatureFlags' => EE_Dependency_Map::load_from_cache,
86
-                'EventEspresso\core\services\payment_methods\gateways\GatewayDataFormatter' => EE_Dependency_Map::load_from_cache,
87
-            ]
88
-        );
89
-        EE_Dependency_Map::instance()->registerDependencies(
90
-            'EventEspresso\PaymentMethods\PayPalCommerce\tools\fees\PartnerPaymentFees',
91
-            [
92
-                'EventEspresso\core\services\payments\PaymentProcessorFees' => EE_Dependency_Map::load_from_cache,
93
-            ]
94
-        );
95
-    }
71
+	/**
72
+	 * Register class dependencies.
73
+	 *
74
+	 * @return void
75
+	 * @since 5.0.22.p
76
+	 */
77
+	protected function registerDependencies(): void
78
+	{
79
+		EE_Dependency_Map::instance()->registerDependencies(
80
+			'EventEspresso\PaymentMethods\PayPalCommerce\api\orders\CreateOrder',
81
+			[
82
+				null,
83
+				null,
84
+				null,
85
+				'EventEspresso\core\domain\services\capabilities\FeatureFlags' => EE_Dependency_Map::load_from_cache,
86
+				'EventEspresso\core\services\payment_methods\gateways\GatewayDataFormatter' => EE_Dependency_Map::load_from_cache,
87
+			]
88
+		);
89
+		EE_Dependency_Map::instance()->registerDependencies(
90
+			'EventEspresso\PaymentMethods\PayPalCommerce\tools\fees\PartnerPaymentFees',
91
+			[
92
+				'EventEspresso\core\services\payments\PaymentProcessorFees' => EE_Dependency_Map::load_from_cache,
93
+			]
94
+		);
95
+	}
96 96
 }
Please login to merge, or discard this patch.
PaymentMethods/PayPalCommerce/api/orders/CreateOrder.php 2 patches
Indentation   +413 added lines, -413 removed lines patch added patch discarded remove patch
@@ -33,418 +33,418 @@
 block discarded – undo
33 33
  */
34 34
 class CreateOrder extends OrdersApi
35 35
 {
36
-    /**
37
-     * Line items total.
38
-     *
39
-     * @var float
40
-     */
41
-    protected float $items_total = 0.0;
42
-
43
-    /**
44
-     * Promotions total.
45
-     *
46
-     * @var float
47
-     */
48
-    protected float $promos_total = 0.0;
49
-
50
-    /**
51
-     * Tax total.
52
-     *
53
-     * @var float
54
-     */
55
-    protected float $tax_total = 0.0;
56
-
57
-    /**
58
-     * Currency.
59
-     *
60
-     * @var string
61
-     */
62
-    protected string $currency_code;
63
-
64
-    /**
65
-     * Billing info.
66
-     *
67
-     * @var array
68
-     */
69
-    protected array $billing_info;
70
-
71
-    /**
72
-     * Transaction this order is for.
73
-     *
74
-     * @var EE_Transaction
75
-     */
76
-    protected EE_Transaction $transaction;
77
-
78
-    private FeatureFlags $feature;
79
-
80
-    private GatewayDataFormatter $gateway_data_formatter;
81
-
82
-    private EE_Payment $payment;
83
-
84
-    /**
85
-     * CreateOrder constructor.
86
-     *
87
-     * @param PayPalApi      $api
88
-     * @param EE_Transaction $transaction
89
-     * @param array          $billing_info
90
-     * @param FeatureFlags   $feature
91
-     */
92
-    public function __construct(PayPalApi $api, EE_Transaction $transaction, array $billing_info, FeatureFlags $feature, GatewayDataFormatter $gateway_data_formatter)
93
-    {
94
-        parent::__construct($api);
95
-        $this->transaction   = $transaction;
96
-        $this->feature       = $feature;
97
-        $this->currency_code = CurrencyManager::currencyCode();
98
-        $this->sanitizeRequestParameters($billing_info);
99
-        $this->gateway_data_formatter = $gateway_data_formatter;
100
-        $this->setPaymentPlaceholder();
101
-    }
102
-
103
-
104
-    /**
105
-     * Sanitize the array of billing form data.
106
-     *
107
-     * @param array $billing_info
108
-     * @return void
109
-     */
110
-    public function sanitizeRequestParameters(array $billing_info): void
111
-    {
112
-        $sanitizer = new RequestSanitizer(new Basic());
113
-        foreach ($billing_info as $item => $value) {
114
-            $this->billing_info[ $item ] = $sanitizer->clean($value);
115
-        }
116
-    }
117
-
118
-
119
-    /**
120
-     * Create PayPal Order.
121
-     *
122
-     * @return array
123
-     * @throws EE_Error
124
-     * @throws ReflectionException
125
-     */
126
-    public function create(): array
127
-    {
128
-        $order_parameters = $this->getParameters();
129
-        // Create Order request.
130
-        $create_response = $this->api->sendRequest($order_parameters, $this->request_url);
131
-        // Check for MISMATCH errors.
132
-        if ($this->isMismatchError($create_response)) {
133
-            // Mismatch, fix items.
134
-            $order_parameters['purchase_units'][0]['items'] = $this->getSimplifiedItems();
135
-            // Add simplified breakdown.
136
-            $order_parameters['purchase_units'][0]['amount']['breakdown'] = $this->getSimplifiedAmountBreakdown();
137
-            // Retry Order request.
138
-            $create_response = $this->api->sendRequest($order_parameters, $this->request_url);
139
-        }
140
-        return $this->validateOrder($create_response, $order_parameters);
141
-    }
142
-
143
-
144
-    /**
145
-     * Form order parameters.
146
-     *
147
-     * @return array
148
-     * @throws EE_Error
149
-     * @throws ReflectionException
150
-     * @throws Exception
151
-     */
152
-    protected function getParameters(): array
153
-    {
154
-        $registrant  = $this->transaction->primary_registration();
155
-        $attendee    = $registrant->attendee();
156
-        $event       = $registrant->event();
157
-        $description = $this->gateway_data_formatter->formatOrderDescription($this->payment);
158
-        $parameters  = [
159
-            'intent'              => 'CAPTURE',
160
-            'purchase_units'      => [
161
-                [
162
-                    'custom_id'   => $this->transaction->ID(),
163
-                    'description' => substr(wp_strip_all_tags($description), 0, 125),
164
-                    'items'       => $this->getLineItems(),
165
-                    'amount'      => [
166
-                        'value'         => (string) CurrencyManager::normalizeValue($this->transaction->remaining()),
167
-                        'currency_code' => $this->currency_code,
168
-                        'breakdown'     => $this->getBreakdown(),
169
-                    ],
170
-                ],
171
-            ],
172
-            'payment_source' => [
173
-                'paypal' => [
174
-                    'experience_context' => [
175
-                        'user_action' => 'PAY_NOW'
176
-                    ],
177
-                    'email_address' => $attendee->email(),
178
-                    'name'  => [
179
-                        'given_name' => $attendee->fname(),
180
-                        'surname' => $attendee->lname(),
181
-                    ],
182
-                    'address' => [
183
-                        'address_line_1'    => $attendee->address(),
184
-                        'address_line_2'    => $attendee->address2(),
185
-                        'admin_area_2'      => $attendee->city(),
186
-                        'admin_area_1'      => $attendee->state_abbrev(),
187
-                        'postal_code'       => $attendee->zip(),
188
-                        'country_code'      => $attendee->country_ID(),
189
-                    ],
190
-                ],
191
-            ],
192
-        ];
193
-        // Do we have the permissions for the fees ?
194
-        $scopes = PayPalExtraMetaManager::getPmOption(
195
-            $this->transaction->payment_method(),
196
-            Domain::META_KEY_AUTHORIZED_SCOPES
197
-        );
198
-        if (
199
-            (
200
-                (defined('EE_PPC_USE_PAYMENT_FEES') && EE_PPC_USE_PAYMENT_FEES)
201
-                || (! defined('EE_PPC_USE_PAYMENT_FEES')
202
-                    && $this->feature->allowed(FeatureFlag::USE_PAYMENT_PROCESSOR_FEES)
203
-                )
204
-            )
205
-            && ! empty($scopes) && in_array('partnerfee', $scopes)
206
-        ) {
207
-            /** @var PartnerPaymentFees $payment_fees */
208
-            $payment_fees = LoaderFactory::getShared(PartnerPaymentFees::class);
209
-            $parameters['purchase_units'][0]['payment_instruction'] = [
210
-                'platform_fees' => [
211
-                    [
212
-                        'amount' => [
213
-                            'value'         => (string) $payment_fees->getPartnerFee($this->transaction),
214
-                            'currency_code' => $this->currency_code,
215
-                        ],
216
-                    ],
217
-                ]
218
-            ];
219
-        }
220
-        return $parameters;
221
-    }
222
-
223
-
224
-    /**
225
-     * Itemize the payment. List all the line items, discounts and taxes.
226
-     *
227
-     * @return array
228
-     * @throws EE_Error|ReflectionException
229
-     */
230
-    protected function getLineItems(): array
231
-    {
232
-        // Order line items.
233
-        $line_items       = [];
234
-        $event_line_items = $this->transaction->items_purchased();
235
-        // List actual line items.
236
-        foreach ($event_line_items as $line_item) {
237
-            if (
238
-                $line_item instanceof EE_Line_Item
239
-                && $line_item->OBJ_type() !== 'Promotion'
240
-                && $line_item->quantity() > 0
241
-            ) {
242
-                $item_money     = CurrencyManager::normalizeValue($line_item->unit_price());
243
-                $line_items []  = [
244
-                    'name'        => substr(wp_strip_all_tags($this->gateway_data_formatter->formatLineItemName($line_item, $this->payment)), 0, 125),
245
-                    'quantity'    => $line_item->quantity(),
246
-                    'description' => substr(wp_strip_all_tags($this->gateway_data_formatter->formatLineItemDesc($line_item, $this->payment)), 0, 125),
247
-                    'unit_amount' => [
248
-                        'currency_code' => $this->currency_code,
249
-                        'value'         => (string) $item_money,
250
-                    ],
251
-                    'category'    => 'DIGITAL_GOODS',
252
-                ];
253
-                // Line item total.
254
-                $this->items_total += $line_item->pretaxTotal();
255
-            } elseif ($line_item->OBJ_type() === 'Promotion' && $line_item->quantity() > 0) {
256
-                // Promotions total.
257
-                $this->promos_total += $line_item->total();
258
-            }
259
-        }
260
-        // Make sure we have an absolute number with only two decimal laces.
261
-        $this->items_total  = CurrencyManager::normalizeValue($this->items_total);
262
-        $this->promos_total = CurrencyManager::normalizeValue($this->promos_total);
263
-        // If this is a partial payment, apply the paid amount as a promo.
264
-        if ($this->transaction->paid() > 0) {
265
-            $this->promos_total += CurrencyManager::normalizeValue($this->transaction->paid());
266
-        }
267
-        $this->countTaxTotal();
268
-        return $line_items;
269
-    }
270
-
271
-
272
-    /**
273
-     * Count the tax total.
274
-     *
275
-     * @return void
276
-     * @throws EE_Error|ReflectionException
277
-     */
278
-    protected function countTaxTotal(): void
279
-    {
280
-        // List taxes.
281
-        $this->tax_total = 0.0;
282
-        $tax_items       = $this->transaction->tax_items();
283
-        foreach ($tax_items as $tax_item) {
284
-            $this->tax_total += $tax_item->total();
285
-        }
286
-        $this->tax_total = CurrencyManager::normalizeValue($this->tax_total);
287
-    }
288
-
289
-
290
-    /**
291
-     * Itemize the payment the breakdown list.
292
-     *
293
-     * @return array
294
-     */
295
-    protected function getBreakdown(): array
296
-    {
297
-        $breakdown['item_total'] = [
298
-            'currency_code' => $this->currency_code,
299
-            'value'         => (string) $this->items_total,
300
-        ];
301
-        $breakdown['tax_total']  = [
302
-            'currency_code' => $this->currency_code,
303
-            'value'         => (string) $this->tax_total,
304
-        ];
305
-        $breakdown['discount']   = [
306
-            'currency_code' => $this->currency_code,
307
-            'value'         => (string) abs($this->promos_total),
308
-        ];
309
-        return $breakdown;
310
-    }
311
-
312
-
313
-    /**
314
-     * Makes sure that we have received an Order back from the API call.
315
-     *
316
-     * @param $response
317
-     * @param $parameters
318
-     * @return array
319
-     * @throws EE_Error
320
-     * @throws ReflectionException
321
-     */
322
-    public function validateOrder($response, $parameters): array
323
-    {
324
-        PayPalLogger::errorLog(
325
-            esc_html__('Validating Order Create:', 'event_espresso'),
326
-            [$this->request_url, $response],
327
-            $this->transaction->payment_method(),
328
-            false,
329
-            $this->transaction
330
-        );
331
-        if (! empty($response['error'])) {
332
-            return $response;
333
-        }
334
-        if (! isset($response['id'])) {
335
-            $message = esc_html__('Unexpected response. Unable to find the order.', 'event_espresso');
336
-            try {
337
-                PayPalLogger::errorLog(
338
-                    $message,
339
-                    [$this->request_url, $parameters, $response],
340
-                    $this->transaction->payment_method()
341
-                );
342
-            } catch (EE_Error | ReflectionException $e) {
343
-                error_log("PayPalLogger Error: $message: " . json_encode($response));
344
-            }
345
-            return [
346
-                'error'    => $response['error'] ?? 'missing_order',
347
-                'message'  => $response['message'] ?? $message,
348
-                'response' => $response,
349
-            ];
350
-        }
351
-        return $response;
352
-    }
353
-
354
-
355
-    /**
356
-     * Check if PayPals response contains 'MISMATCH' errors.
357
-     *
358
-     * @return bool
359
-     */
360
-    public function isMismatchError($response): bool
361
-    {
362
-        if (! isset($response['details']) || ! is_array($response['details'])) {
363
-            return false;
364
-        }
365
-
366
-        foreach($response['details'] as $detail) {
367
-            if (! empty($detail['issue'])) {
368
-                if (
369
-                    strtoupper($detail['issue']) === 'ITEM_TOTAL_MISMATCH'
370
-                    || strtoupper($detail['issue']) === 'AMOUNT_MISMATCH'
371
-            ) {
372
-                    PayPalLogger::errorLog(
373
-                        esc_html__('Mistmatch Error:', 'event_espresso'),
374
-                        [$this->request_url, $response],
375
-                        $this->transaction->payment_method(),
376
-                        false,
377
-                        $this->transaction
378
-                    );
379
-                    return true;
380
-                }
381
-            }
382
-        }
383
-        return false;
384
-    }
385
-
386
-
387
-    /**
388
-     * Itemize the simplified payment breakdown list.
389
-     *
390
-     * @return array
391
-     */
392
-    protected function getSimplifiedAmountBreakdown(): array
393
-    {
394
-        $tax_total = $this->transaction->tax_total();
395
-        $breakdown['item_total'] = [
396
-            'currency_code' => $this->currency_code,
397
-            'value'         => (string) CurrencyManager::normalizeValue($this->transaction->remaining() - $tax_total)
398
-        ];
399
-        if ($tax_total > 0) {
400
-            $breakdown['tax_total'] = [
401
-                'currency_code' => $this->currency_code,
402
-                'value'         => (string) CurrencyManager::normalizeValue($tax_total)
403
-            ];
404
-        }
405
-        return $breakdown;
406
-    }
407
-
408
-
409
-    /**
410
-     * Generate single line item for full order.
411
-     *
412
-     * @return array
413
-     */
414
-    protected function getSimplifiedItems(): array
415
-    {
416
-        // Simplified single line item.
417
-        $line_items             = [];
418
-        $primary_registrant     = $this->transaction->primary_registration();
419
-        $event_obj              = $primary_registrant->event_obj();
420
-        $name_and_description   = $this->gateway_data_formatter->formatOrderDescription($this->payment);
36
+	/**
37
+	 * Line items total.
38
+	 *
39
+	 * @var float
40
+	 */
41
+	protected float $items_total = 0.0;
42
+
43
+	/**
44
+	 * Promotions total.
45
+	 *
46
+	 * @var float
47
+	 */
48
+	protected float $promos_total = 0.0;
49
+
50
+	/**
51
+	 * Tax total.
52
+	 *
53
+	 * @var float
54
+	 */
55
+	protected float $tax_total = 0.0;
56
+
57
+	/**
58
+	 * Currency.
59
+	 *
60
+	 * @var string
61
+	 */
62
+	protected string $currency_code;
63
+
64
+	/**
65
+	 * Billing info.
66
+	 *
67
+	 * @var array
68
+	 */
69
+	protected array $billing_info;
70
+
71
+	/**
72
+	 * Transaction this order is for.
73
+	 *
74
+	 * @var EE_Transaction
75
+	 */
76
+	protected EE_Transaction $transaction;
77
+
78
+	private FeatureFlags $feature;
79
+
80
+	private GatewayDataFormatter $gateway_data_formatter;
81
+
82
+	private EE_Payment $payment;
83
+
84
+	/**
85
+	 * CreateOrder constructor.
86
+	 *
87
+	 * @param PayPalApi      $api
88
+	 * @param EE_Transaction $transaction
89
+	 * @param array          $billing_info
90
+	 * @param FeatureFlags   $feature
91
+	 */
92
+	public function __construct(PayPalApi $api, EE_Transaction $transaction, array $billing_info, FeatureFlags $feature, GatewayDataFormatter $gateway_data_formatter)
93
+	{
94
+		parent::__construct($api);
95
+		$this->transaction   = $transaction;
96
+		$this->feature       = $feature;
97
+		$this->currency_code = CurrencyManager::currencyCode();
98
+		$this->sanitizeRequestParameters($billing_info);
99
+		$this->gateway_data_formatter = $gateway_data_formatter;
100
+		$this->setPaymentPlaceholder();
101
+	}
102
+
103
+
104
+	/**
105
+	 * Sanitize the array of billing form data.
106
+	 *
107
+	 * @param array $billing_info
108
+	 * @return void
109
+	 */
110
+	public function sanitizeRequestParameters(array $billing_info): void
111
+	{
112
+		$sanitizer = new RequestSanitizer(new Basic());
113
+		foreach ($billing_info as $item => $value) {
114
+			$this->billing_info[ $item ] = $sanitizer->clean($value);
115
+		}
116
+	}
117
+
118
+
119
+	/**
120
+	 * Create PayPal Order.
121
+	 *
122
+	 * @return array
123
+	 * @throws EE_Error
124
+	 * @throws ReflectionException
125
+	 */
126
+	public function create(): array
127
+	{
128
+		$order_parameters = $this->getParameters();
129
+		// Create Order request.
130
+		$create_response = $this->api->sendRequest($order_parameters, $this->request_url);
131
+		// Check for MISMATCH errors.
132
+		if ($this->isMismatchError($create_response)) {
133
+			// Mismatch, fix items.
134
+			$order_parameters['purchase_units'][0]['items'] = $this->getSimplifiedItems();
135
+			// Add simplified breakdown.
136
+			$order_parameters['purchase_units'][0]['amount']['breakdown'] = $this->getSimplifiedAmountBreakdown();
137
+			// Retry Order request.
138
+			$create_response = $this->api->sendRequest($order_parameters, $this->request_url);
139
+		}
140
+		return $this->validateOrder($create_response, $order_parameters);
141
+	}
142
+
143
+
144
+	/**
145
+	 * Form order parameters.
146
+	 *
147
+	 * @return array
148
+	 * @throws EE_Error
149
+	 * @throws ReflectionException
150
+	 * @throws Exception
151
+	 */
152
+	protected function getParameters(): array
153
+	{
154
+		$registrant  = $this->transaction->primary_registration();
155
+		$attendee    = $registrant->attendee();
156
+		$event       = $registrant->event();
157
+		$description = $this->gateway_data_formatter->formatOrderDescription($this->payment);
158
+		$parameters  = [
159
+			'intent'              => 'CAPTURE',
160
+			'purchase_units'      => [
161
+				[
162
+					'custom_id'   => $this->transaction->ID(),
163
+					'description' => substr(wp_strip_all_tags($description), 0, 125),
164
+					'items'       => $this->getLineItems(),
165
+					'amount'      => [
166
+						'value'         => (string) CurrencyManager::normalizeValue($this->transaction->remaining()),
167
+						'currency_code' => $this->currency_code,
168
+						'breakdown'     => $this->getBreakdown(),
169
+					],
170
+				],
171
+			],
172
+			'payment_source' => [
173
+				'paypal' => [
174
+					'experience_context' => [
175
+						'user_action' => 'PAY_NOW'
176
+					],
177
+					'email_address' => $attendee->email(),
178
+					'name'  => [
179
+						'given_name' => $attendee->fname(),
180
+						'surname' => $attendee->lname(),
181
+					],
182
+					'address' => [
183
+						'address_line_1'    => $attendee->address(),
184
+						'address_line_2'    => $attendee->address2(),
185
+						'admin_area_2'      => $attendee->city(),
186
+						'admin_area_1'      => $attendee->state_abbrev(),
187
+						'postal_code'       => $attendee->zip(),
188
+						'country_code'      => $attendee->country_ID(),
189
+					],
190
+				],
191
+			],
192
+		];
193
+		// Do we have the permissions for the fees ?
194
+		$scopes = PayPalExtraMetaManager::getPmOption(
195
+			$this->transaction->payment_method(),
196
+			Domain::META_KEY_AUTHORIZED_SCOPES
197
+		);
198
+		if (
199
+			(
200
+				(defined('EE_PPC_USE_PAYMENT_FEES') && EE_PPC_USE_PAYMENT_FEES)
201
+				|| (! defined('EE_PPC_USE_PAYMENT_FEES')
202
+					&& $this->feature->allowed(FeatureFlag::USE_PAYMENT_PROCESSOR_FEES)
203
+				)
204
+			)
205
+			&& ! empty($scopes) && in_array('partnerfee', $scopes)
206
+		) {
207
+			/** @var PartnerPaymentFees $payment_fees */
208
+			$payment_fees = LoaderFactory::getShared(PartnerPaymentFees::class);
209
+			$parameters['purchase_units'][0]['payment_instruction'] = [
210
+				'platform_fees' => [
211
+					[
212
+						'amount' => [
213
+							'value'         => (string) $payment_fees->getPartnerFee($this->transaction),
214
+							'currency_code' => $this->currency_code,
215
+						],
216
+					],
217
+				]
218
+			];
219
+		}
220
+		return $parameters;
221
+	}
222
+
223
+
224
+	/**
225
+	 * Itemize the payment. List all the line items, discounts and taxes.
226
+	 *
227
+	 * @return array
228
+	 * @throws EE_Error|ReflectionException
229
+	 */
230
+	protected function getLineItems(): array
231
+	{
232
+		// Order line items.
233
+		$line_items       = [];
234
+		$event_line_items = $this->transaction->items_purchased();
235
+		// List actual line items.
236
+		foreach ($event_line_items as $line_item) {
237
+			if (
238
+				$line_item instanceof EE_Line_Item
239
+				&& $line_item->OBJ_type() !== 'Promotion'
240
+				&& $line_item->quantity() > 0
241
+			) {
242
+				$item_money     = CurrencyManager::normalizeValue($line_item->unit_price());
243
+				$line_items []  = [
244
+					'name'        => substr(wp_strip_all_tags($this->gateway_data_formatter->formatLineItemName($line_item, $this->payment)), 0, 125),
245
+					'quantity'    => $line_item->quantity(),
246
+					'description' => substr(wp_strip_all_tags($this->gateway_data_formatter->formatLineItemDesc($line_item, $this->payment)), 0, 125),
247
+					'unit_amount' => [
248
+						'currency_code' => $this->currency_code,
249
+						'value'         => (string) $item_money,
250
+					],
251
+					'category'    => 'DIGITAL_GOODS',
252
+				];
253
+				// Line item total.
254
+				$this->items_total += $line_item->pretaxTotal();
255
+			} elseif ($line_item->OBJ_type() === 'Promotion' && $line_item->quantity() > 0) {
256
+				// Promotions total.
257
+				$this->promos_total += $line_item->total();
258
+			}
259
+		}
260
+		// Make sure we have an absolute number with only two decimal laces.
261
+		$this->items_total  = CurrencyManager::normalizeValue($this->items_total);
262
+		$this->promos_total = CurrencyManager::normalizeValue($this->promos_total);
263
+		// If this is a partial payment, apply the paid amount as a promo.
264
+		if ($this->transaction->paid() > 0) {
265
+			$this->promos_total += CurrencyManager::normalizeValue($this->transaction->paid());
266
+		}
267
+		$this->countTaxTotal();
268
+		return $line_items;
269
+	}
270
+
271
+
272
+	/**
273
+	 * Count the tax total.
274
+	 *
275
+	 * @return void
276
+	 * @throws EE_Error|ReflectionException
277
+	 */
278
+	protected function countTaxTotal(): void
279
+	{
280
+		// List taxes.
281
+		$this->tax_total = 0.0;
282
+		$tax_items       = $this->transaction->tax_items();
283
+		foreach ($tax_items as $tax_item) {
284
+			$this->tax_total += $tax_item->total();
285
+		}
286
+		$this->tax_total = CurrencyManager::normalizeValue($this->tax_total);
287
+	}
288
+
289
+
290
+	/**
291
+	 * Itemize the payment the breakdown list.
292
+	 *
293
+	 * @return array
294
+	 */
295
+	protected function getBreakdown(): array
296
+	{
297
+		$breakdown['item_total'] = [
298
+			'currency_code' => $this->currency_code,
299
+			'value'         => (string) $this->items_total,
300
+		];
301
+		$breakdown['tax_total']  = [
302
+			'currency_code' => $this->currency_code,
303
+			'value'         => (string) $this->tax_total,
304
+		];
305
+		$breakdown['discount']   = [
306
+			'currency_code' => $this->currency_code,
307
+			'value'         => (string) abs($this->promos_total),
308
+		];
309
+		return $breakdown;
310
+	}
311
+
312
+
313
+	/**
314
+	 * Makes sure that we have received an Order back from the API call.
315
+	 *
316
+	 * @param $response
317
+	 * @param $parameters
318
+	 * @return array
319
+	 * @throws EE_Error
320
+	 * @throws ReflectionException
321
+	 */
322
+	public function validateOrder($response, $parameters): array
323
+	{
324
+		PayPalLogger::errorLog(
325
+			esc_html__('Validating Order Create:', 'event_espresso'),
326
+			[$this->request_url, $response],
327
+			$this->transaction->payment_method(),
328
+			false,
329
+			$this->transaction
330
+		);
331
+		if (! empty($response['error'])) {
332
+			return $response;
333
+		}
334
+		if (! isset($response['id'])) {
335
+			$message = esc_html__('Unexpected response. Unable to find the order.', 'event_espresso');
336
+			try {
337
+				PayPalLogger::errorLog(
338
+					$message,
339
+					[$this->request_url, $parameters, $response],
340
+					$this->transaction->payment_method()
341
+				);
342
+			} catch (EE_Error | ReflectionException $e) {
343
+				error_log("PayPalLogger Error: $message: " . json_encode($response));
344
+			}
345
+			return [
346
+				'error'    => $response['error'] ?? 'missing_order',
347
+				'message'  => $response['message'] ?? $message,
348
+				'response' => $response,
349
+			];
350
+		}
351
+		return $response;
352
+	}
353
+
354
+
355
+	/**
356
+	 * Check if PayPals response contains 'MISMATCH' errors.
357
+	 *
358
+	 * @return bool
359
+	 */
360
+	public function isMismatchError($response): bool
361
+	{
362
+		if (! isset($response['details']) || ! is_array($response['details'])) {
363
+			return false;
364
+		}
365
+
366
+		foreach($response['details'] as $detail) {
367
+			if (! empty($detail['issue'])) {
368
+				if (
369
+					strtoupper($detail['issue']) === 'ITEM_TOTAL_MISMATCH'
370
+					|| strtoupper($detail['issue']) === 'AMOUNT_MISMATCH'
371
+			) {
372
+					PayPalLogger::errorLog(
373
+						esc_html__('Mistmatch Error:', 'event_espresso'),
374
+						[$this->request_url, $response],
375
+						$this->transaction->payment_method(),
376
+						false,
377
+						$this->transaction
378
+					);
379
+					return true;
380
+				}
381
+			}
382
+		}
383
+		return false;
384
+	}
385
+
386
+
387
+	/**
388
+	 * Itemize the simplified payment breakdown list.
389
+	 *
390
+	 * @return array
391
+	 */
392
+	protected function getSimplifiedAmountBreakdown(): array
393
+	{
394
+		$tax_total = $this->transaction->tax_total();
395
+		$breakdown['item_total'] = [
396
+			'currency_code' => $this->currency_code,
397
+			'value'         => (string) CurrencyManager::normalizeValue($this->transaction->remaining() - $tax_total)
398
+		];
399
+		if ($tax_total > 0) {
400
+			$breakdown['tax_total'] = [
401
+				'currency_code' => $this->currency_code,
402
+				'value'         => (string) CurrencyManager::normalizeValue($tax_total)
403
+			];
404
+		}
405
+		return $breakdown;
406
+	}
407
+
408
+
409
+	/**
410
+	 * Generate single line item for full order.
411
+	 *
412
+	 * @return array
413
+	 */
414
+	protected function getSimplifiedItems(): array
415
+	{
416
+		// Simplified single line item.
417
+		$line_items             = [];
418
+		$primary_registrant     = $this->transaction->primary_registration();
419
+		$event_obj              = $primary_registrant->event_obj();
420
+		$name_and_description   = $this->gateway_data_formatter->formatOrderDescription($this->payment);
421 421
         
422
-        $line_items[]   = [
423
-            'name'        => substr(wp_strip_all_tags($name_and_description), 0, 125),
424
-            'quantity'    => 1,
425
-            'description' => substr(wp_strip_all_tags($name_and_description), 0, 2047),
426
-            'unit_amount' => [
427
-                'currency_code' => $this->currency_code,
428
-                'value'         => (string) CurrencyManager::normalizeValue($this->transaction->remaining() - $this->transaction->tax_total()),
429
-            ],
430
-            'category'    => 'DIGITAL_GOODS',
431
-        ];
432
-        return $line_items;
433
-    }
434
-
435
-    /**
436
-     * Generates an EE_Payment object but doesn't save it.
437
-     */
438
-    private function setPaymentPlaceholder(): void
439
-    {
440
-        $this->payment = EE_Payment::new_instance(
441
-            [
442
-                'STS_ID'        => EEM_Payment::status_id_pending,
443
-                'TXN_ID'        => $this->transaction->ID(),
444
-                'PMD_ID'        => $this->transaction->payment_method_ID(),
445
-                'PAY_amount'    => 0.00,
446
-                'PAY_timestamp' => time(),
447
-            ]
448
-        );
449
-    }
422
+		$line_items[]   = [
423
+			'name'        => substr(wp_strip_all_tags($name_and_description), 0, 125),
424
+			'quantity'    => 1,
425
+			'description' => substr(wp_strip_all_tags($name_and_description), 0, 2047),
426
+			'unit_amount' => [
427
+				'currency_code' => $this->currency_code,
428
+				'value'         => (string) CurrencyManager::normalizeValue($this->transaction->remaining() - $this->transaction->tax_total()),
429
+			],
430
+			'category'    => 'DIGITAL_GOODS',
431
+		];
432
+		return $line_items;
433
+	}
434
+
435
+	/**
436
+	 * Generates an EE_Payment object but doesn't save it.
437
+	 */
438
+	private function setPaymentPlaceholder(): void
439
+	{
440
+		$this->payment = EE_Payment::new_instance(
441
+			[
442
+				'STS_ID'        => EEM_Payment::status_id_pending,
443
+				'TXN_ID'        => $this->transaction->ID(),
444
+				'PMD_ID'        => $this->transaction->payment_method_ID(),
445
+				'PAY_amount'    => 0.00,
446
+				'PAY_timestamp' => time(),
447
+			]
448
+		);
449
+	}
450 450
 }
Please login to merge, or discard this patch.
Spacing   +11 added lines, -11 removed lines patch added patch discarded remove patch
@@ -111,7 +111,7 @@  discard block
 block discarded – undo
111 111
     {
112 112
         $sanitizer = new RequestSanitizer(new Basic());
113 113
         foreach ($billing_info as $item => $value) {
114
-            $this->billing_info[ $item ] = $sanitizer->clean($value);
114
+            $this->billing_info[$item] = $sanitizer->clean($value);
115 115
         }
116 116
     }
117 117
 
@@ -198,7 +198,7 @@  discard block
 block discarded – undo
198 198
         if (
199 199
             (
200 200
                 (defined('EE_PPC_USE_PAYMENT_FEES') && EE_PPC_USE_PAYMENT_FEES)
201
-                || (! defined('EE_PPC_USE_PAYMENT_FEES')
201
+                || ( ! defined('EE_PPC_USE_PAYMENT_FEES')
202 202
                     && $this->feature->allowed(FeatureFlag::USE_PAYMENT_PROCESSOR_FEES)
203 203
                 )
204 204
             )
@@ -298,11 +298,11 @@  discard block
 block discarded – undo
298 298
             'currency_code' => $this->currency_code,
299 299
             'value'         => (string) $this->items_total,
300 300
         ];
301
-        $breakdown['tax_total']  = [
301
+        $breakdown['tax_total'] = [
302 302
             'currency_code' => $this->currency_code,
303 303
             'value'         => (string) $this->tax_total,
304 304
         ];
305
-        $breakdown['discount']   = [
305
+        $breakdown['discount'] = [
306 306
             'currency_code' => $this->currency_code,
307 307
             'value'         => (string) abs($this->promos_total),
308 308
         ];
@@ -328,10 +328,10 @@  discard block
 block discarded – undo
328 328
             false,
329 329
             $this->transaction
330 330
         );
331
-        if (! empty($response['error'])) {
331
+        if ( ! empty($response['error'])) {
332 332
             return $response;
333 333
         }
334
-        if (! isset($response['id'])) {
334
+        if ( ! isset($response['id'])) {
335 335
             $message = esc_html__('Unexpected response. Unable to find the order.', 'event_espresso');
336 336
             try {
337 337
                 PayPalLogger::errorLog(
@@ -340,7 +340,7 @@  discard block
 block discarded – undo
340 340
                     $this->transaction->payment_method()
341 341
                 );
342 342
             } catch (EE_Error | ReflectionException $e) {
343
-                error_log("PayPalLogger Error: $message: " . json_encode($response));
343
+                error_log("PayPalLogger Error: $message: ".json_encode($response));
344 344
             }
345 345
             return [
346 346
                 'error'    => $response['error'] ?? 'missing_order',
@@ -359,12 +359,12 @@  discard block
 block discarded – undo
359 359
      */
360 360
     public function isMismatchError($response): bool
361 361
     {
362
-        if (! isset($response['details']) || ! is_array($response['details'])) {
362
+        if ( ! isset($response['details']) || ! is_array($response['details'])) {
363 363
             return false;
364 364
         }
365 365
 
366
-        foreach($response['details'] as $detail) {
367
-            if (! empty($detail['issue'])) {
366
+        foreach ($response['details'] as $detail) {
367
+            if ( ! empty($detail['issue'])) {
368 368
                 if (
369 369
                     strtoupper($detail['issue']) === 'ITEM_TOTAL_MISMATCH'
370 370
                     || strtoupper($detail['issue']) === 'AMOUNT_MISMATCH'
@@ -419,7 +419,7 @@  discard block
 block discarded – undo
419 419
         $event_obj              = $primary_registrant->event_obj();
420 420
         $name_and_description   = $this->gateway_data_formatter->formatOrderDescription($this->payment);
421 421
         
422
-        $line_items[]   = [
422
+        $line_items[] = [
423 423
             'name'        => substr(wp_strip_all_tags($name_and_description), 0, 125),
424 424
             'quantity'    => 1,
425 425
             'description' => substr(wp_strip_all_tags($name_and_description), 0, 2047),
Please login to merge, or discard this patch.
reg_steps/payment_options/EE_SPCO_Reg_Step_Payment_Options.class.php 2 patches
Indentation   +2801 added lines, -2801 removed lines patch added patch discarded remove patch
@@ -23,2805 +23,2805 @@
 block discarded – undo
23 23
  */
24 24
 class EE_SPCO_Reg_Step_Payment_Options extends EE_SPCO_Reg_Step
25 25
 {
26
-    protected ?EE_Line_Item_Display $line_item_display = null;
27
-
28
-    protected bool $handle_IPN_in_this_request = false;
29
-
30
-    /**
31
-     * @var EEM_Payment_Method|EEM_Base
32
-     * @since 5.0.42
33
-     */
34
-    protected EEM_Payment_Method $payment_method_model;
35
-
36
-
37
-    /**
38
-     * set_hooks - for hooking into EE Core, other modules, etc
39
-     *
40
-     * @return    void
41
-     */
42
-    public static function set_hooks()
43
-    {
44
-        add_filter(
45
-            'FHEE__SPCO__EE_Line_Item_Filter_Collection',
46
-            ['EE_SPCO_Reg_Step_Payment_Options', 'add_spco_line_item_filters']
47
-        );
48
-        add_action(
49
-            'wp_ajax_switch_spco_billing_form',
50
-            ['EE_SPCO_Reg_Step_Payment_Options', 'switch_spco_billing_form']
51
-        );
52
-        add_action(
53
-            'wp_ajax_nopriv_switch_spco_billing_form',
54
-            ['EE_SPCO_Reg_Step_Payment_Options', 'switch_spco_billing_form']
55
-        );
56
-        add_action('wp_ajax_save_payer_details', ['EE_SPCO_Reg_Step_Payment_Options', 'save_payer_details']);
57
-        add_action(
58
-            'wp_ajax_nopriv_save_payer_details',
59
-            ['EE_SPCO_Reg_Step_Payment_Options', 'save_payer_details']
60
-        );
61
-        add_action(
62
-            'wp_ajax_get_transaction_details_for_gateways',
63
-            ['EE_SPCO_Reg_Step_Payment_Options', 'get_transaction_details']
64
-        );
65
-        add_action(
66
-            'wp_ajax_nopriv_get_transaction_details_for_gateways',
67
-            ['EE_SPCO_Reg_Step_Payment_Options', 'get_transaction_details']
68
-        );
69
-        add_filter(
70
-            'FHEE__EED_Recaptcha___bypass_recaptcha__bypass_request_params_array',
71
-            ['EE_SPCO_Reg_Step_Payment_Options', 'bypass_recaptcha_for_load_payment_method']
72
-        );
73
-    }
74
-
75
-
76
-    /**
77
-     * @throws EE_Error
78
-     * @throws ReflectionException
79
-     */
80
-    public static function switch_spco_billing_form()
81
-    {
82
-        EED_Single_Page_Checkout::process_ajax_request('switch_payment_method');
83
-    }
84
-
85
-
86
-    /**
87
-     * @throws EE_Error
88
-     * @throws ReflectionException
89
-     */
90
-    public static function save_payer_details()
91
-    {
92
-        EED_Single_Page_Checkout::process_ajax_request('save_payer_details_via_ajax');
93
-    }
94
-
95
-
96
-    /**
97
-     * @throws EE_Error
98
-     * @throws ReflectionException
99
-     */
100
-    public static function get_transaction_details()
101
-    {
102
-        EED_Single_Page_Checkout::process_ajax_request('get_transaction_details_for_gateways');
103
-    }
104
-
105
-
106
-    /**
107
-     * @return array
108
-     */
109
-    public static function bypass_recaptcha_for_load_payment_method(): array
110
-    {
111
-        return [
112
-            'EESID'  => EE_Registry::instance()->SSN->id(),
113
-            'step'   => 'payment_options',
114
-            'action' => 'spco_billing_form',
115
-        ];
116
-    }
117
-
118
-
119
-    /**
120
-     * @param EE_Checkout $checkout
121
-     * @throws EE_Error
122
-     * @throws ReflectionException
123
-     */
124
-    public function __construct(EE_Checkout $checkout)
125
-    {
126
-        $this->request              = EED_Single_Page_Checkout::getRequest();
127
-        $this->_slug                = 'payment_options';
128
-        $this->_name                = esc_html__('Payment Options', 'event_espresso');
129
-        $this->_template            = SPCO_REG_STEPS_PATH . $this->_slug . '/payment_options_main.template.php';
130
-        $this->checkout             = $checkout;
131
-        $this->payment_method_model = EEM_Payment_Method::instance();
132
-        $this->_reset_success_message();
133
-        $this->set_instructions(
134
-            esc_html__(
135
-                'Please select a method of payment and provide any necessary billing information before proceeding.',
136
-                'event_espresso'
137
-            )
138
-        );
139
-    }
140
-
141
-
142
-    public function line_item_display(): ?EE_Line_Item_Display
143
-    {
144
-        return $this->line_item_display;
145
-    }
146
-
147
-
148
-    public function set_line_item_display(EE_Line_Item_Display $line_item_display)
149
-    {
150
-        $this->line_item_display = $line_item_display;
151
-    }
152
-
153
-
154
-    public function handle_IPN_in_this_request(): bool
155
-    {
156
-        return $this->handle_IPN_in_this_request;
157
-    }
158
-
159
-
160
-    public function set_handle_IPN_in_this_request(bool $handle_IPN_in_this_request)
161
-    {
162
-        $this->handle_IPN_in_this_request = filter_var($handle_IPN_in_this_request, FILTER_VALIDATE_BOOLEAN);
163
-    }
164
-
165
-
166
-    /**
167
-     * @return void
168
-     */
169
-    public function translate_js_strings()
170
-    {
171
-        EE_Registry::$i18n_js_strings['no_payment_method']      = esc_html__(
172
-            'Please select a method of payment in order to continue.',
173
-            'event_espresso'
174
-        );
175
-        EE_Registry::$i18n_js_strings['invalid_payment_method'] = esc_html__(
176
-            'A valid method of payment could not be determined. Please refresh the page and try again.',
177
-            'event_espresso'
178
-        );
179
-        EE_Registry::$i18n_js_strings['forwarding_to_offsite']  = esc_html__(
180
-            'Forwarding to Secure Payment Provider.',
181
-            'event_espresso'
182
-        );
183
-    }
184
-
185
-
186
-    /**
187
-     * @return void
188
-     * @throws EE_Error
189
-     * @throws ReflectionException
190
-     */
191
-    public function enqueue_styles_and_scripts()
192
-    {
193
-        $transaction = $this->checkout->transaction;
194
-        // if the transaction isn't set or nothing is owed on it, don't enqueue any JS
195
-        if (! $transaction instanceof EE_Transaction || EEH_Money::compare_floats($transaction->remaining(), 0)) {
196
-            return;
197
-        }
198
-        foreach ($this->checkout->available_payment_methods as $payment_method) {
199
-            $type_obj = $payment_method->type_obj();
200
-            if ($type_obj instanceof EE_PMT_Base) {
201
-                $billing_form = $type_obj->generate_new_billing_form($transaction);
202
-                if ($billing_form instanceof EE_Form_Section_Proper) {
203
-                    $billing_form->enqueue_js();
204
-                }
205
-            }
206
-        }
207
-    }
208
-
209
-
210
-    /**
211
-     * @return bool
212
-     * @throws EE_Error
213
-     * @throws InvalidArgumentException
214
-     * @throws ReflectionException
215
-     * @throws InvalidDataTypeException
216
-     * @throws InvalidInterfaceException
217
-     */
218
-    public function initialize_reg_step(): bool
219
-    {
220
-        // TODO: if /when we implement donations, then this will need overriding
221
-        if (
222
-            // don't need payment options for:
223
-            // registrations made via the admin
224
-            // completed transactions
225
-            // overpaid transactions
226
-            // $ 0.00 transactions(no payment required)
227
-            ! $this->checkout->payment_required()
228
-            // but do NOT remove if current action being called belongs to this reg step
229
-            && ! is_callable([$this, $this->checkout->action])
230
-            && ! $this->completed()
231
-        ) {
232
-            // and if so, then we no longer need the Payment Options step
233
-            if ($this->is_current_step()) {
234
-                $this->checkout->generate_reg_form = false;
235
-            }
236
-            $this->checkout->remove_reg_step($this->_slug);
237
-            // DEBUG LOG
238
-            // $this->checkout->log( __CLASS__, __FUNCTION__, __LINE__ );
239
-            return false;
240
-        }
241
-        // get all active payment methods
242
-        $this->checkout->available_payment_methods = $this->_get_available_payment_methods();
243
-        $this->setDefaultPaymentMethod($this->checkout->available_payment_methods);
244
-        return true;
245
-    }
246
-
247
-
248
-    /**
249
-     * @param array $payment_methods
250
-     * @return void
251
-     * @throws EE_Error
252
-     * @throws ReflectionException
253
-     * @since 5.0.42
254
-     */
255
-    private function setDefaultPaymentMethod(array $payment_methods): void {
256
-        foreach ($payment_methods as $payment_method) {
257
-            if ($payment_method instanceof EE_Payment_Method && $payment_method->open_by_default()) {
258
-                $this->checkout->default_payment_method = $payment_method;
259
-                return;
260
-            }
261
-        }
262
-    }
263
-
264
-
265
-    /**
266
-     * @return EE_Form_Section_Proper
267
-     * @throws EE_Error
268
-     * @throws InvalidArgumentException
269
-     * @throws ReflectionException
270
-     * @throws EntityNotFoundException
271
-     * @throws InvalidDataTypeException
272
-     * @throws InvalidInterfaceException
273
-     * @throws InvalidStatusException
274
-     */
275
-    public function generate_reg_form(): EE_Form_Section_Proper
276
-    {
277
-        // reset in case someone changes their mind
278
-        $this->_reset_selected_method_of_payment();
279
-        // set some defaults
280
-        $this->checkout->selected_method_of_payment = 'payments_closed';
281
-        $registrations_requiring_payment            = [];
282
-        $registrations_for_free_events              = [];
283
-        $registrations_requiring_pre_approval       = [];
284
-        $sold_out_events                            = [];
285
-        $insufficient_spaces_available              = [];
286
-        $no_payment_required                        = true;
287
-        // loop thru registrations to gather info
288
-        $registrations         = $this->checkout->transaction->registrations($this->checkout->reg_cache_where_params);
289
-        $ejected_registrations = EE_SPCO_Reg_Step_Payment_Options::find_registrations_that_lost_their_space(
290
-            $registrations,
291
-            $this->checkout->revisit
292
-        );
293
-        foreach ($registrations as $REG_ID => $registration) {
294
-            /** @var $registration EE_Registration */
295
-            // Skip if the registration has been moved
296
-            if ($registration->wasMoved()) {
297
-                continue;
298
-            }
299
-            // has this registration lost it's space ?
300
-            if (isset($ejected_registrations[ $REG_ID ])) {
301
-                if ($registration->event()->is_sold_out() || $registration->event()->is_sold_out(true)) {
302
-                    $sold_out_events[ $registration->event()->ID() ] = $registration->event();
303
-                } else {
304
-                    $insufficient_spaces_available[ $registration->event()->ID() ] = $registration->event();
305
-                }
306
-                continue;
307
-            }
308
-            // event requires admin approval
309
-            if ($registration->status_ID() === RegStatus::AWAITING_REVIEW) {
310
-                // add event to list of events with pre-approval reg status
311
-                $registrations_requiring_pre_approval[ $REG_ID ] = $registration;
312
-                do_action(
313
-                    'AHEE__EE_SPCO_Reg_Step_Payment_Options__generate_reg_form__event_requires_pre_approval',
314
-                    $registration->event(),
315
-                    $this
316
-                );
317
-                continue;
318
-            }
319
-            if (
320
-                $this->checkout->revisit
321
-                && $registration->status_ID() !== RegStatus::APPROVED
322
-                && (
323
-                    $registration->event()->is_sold_out()
324
-                    || $registration->event()->is_sold_out(true)
325
-                )
326
-            ) {
327
-                // add event to list of events that are sold out
328
-                $sold_out_events[ $registration->event()->ID() ] = $registration->event();
329
-                do_action(
330
-                    'AHEE__EE_SPCO_Reg_Step_Payment_Options__generate_reg_form__sold_out_event',
331
-                    $registration->event(),
332
-                    $this
333
-                );
334
-                continue;
335
-            }
336
-            // are they allowed to pay now and is there monies owing?
337
-            if ($registration->owes_monies_and_can_pay()) {
338
-                $registrations_requiring_payment[ $REG_ID ] = $registration;
339
-                do_action(
340
-                    'AHEE__EE_SPCO_Reg_Step_Payment_Options__generate_reg_form__event_requires_payment',
341
-                    $registration->event(),
342
-                    $this
343
-                );
344
-            } elseif (
345
-                ! $this->checkout->revisit
346
-                && $registration->status_ID() !== RegStatus::AWAITING_REVIEW
347
-                && $registration->ticket()->is_free()
348
-            ) {
349
-                $registrations_for_free_events[ $registration->ticket()->ID() ] = $registration;
350
-            }
351
-        }
352
-        $subsections = [];
353
-        // now decide which template to load
354
-        if (! empty($sold_out_events)) {
355
-            $subsections['sold_out_events'] = $this->_sold_out_events($sold_out_events);
356
-        }
357
-        if (! empty($insufficient_spaces_available)) {
358
-            $subsections['insufficient_space'] = $this->_insufficient_spaces_available(
359
-                $insufficient_spaces_available
360
-            );
361
-        }
362
-        if (! empty($registrations_requiring_pre_approval)) {
363
-            $subsections['registrations_requiring_pre_approval'] = $this->_registrations_requiring_pre_approval(
364
-                $registrations_requiring_pre_approval
365
-            );
366
-        }
367
-        if (! empty($registrations_for_free_events)) {
368
-            $subsections['no_payment_required'] = $this->_no_payment_required($registrations_for_free_events);
369
-        }
370
-        if (! empty($registrations_requiring_payment)) {
371
-            if ($this->checkout->amount_owing > 0) {
372
-                // check for method_of_payment before setting up line items
373
-                // so that surcharges can be applied to the line items based on the selected method of payment
374
-                $this->checkout->selected_method_of_payment = $this->_get_selected_method_of_payment();
375
-                do_action(
376
-                    'AHEE__EE_SPCO_Reg_Step_Payment_Options__generate_reg_form__registrations_requiring_payment',
377
-                    $this,
378
-                    $registrations_requiring_payment
379
-                );
380
-                // autoload Line_Item_Display classes
381
-                EEH_Autoloader::register_line_item_filter_autoloaders();
382
-                $line_item_filter_processor = new EE_Line_Item_Filter_Processor(
383
-                    apply_filters(
384
-                        'FHEE__SPCO__EE_Line_Item_Filter_Collection',
385
-                        new EE_Line_Item_Filter_Collection()
386
-                    ),
387
-                    $this->checkout->cart->get_grand_total()
388
-                );
389
-                /** @var EE_Line_Item $filtered_line_item_tree */
390
-                $filtered_line_item_tree = $line_item_filter_processor->process();
391
-                EEH_Autoloader::register_line_item_display_autoloaders();
392
-                $this->set_line_item_display(new EE_Line_Item_Display('spco'));
393
-                $subsections['payment_options'] = $this->_display_payment_options(
394
-                    $this->line_item_display->display_line_item(
395
-                        $filtered_line_item_tree,
396
-                        ['registrations' => $registrations]
397
-                    )
398
-                );
399
-                $this->checkout->amount_owing   = $filtered_line_item_tree->total();
400
-                $this->_apply_registration_payments_to_amount_owing($registrations);
401
-            }
402
-            $no_payment_required = false;
403
-        } else {
404
-            $this->_hide_reg_step_submit_button_if_revisit();
405
-        }
406
-        $this->_save_selected_method_of_payment();
407
-
408
-        $subsections['default_hidden_inputs'] = $this->reg_step_hidden_inputs();
409
-        $subsections['extra_hidden_inputs']   = $this->_extra_hidden_inputs($no_payment_required);
410
-
411
-        return new EE_Form_Section_Proper(
412
-            [
413
-                'name'            => $this->reg_form_name(),
414
-                'html_id'         => $this->reg_form_name(),
415
-                'subsections'     => $subsections,
416
-                'layout_strategy' => new EE_No_Layout(),
417
-            ]
418
-        );
419
-    }
420
-
421
-
422
-    /**
423
-     * add line item filters required for this reg step
424
-     * these filters are applied via this line in EE_SPCO_Reg_Step_Payment_Options::set_hooks():
425
-     *        add_filter( 'FHEE__SPCO__EE_Line_Item_Filter_Collection', array( 'EE_SPCO_Reg_Step_Payment_Options',
426
-     *        'add_spco_line_item_filters' ) ); so any code that wants to use the same set of filters during the
427
-     *        payment options reg step, can apply these filters via the following: apply_filters(
428
-     *        'FHEE__SPCO__EE_Line_Item_Filter_Collection', new EE_Line_Item_Filter_Collection() ) or to an existing
429
-     *        filter collection by passing that instead of instantiating a new collection
430
-     *
431
-     * @param EE_Line_Item_Filter_Collection $line_item_filter_collection
432
-     * @return EE_Line_Item_Filter_Collection
433
-     * @throws EE_Error
434
-     * @throws InvalidArgumentException
435
-     * @throws ReflectionException
436
-     * @throws EntityNotFoundException
437
-     * @throws InvalidDataTypeException
438
-     * @throws InvalidInterfaceException
439
-     * @throws InvalidStatusException
440
-     */
441
-    public static function add_spco_line_item_filters(
442
-        EE_Line_Item_Filter_Collection $line_item_filter_collection
443
-    ): EE_Line_Item_Filter_Collection {
444
-        if (! EE_Registry::instance()->SSN instanceof EE_Session) {
445
-            return $line_item_filter_collection;
446
-        }
447
-        if (! EE_Registry::instance()->SSN->checkout() instanceof EE_Checkout) {
448
-            return $line_item_filter_collection;
449
-        }
450
-        if (! EE_Registry::instance()->SSN->checkout()->transaction instanceof EE_Transaction) {
451
-            return $line_item_filter_collection;
452
-        }
453
-        $line_item_filter_collection->add(
454
-            new EE_Billable_Line_Item_Filter(
455
-                EE_SPCO_Reg_Step_Payment_Options::remove_ejected_registrations(
456
-                    EE_Registry::instance()->SSN->checkout()->transaction->registrations(
457
-                        EE_Registry::instance()->SSN->checkout()->reg_cache_where_params
458
-                    )
459
-                )
460
-            )
461
-        );
462
-        $line_item_filter_collection->add(new EE_Non_Zero_Line_Item_Filter());
463
-        return $line_item_filter_collection;
464
-    }
465
-
466
-
467
-    /**
468
-     * if a registrant has lost their potential space at an event due to lack of payment,
469
-     * then this method removes them from the list of registrations being paid for during this request
470
-     *
471
-     * @param EE_Registration[] $registrations
472
-     * @return EE_Registration[]
473
-     * @throws EE_Error
474
-     * @throws InvalidArgumentException
475
-     * @throws ReflectionException
476
-     * @throws EntityNotFoundException
477
-     * @throws InvalidDataTypeException
478
-     * @throws InvalidInterfaceException
479
-     * @throws InvalidStatusException
480
-     */
481
-    public static function remove_ejected_registrations(array $registrations): array
482
-    {
483
-        $ejected_registrations = EE_SPCO_Reg_Step_Payment_Options::find_registrations_that_lost_their_space(
484
-            $registrations,
485
-            EE_Registry::instance()->SSN->checkout()->revisit
486
-        );
487
-        foreach ($registrations as $REG_ID => $registration) {
488
-            // has this registration lost it's space ?
489
-            if (isset($ejected_registrations[ $REG_ID ])) {
490
-                unset($registrations[ $REG_ID ]);
491
-            }
492
-        }
493
-        return $registrations;
494
-    }
495
-
496
-
497
-    /**
498
-     * If a registrant chooses an offline payment method like Invoice,
499
-     * then no space is reserved for them at the event until they fully pay fo that site
500
-     * (unless the event's default reg status is set to APPROVED)
501
-     * if a registrant then later returns to pay, but the number of spaces available has been reduced due to sales,
502
-     * then this method will determine which registrations have lost the ability to complete the reg process.
503
-     *
504
-     * @param EE_Registration[] $registrations
505
-     * @param bool              $revisit
506
-     * @return array
507
-     * @throws EE_Error
508
-     * @throws InvalidArgumentException
509
-     * @throws ReflectionException
510
-     * @throws EntityNotFoundException
511
-     * @throws InvalidDataTypeException
512
-     * @throws InvalidInterfaceException
513
-     * @throws InvalidStatusException
514
-     */
515
-    public static function find_registrations_that_lost_their_space(array $registrations, bool $revisit = false): array
516
-    {
517
-        // registrations per event
518
-        $event_reg_count = [];
519
-        // spaces left per event
520
-        $event_spaces_remaining = [];
521
-        // tickets left sorted by ID
522
-        $tickets_remaining = [];
523
-        // registrations that have lost their space
524
-        $ejected_registrations = [];
525
-        foreach ($registrations as $REG_ID => $registration) {
526
-            if (
527
-                $registration->status_ID() === RegStatus::APPROVED
528
-                || apply_filters(
529
-                    'FHEE__EE_SPCO_Reg_Step_Payment_Options__find_registrations_that_lost_their_space__allow_reg_payment',
530
-                    false,
531
-                    $registration,
532
-                    $revisit
533
-                )
534
-            ) {
535
-                continue;
536
-            }
537
-            $EVT_ID = $registration->event_ID();
538
-            $ticket = $registration->ticket();
539
-            if (! isset($tickets_remaining[ $ticket->ID() ])) {
540
-                $tickets_remaining[ $ticket->ID() ] = $ticket->remaining();
541
-            }
542
-            if ($tickets_remaining[ $ticket->ID() ] > 0) {
543
-                if (! isset($event_reg_count[ $EVT_ID ])) {
544
-                    $event_reg_count[ $EVT_ID ] = 0;
545
-                }
546
-                $event_reg_count[ $EVT_ID ]++;
547
-                if (! isset($event_spaces_remaining[ $EVT_ID ])) {
548
-                    $event_spaces_remaining[ $EVT_ID ] = $registration->event()->spaces_remaining_for_sale();
549
-                }
550
-            }
551
-            if (
552
-                $revisit
553
-                && ($tickets_remaining[ $ticket->ID() ] === 0
554
-                    || $event_reg_count[ $EVT_ID ] > $event_spaces_remaining[ $EVT_ID ]
555
-                )
556
-            ) {
557
-                $ejected_registrations[ $REG_ID ] = $registration->event();
558
-                if ($registration->status_ID() !== RegStatus::WAIT_LIST) {
559
-                    /** @type EE_Registration_Processor $registration_processor */
560
-                    $registration_processor = EE_Registry::instance()->load_class('Registration_Processor');
561
-                    // at this point, we should have enough details about the registrant to consider the registration
562
-                    // NOT incomplete
563
-                    $registration_processor->manually_update_registration_status(
564
-                        $registration,
565
-                        RegStatus::WAIT_LIST
566
-                    );
567
-                }
568
-            }
569
-        }
570
-        return $ejected_registrations;
571
-    }
572
-
573
-
574
-    /**
575
-     * removes the HTML for the reg step submit button
576
-     * by replacing it with an empty string via filter callback
577
-     *
578
-     * @return void
579
-     */
580
-    protected function _hide_reg_step_submit_button_if_revisit()
581
-    {
582
-        if ($this->checkout->revisit) {
583
-            add_filter('FHEE__EE_SPCO_Reg_Step__reg_step_submit_button__sbmt_btn_html', '__return_empty_string');
584
-        }
585
-    }
586
-
587
-
588
-    /**
589
-     * displays notices regarding events that have sold out since the registrant first signed up
590
-     *
591
-     * @param EE_Event[] $sold_out_events_array
592
-     * @return EE_Form_Section_Proper
593
-     * @throws EE_Error
594
-     * @throws ReflectionException
595
-     */
596
-    private function _sold_out_events(array $sold_out_events_array = []): EE_Form_Section_Proper
597
-    {
598
-        // set some defaults
599
-        $this->checkout->selected_method_of_payment = 'events_sold_out';
600
-        $sold_out_events                            = '';
601
-        foreach ($sold_out_events_array as $sold_out_event) {
602
-            $sold_out_events .= EEH_HTML::li(
603
-                EEH_HTML::span(
604
-                    '  ' . $sold_out_event->name(),
605
-                    '',
606
-                    'dashicons dashicons-marker ee-icon-size-16 pink-text'
607
-                )
608
-            );
609
-        }
610
-        return new EE_Form_Section_Proper(
611
-            [
612
-                'layout_strategy' => new EE_Template_Layout(
613
-                    [
614
-                        'layout_template_file' => SPCO_REG_STEPS_PATH
615
-                            . $this->_slug
616
-                            . '/sold_out_events.template.php',
617
-                        'template_args'        => apply_filters(
618
-                            'FHEE__EE_SPCO_Reg_Step_Payment_Options___sold_out_events__template_args',
619
-                            [
620
-                                'sold_out_events'     => $sold_out_events,
621
-                                'sold_out_events_msg' => apply_filters(
622
-                                    'FHEE__EE_SPCO_Reg_Step_Payment_Options___sold_out_events__sold_out_events_msg',
623
-                                    sprintf(
624
-                                        esc_html__(
625
-                                            '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',
626
-                                            'event_espresso'
627
-                                        ),
628
-                                        '<strong>',
629
-                                        '</strong>',
630
-                                        '<br />'
631
-                                    )
632
-                                ),
633
-                            ]
634
-                        ),
635
-                    ]
636
-                ),
637
-            ]
638
-        );
639
-    }
640
-
641
-
642
-    /**
643
-     * displays notices regarding events that do not have enough remaining spaces
644
-     * to satisfy the current number of registrations looking to pay
645
-     *
646
-     * @param EE_Event[] $insufficient_spaces_events_array
647
-     * @return EE_Form_Section_Proper
648
-     * @throws EE_Error
649
-     * @throws ReflectionException
650
-     */
651
-    private function _insufficient_spaces_available(
652
-        array $insufficient_spaces_events_array = []
653
-    ): EE_Form_Section_Proper {
654
-        // set some defaults
655
-        $this->checkout->selected_method_of_payment = 'invoice';
656
-        $insufficient_space_events                  = '';
657
-        foreach ($insufficient_spaces_events_array as $event) {
658
-            if ($event instanceof EE_Event) {
659
-                $insufficient_space_events .= EEH_HTML::li(
660
-                    EEH_HTML::span(' ' . $event->name(), '', 'dashicons dashicons-marker ee-icon-size-16 pink-text')
661
-                );
662
-            }
663
-        }
664
-        return new EE_Form_Section_Proper(
665
-            [
666
-                'subsections'     => [
667
-                    'default_hidden_inputs' => $this->reg_step_hidden_inputs(),
668
-                    'extra_hidden_inputs'   => $this->_extra_hidden_inputs(),
669
-                ],
670
-                'layout_strategy' => new EE_Template_Layout(
671
-                    [
672
-                        'layout_template_file' => SPCO_REG_STEPS_PATH
673
-                            . $this->_slug
674
-                            . '/sold_out_events.template.php',
675
-                        'template_args'        => apply_filters(
676
-                            'FHEE__EE_SPCO_Reg_Step_Payment_Options___insufficient_spaces_available__template_args',
677
-                            [
678
-                                'sold_out_events'     => $insufficient_space_events,
679
-                                'sold_out_events_msg' => apply_filters(
680
-                                    'FHEE__EE_SPCO_Reg_Step_Payment_Options___insufficient_spaces_available__insufficient_space_msg',
681
-                                    esc_html__(
682
-                                        '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.',
683
-                                        'event_espresso'
684
-                                    )
685
-                                ),
686
-                            ]
687
-                        ),
688
-                    ]
689
-                ),
690
-            ]
691
-        );
692
-    }
693
-
694
-
695
-    /**
696
-     * @param array $registrations_requiring_pre_approval
697
-     * @return EE_Form_Section_Proper
698
-     * @throws EE_Error
699
-     * @throws EntityNotFoundException
700
-     * @throws ReflectionException
701
-     */
702
-    private function _registrations_requiring_pre_approval(
703
-        array $registrations_requiring_pre_approval = []
704
-    ): EE_Form_Section_Proper {
705
-        $events_requiring_pre_approval = [];
706
-        foreach ($registrations_requiring_pre_approval as $registration) {
707
-            if ($registration instanceof EE_Registration && $registration->event() instanceof EE_Event) {
708
-                $events_requiring_pre_approval[ $registration->event()->ID() ] = EEH_HTML::li(
709
-                    EEH_HTML::span(
710
-                        '',
711
-                        '',
712
-                        'dashicons dashicons-marker ee-icon-size-16 orange-text'
713
-                    )
714
-                    . EEH_HTML::span($registration->event()->name(), '', 'orange-text')
715
-                );
716
-            }
717
-        }
718
-        return new EE_Form_Section_Proper(
719
-            [
720
-                'layout_strategy' => new EE_Template_Layout(
721
-                    [
722
-                        'layout_template_file' => SPCO_REG_STEPS_PATH
723
-                            . $this->_slug
724
-                            . '/events_requiring_pre_approval.template.php', // layout_template
725
-                        'template_args'        => apply_filters(
726
-                            'FHEE__EE_SPCO_Reg_Step_Payment_Options___sold_out_events__template_args',
727
-                            [
728
-                                'events_requiring_pre_approval'     => implode('', $events_requiring_pre_approval),
729
-                                'events_requiring_pre_approval_msg' => apply_filters(
730
-                                    'FHEE__EE_SPCO_Reg_Step_Payment_Options___events_requiring_pre_approval__events_requiring_pre_approval_msg',
731
-                                    esc_html__(
732
-                                        '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.',
733
-                                        'event_espresso'
734
-                                    )
735
-                                ),
736
-                            ]
737
-                        ),
738
-                    ]
739
-                ),
740
-            ]
741
-        );
742
-    }
743
-
744
-
745
-    /**
746
-     * @param EE_Event[] $registrations_for_free_events
747
-     * @return EE_Form_Section_Proper
748
-     * @throws EE_Error
749
-     */
750
-    private function _no_payment_required(array $registrations_for_free_events = []): EE_Form_Section_Proper
751
-    {
752
-        // set some defaults
753
-        $this->checkout->selected_method_of_payment = 'no_payment_required';
754
-        // generate no_payment_required form
755
-        return new EE_Form_Section_Proper(
756
-            [
757
-                'layout_strategy' => new EE_Template_Layout(
758
-                    [
759
-                        'layout_template_file' => SPCO_REG_STEPS_PATH
760
-                            . $this->_slug
761
-                            . '/no_payment_required.template.php', // layout_template
762
-                        'template_args'        => apply_filters(
763
-                            'FHEE__EE_SPCO_Reg_Step_Payment_Options___no_payment_required__template_args',
764
-                            [
765
-                                'revisit'                       => $this->checkout->revisit,
766
-                                'registrations'                 => [],
767
-                                'ticket_count'                  => [],
768
-                                'registrations_for_free_events' => $registrations_for_free_events,
769
-                                'no_payment_required_msg'       => EEH_HTML::p(
770
-                                    esc_html__('This is a free event, so no billing will occur.', 'event_espresso')
771
-                                ),
772
-                            ]
773
-                        ),
774
-                    ]
775
-                ),
776
-            ]
777
-        );
778
-    }
779
-
780
-
781
-    /**
782
-     * @param string $transaction_details HTML from EE_SPCO_Line_Item_Display_Strategy
783
-     * @return EE_Form_Section_Proper
784
-     * @throws EE_Error
785
-     * @throws ReflectionException
786
-     */
787
-    private function _display_payment_options(string $transaction_details = ''): EE_Form_Section_Proper
788
-    {
789
-        // build payment options form
790
-        return apply_filters(
791
-            'FHEE__EE_SPCO_Reg_Step_Payment_Options___display_payment_options__payment_options_form',
792
-            new EE_Form_Section_Proper(
793
-                [
794
-                    'subsections'     => [
795
-                        'before_payment_options' => apply_filters(
796
-                            'FHEE__EE_SPCO_Reg_Step_Payment_Options___display_payment_options__before_payment_options',
797
-                            new EE_Form_Section_Proper(
798
-                                ['layout_strategy' => new EE_Div_Per_Section_Layout()]
799
-                            )
800
-                        ),
801
-                        'payment_options'        => $this->_setup_payment_options(),
802
-                        'after_payment_options'  => apply_filters(
803
-                            'FHEE__EE_SPCO_Reg_Step_Payment_Options___display_payment_options__after_payment_options',
804
-                            new EE_Form_Section_Proper(
805
-                                ['layout_strategy' => new EE_Div_Per_Section_Layout()]
806
-                            )
807
-                        ),
808
-                    ],
809
-                    'layout_strategy' => new EE_Template_Layout(
810
-                        [
811
-                            'layout_template_file' => $this->_template,
812
-                            'template_args'        => apply_filters(
813
-                                'FHEE__EE_SPCO_Reg_Step_Payment_Options___display_payment_options__template_args',
814
-                                [
815
-                                    'reg_count'                 => $this->line_item_display->total_items(),
816
-                                    'transaction_details'       => $transaction_details,
817
-                                    'available_payment_methods' => [],
818
-                                ]
819
-                            ),
820
-                        ]
821
-                    ),
822
-                ]
823
-            )
824
-        );
825
-    }
826
-
827
-
828
-    /**
829
-     * @param bool $no_payment_required
830
-     * @return EE_Form_Section_Proper
831
-     * @throws EE_Error
832
-     * @throws ReflectionException
833
-     */
834
-    private function _extra_hidden_inputs(bool $no_payment_required = true): EE_Form_Section_Proper
835
-    {
836
-        return new EE_Form_Section_Proper(
837
-            [
838
-                'html_id'         => 'ee-' . $this->slug() . '-extra-hidden-inputs',
839
-                'layout_strategy' => new EE_Div_Per_Section_Layout(),
840
-                'subsections'     => [
841
-                    'spco_no_payment_required' => new EE_Hidden_Input(
842
-                        [
843
-                            'normalization_strategy' => new EE_Boolean_Normalization(),
844
-                            'html_name'              => 'spco_no_payment_required',
845
-                            'html_id'                => 'spco-no-payment-required-payment_options',
846
-                            'default'                => $no_payment_required,
847
-                        ]
848
-                    ),
849
-                    'spco_transaction_id'      => new EE_Fixed_Hidden_Input(
850
-                        [
851
-                            'normalization_strategy' => new EE_Int_Normalization(),
852
-                            'html_name'              => 'spco_transaction_id',
853
-                            'html_id'                => 'spco-transaction-id',
854
-                            'default'                => $this->checkout->transaction->ID(),
855
-                        ]
856
-                    ),
857
-                ],
858
-            ]
859
-        );
860
-    }
861
-
862
-
863
-    /**
864
-     * @param array $registrations
865
-     * @throws EE_Error
866
-     * @throws ReflectionException
867
-     */
868
-    protected function _apply_registration_payments_to_amount_owing(array $registrations)
869
-    {
870
-        $payments = [];
871
-        foreach ($registrations as $registration) {
872
-            if ($registration instanceof EE_Registration && $registration->owes_monies_and_can_pay()) {
873
-                $payments += $registration->registration_payments();
874
-            }
875
-        }
876
-        if (! empty($payments)) {
877
-            foreach ($payments as $payment) {
878
-                if ($payment instanceof EE_Registration_Payment) {
879
-                    $this->checkout->amount_owing -= $payment->amount();
880
-                }
881
-            }
882
-        }
883
-    }
884
-
885
-
886
-    /**
887
-     * @param bool $force_reset
888
-     * @return void
889
-     */
890
-    private function _reset_selected_method_of_payment(bool $force_reset = false)
891
-    {
892
-        /** @var RequestInterface $request */
893
-        $request              = LoaderFactory::getLoader()->getShared(RequestInterface::class);
894
-        $reset_payment_method = $request->getRequestParam('reset_payment_method', $force_reset, 'bool');
895
-        if ($reset_payment_method) {
896
-            $this->checkout->selected_method_of_payment = null;
897
-            $this->checkout->payment_method             = null;
898
-            $this->checkout->billing_form               = null;
899
-            $this->_save_selected_method_of_payment();
900
-        }
901
-    }
902
-
903
-
904
-    /**
905
-     * stores the selected_method_of_payment in the session
906
-     * so that it's available for all subsequent requests including AJAX
907
-     *
908
-     * @param string $selected_method_of_payment
909
-     * @return void
910
-     */
911
-    private function _save_selected_method_of_payment(string $selected_method_of_payment = '')
912
-    {
913
-        $selected_method_of_payment = ! empty($selected_method_of_payment)
914
-            ? $selected_method_of_payment
915
-            : $this->checkout->selected_method_of_payment;
916
-        EE_Registry::instance()->SSN->set_session_data(
917
-            ['selected_method_of_payment' => $selected_method_of_payment]
918
-        );
919
-    }
920
-
921
-
922
-    /**
923
-     * @return EE_Form_Section_Proper
924
-     * @throws EE_Error
925
-     * @throws ReflectionException
926
-     */
927
-    public function _setup_payment_options(): EE_Form_Section_Proper
928
-    {
929
-        // load payment method classes
930
-        if (empty($this->checkout->available_payment_methods)) {
931
-            EE_Error::add_error(
932
-                apply_filters(
933
-                    'FHEE__EE_SPCO_Reg_Step_Payment_Options___setup_payment_options__error_message_no_payment_methods',
934
-                    sprintf(
935
-                        esc_html__(
936
-                            '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.',
937
-                            'event_espresso'
938
-                        ),
939
-                        '<br>',
940
-                        EE_Registry::instance()->CFG->organization->get_pretty('email')
941
-                    )
942
-                ),
943
-                __FILE__,
944
-                __FUNCTION__,
945
-                __LINE__
946
-            );
947
-        }
948
-        // switch up header depending on number of available payment methods
949
-        $payment_method_header     = count($this->checkout->available_payment_methods) > 1
950
-            ? apply_filters(
951
-                'FHEE__registration_page_payment_options__method_of_payment_hdr',
952
-                esc_html__('Please Select Your Method of Payment', 'event_espresso')
953
-            )
954
-            : apply_filters(
955
-                'FHEE__registration_page_payment_options__method_of_payment_hdr',
956
-                esc_html__('Method of Payment', 'event_espresso')
957
-            );
958
-        $available_payment_methods = [
959
-            // display the "Payment Method" header
960
-            'payment_method_header' => new EE_Form_Section_HTML(
961
-                apply_filters(
962
-                    'FHEE__EE_SPCO_Reg_Step_Payment_Options___setup_payment_options__payment_method_header',
963
-                    EEH_HTML::h4($payment_method_header, 'method-of-payment-hdr'),
964
-                    $payment_method_header
965
-                )
966
-            ),
967
-        ];
968
-        // the list of actual payment methods ( invoice, PayPal, etc ) in a  ( slug => HTML )  format
969
-        $available_payment_method_options = [];
970
-        $default_payment_method_option    = [];
971
-        // additional instructions to be displayed and hidden below payment methods (adding a clearing div to start)
972
-        $payment_methods_billing_info = [
973
-            new EE_Form_Section_HTML(
974
-                EEH_HTML::div('<br />', '', '', 'clear:both;')
975
-            ),
976
-        ];
977
-        // loop through payment methods
978
-        foreach ($this->checkout->available_payment_methods as $payment_method) {
979
-            if (! $payment_method instanceof EE_Payment_Method) {
980
-                continue;
981
-            }
982
-
983
-            $payment_method_button = EEH_HTML::img(
984
-                $payment_method->button_url(),
985
-                $payment_method->name(),
986
-                'spco-payment-method-' . $payment_method->slug() . '-btn-img',
987
-                'spco-payment-method-btn-img'
988
-            );
989
-            // check if any payment methods are set as default
990
-            // if payment method is already selected
991
-            // OR nothing is selected and this payment method is the default
992
-            if (
993
-                ($this->checkout->selected_method_of_payment === $payment_method->slug())
994
-                || (
995
-                    ! $this->checkout->selected_method_of_payment
996
-                    && $payment_method->open_by_default()
997
-                )
998
-            ) {
999
-                $this->checkout->selected_method_of_payment = $payment_method->slug();
1000
-                $this->_save_selected_method_of_payment();
1001
-                $default_payment_method_option[ $payment_method->slug() ] = $payment_method_button;
1002
-            } else {
1003
-                $available_payment_method_options[ $payment_method->slug() ] = $payment_method_button;
1004
-            }
1005
-            $payment_methods_billing_info[ $payment_method->slug() . '-info' ] =
1006
-                $this->_payment_method_billing_info(
1007
-                    $payment_method
1008
-                );
1009
-        }
1010
-        // prepend available_payment_method_options with default_payment_method_option so that it appears first in list
1011
-        // of PMs
1012
-        $available_payment_method_options = $default_payment_method_option + $available_payment_method_options;
1013
-        // now generate the actual form  inputs
1014
-        $available_payment_methods['available_payment_methods'] = $this->_available_payment_method_inputs(
1015
-            $available_payment_method_options
1016
-        );
1017
-        $available_payment_methods                              += $payment_methods_billing_info;
1018
-        // build the available payment methods form
1019
-        return new EE_Form_Section_Proper(
1020
-            [
1021
-                'html_id'         => 'spco-available-methods-of-payment-dv',
1022
-                'subsections'     => $available_payment_methods,
1023
-                'layout_strategy' => new EE_Div_Per_Section_Layout(),
1024
-            ]
1025
-        );
1026
-    }
1027
-
1028
-
1029
-    /**
1030
-     * @return EE_Payment_Method[]
1031
-     * @throws EE_Error
1032
-     * @throws ReflectionException
1033
-     */
1034
-    protected function _get_available_payment_methods(): array
1035
-    {
1036
-        if (! empty($this->checkout->available_payment_methods)) {
1037
-            return $this->checkout->available_payment_methods;
1038
-        }
1039
-        $available_payment_methods = [];
1040
-        // get all active payment methods
1041
-        $payment_methods = $this->payment_method_model->get_all_for_transaction(
1042
-            $this->checkout->transaction,
1043
-            EEM_Payment_Method::scope_cart
1044
-        );
1045
-        foreach ($payment_methods as $payment_method) {
1046
-            if ($payment_method instanceof EE_Payment_Method) {
1047
-                $available_payment_methods[ $payment_method->slug() ] = $payment_method;
1048
-            }
1049
-        }
1050
-        return $available_payment_methods;
1051
-    }
1052
-
1053
-
1054
-    /**
1055
-     * @param array $available_payment_method_options
1056
-     * @return EE_Form_Section_Proper
1057
-     * @throws EE_Error
1058
-     * @throws EE_Error
1059
-     */
1060
-    private function _available_payment_method_inputs(
1061
-        array $available_payment_method_options = []
1062
-    ): EE_Form_Section_Proper {
1063
-        // generate inputs
1064
-        return new EE_Form_Section_Proper(
1065
-            [
1066
-                'html_id'         => 'ee-available-payment-method-inputs',
1067
-                'layout_strategy' => new EE_Div_Per_Section_Layout(),
1068
-                'subsections'     => [
1069
-                    '' => new EE_Radio_Button_Input(
1070
-                        $available_payment_method_options,
1071
-                        [
1072
-                            'html_name'          => 'selected_method_of_payment',
1073
-                            'html_class'         => 'spco-payment-method',
1074
-                            'default'            => $this->checkout->selected_method_of_payment,
1075
-                            'label_size'         => 11,
1076
-                            'enforce_label_size' => true,
1077
-                        ]
1078
-                    ),
1079
-                ],
1080
-            ]
1081
-        );
1082
-    }
1083
-
1084
-
1085
-    /**
1086
-     * @param EE_Payment_Method $payment_method
1087
-     * @return EE_Form_Section_Proper
1088
-     * @throws EE_Error
1089
-     * @throws ReflectionException
1090
-     */
1091
-    private function _payment_method_billing_info(EE_Payment_Method $payment_method): EE_Form_Section_Proper
1092
-    {
1093
-        $currently_selected = $this->checkout->selected_method_of_payment === $payment_method->slug();
1094
-        // generate the billing form for payment method
1095
-        $billing_form                 = $currently_selected
1096
-            ? $this->_get_billing_form_for_payment_method($payment_method)
1097
-            : new EE_Form_Section_HTML();
1098
-        $this->checkout->billing_form = $currently_selected
1099
-            ? $billing_form
1100
-            : $this->checkout->billing_form;
1101
-        // it's all in the details
1102
-        $info_html = EEH_HTML::h3(
1103
-            esc_html__('Important information regarding your payment', 'event_espresso'),
1104
-            '',
1105
-            'spco-payment-method-hdr'
1106
-        );
1107
-        // add some info regarding the step, either from what's saved in the admin,
1108
-        // or a default string depending on whether the PM has a billing form or not
1109
-        if ($payment_method->description()) {
1110
-            $payment_method_info = $payment_method->description();
1111
-        } elseif ($billing_form instanceof EE_Billing_Info_Form) {
1112
-            $payment_method_info = sprintf(
1113
-                esc_html__(
1114
-                    'Please provide the following billing information, then click the "%1$s" button below in order to proceed.',
1115
-                    'event_espresso'
1116
-                ),
1117
-                $this->submit_button_text()
1118
-            );
1119
-        } else {
1120
-            $payment_method_info = sprintf(
1121
-                esc_html__('Please click the "%1$s" button below in order to proceed.', 'event_espresso'),
1122
-                $this->submit_button_text()
1123
-            );
1124
-        }
1125
-        $info_html .= EEH_HTML::div(
1126
-            apply_filters(
1127
-                'FHEE__EE_SPCO_Reg_Step_Payment_Options___payment_method_billing_info__payment_method_info',
1128
-                $payment_method_info
1129
-            ),
1130
-            '',
1131
-            'spco-payment-method-desc ee-attention'
1132
-        );
1133
-        return new EE_Form_Section_Proper(
1134
-            [
1135
-                'html_id'         => 'spco-payment-method-info-' . $payment_method->slug(),
1136
-                'html_class'      => 'spco-payment-method-info-dv',
1137
-                // only display the selected or default PM
1138
-                'html_style'      => $currently_selected ? '' : 'display:none;',
1139
-                'layout_strategy' => new EE_Div_Per_Section_Layout(),
1140
-                'subsections'     => [
1141
-                    'info'         => new EE_Form_Section_HTML($info_html),
1142
-                    'billing_form' => $currently_selected ? $billing_form : new EE_Form_Section_HTML(),
1143
-                ],
1144
-            ]
1145
-        );
1146
-    }
1147
-
1148
-
1149
-    /**
1150
-     * @return bool
1151
-     * @throws EE_Error
1152
-     * @throws InvalidArgumentException
1153
-     * @throws ReflectionException
1154
-     * @throws InvalidDataTypeException
1155
-     * @throws InvalidInterfaceException
1156
-     */
1157
-    public function get_billing_form_html_for_payment_method(): bool
1158
-    {
1159
-        // how have they chosen to pay?
1160
-        $this->checkout->selected_method_of_payment = $this->_get_selected_method_of_payment(true);
1161
-        $this->checkout->payment_method             = $this->_get_payment_method_for_selected_method_of_payment();
1162
-        if (! $this->checkout->payment_method instanceof EE_Payment_Method) {
1163
-            return false;
1164
-        }
1165
-        if (
1166
-            apply_filters(
1167
-                'FHEE__EE_SPCO_Reg_Step_Payment_Options__registration_checkout__selected_payment_method__display_success',
1168
-                false
1169
-            )
1170
-        ) {
1171
-            EE_Error::add_success(
1172
-                apply_filters(
1173
-                    'FHEE__Single_Page_Checkout__registration_checkout__selected_payment_method',
1174
-                    sprintf(
1175
-                        esc_html__(
1176
-                            'You have selected "%s" as your method of payment. Please note the important payment information below.',
1177
-                            'event_espresso'
1178
-                        ),
1179
-                        $this->checkout->payment_method->name()
1180
-                    )
1181
-                )
1182
-            );
1183
-        }
1184
-        // now generate billing form for selected method of payment
1185
-        $payment_method_billing_form = $this->_get_billing_form_for_payment_method($this->checkout->payment_method);
1186
-        // fill form with attendee info if applicable
1187
-        if (
1188
-            $payment_method_billing_form instanceof EE_Billing_Attendee_Info_Form
1189
-            && $this->checkout->transaction_has_primary_registrant()
1190
-        ) {
1191
-            $payment_method_billing_form->populate_from_attendee(
1192
-                $this->checkout->transaction->primary_registration()->attendee()
1193
-            );
1194
-        }
1195
-        // and debug content
1196
-        if (
1197
-            $payment_method_billing_form instanceof EE_Billing_Info_Form
1198
-            && $this->checkout->payment_method->type_obj() instanceof EE_PMT_Base
1199
-        ) {
1200
-            $payment_method_billing_form =
1201
-                $this->checkout->payment_method->type_obj()->apply_billing_form_debug_settings(
1202
-                    $payment_method_billing_form
1203
-                );
1204
-        }
1205
-        $billing_info = $payment_method_billing_form instanceof EE_Form_Section_Proper
1206
-            ? $payment_method_billing_form->get_html()
1207
-            : '';
1208
-        $this->checkout->json_response->set_return_data(['payment_method_info' => $billing_info]);
1209
-        // localize validation rules for main form
1210
-        $this->checkout->current_step->reg_form->localize_validation_rules();
1211
-        $this->checkout->json_response->add_validation_rules(EE_Form_Section_Proper::js_localization());
1212
-        return true;
1213
-    }
1214
-
1215
-
1216
-    /**
1217
-     * @param EE_Payment_Method $payment_method
1218
-     * @return EE_Billing_Info_Form|EE_Billing_Attendee_Info_Form|EE_Form_Section_HTML
1219
-     * @throws EE_Error
1220
-     * @throws ReflectionException
1221
-     */
1222
-    private function _get_billing_form_for_payment_method(EE_Payment_Method $payment_method)
1223
-    {
1224
-        $billing_form = $payment_method->type_obj()->billing_form(
1225
-            $this->checkout->transaction,
1226
-            ['amount_owing' => $this->checkout->amount_owing]
1227
-        );
1228
-        if ($billing_form instanceof EE_Billing_Info_Form) {
1229
-            if (
1230
-                apply_filters(
1231
-                    'FHEE__EE_SPCO_Reg_Step_Payment_Options__registration_checkout__selected_payment_method__display_success',
1232
-                    false
1233
-                )
1234
-                && $this->request->requestParamIsSet('payment_method')
1235
-            ) {
1236
-                EE_Error::add_success(
1237
-                    apply_filters(
1238
-                        'FHEE__Single_Page_Checkout__registration_checkout__selected_payment_method',
1239
-                        sprintf(
1240
-                            esc_html__(
1241
-                                'You have selected "%s" as your method of payment. Please note the important payment information below.',
1242
-                                'event_espresso'
1243
-                            ),
1244
-                            $payment_method->name()
1245
-                        )
1246
-                    )
1247
-                );
1248
-            }
1249
-            return apply_filters(
1250
-                'FHEE__EE_SPCO_Reg_Step_Payment_Options___get_billing_form_for_payment_method__billing_form',
1251
-                $billing_form,
1252
-                $payment_method
1253
-            );
1254
-        }
1255
-        // no actual billing form, so return empty HTML form section
1256
-        return new EE_Form_Section_HTML();
1257
-    }
1258
-
1259
-
1260
-    /**
1261
-     * @param boolean $required whether to throw an error if the "selected_method_of_payment"
1262
-     *                          is not found in the incoming request
1263
-     * @param string  $request_param
1264
-     * @return NULL|string
1265
-     * @throws EE_Error
1266
-     * @throws ReflectionException
1267
-     */
1268
-    private function _get_selected_method_of_payment(
1269
-        bool $required = false,
1270
-        string $request_param = 'selected_method_of_payment'
1271
-    ): ?string {
1272
-        // is selected_method_of_payment set in the request ?
1273
-        $selected_method_of_payment = $this->request->getRequestParam($request_param);
1274
-        if ($selected_method_of_payment) {
1275
-            // sanitize it
1276
-            $selected_method_of_payment = is_array($selected_method_of_payment)
1277
-                ? array_shift($selected_method_of_payment)
1278
-                : $selected_method_of_payment;
1279
-            $selected_method_of_payment = sanitize_text_field($selected_method_of_payment);
1280
-            // store it in the session so that it's available for all subsequent requests including AJAX
1281
-            $this->_save_selected_method_of_payment($selected_method_of_payment);
1282
-        } else {
1283
-            // or is it set in the session ?
1284
-            $selected_method_of_payment = EE_Registry::instance()->SSN->get_session_data(
1285
-                'selected_method_of_payment'
1286
-            );
1287
-        }
1288
-        if (
1289
-            empty($selected_method_of_payment)
1290
-            && $this->checkout->default_payment_method instanceof EE_Payment_Method
1291
-        ) {
1292
-            $selected_method_of_payment = $this->checkout->default_payment_method->slug();
1293
-        }
1294
-        // still no payment method?
1295
-        if (empty($selected_method_of_payment) && $required) {
1296
-            EE_Error::add_error(
1297
-                sprintf(
1298
-                    esc_html__(
1299
-                        '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.',
1300
-                        'event_espresso'
1301
-                    ),
1302
-                    '<br/>',
1303
-                    '<br/>',
1304
-                    EE_Registry::instance()->CFG->organization->get_pretty('email')
1305
-                ),
1306
-                __FILE__,
1307
-                __FUNCTION__,
1308
-                __LINE__
1309
-            );
1310
-            return null;
1311
-        }
1312
-        return $selected_method_of_payment;
1313
-    }
1314
-
1315
-
1316
-
1317
-
1318
-
1319
-
1320
-    /********************************************************************************************************/
1321
-    /***********************************  SWITCH PAYMENT METHOD  ************************************/
1322
-    /********************************************************************************************************/
1323
-
1324
-
1325
-    /**
1326
-     * @return bool
1327
-     * @throws EE_Error
1328
-     * @throws ReflectionException
1329
-     */
1330
-    public function switch_payment_method()
1331
-    {
1332
-        if (! $this->_verify_payment_method_is_set()) {
1333
-            return false;
1334
-        }
1335
-        if (
1336
-            apply_filters(
1337
-                'FHEE__EE_SPCO_Reg_Step_Payment_Options__registration_checkout__selected_payment_method__display_success',
1338
-                false
1339
-            )
1340
-        ) {
1341
-            EE_Error::add_success(
1342
-                apply_filters(
1343
-                    'FHEE__Single_Page_Checkout__registration_checkout__selected_payment_method',
1344
-                    sprintf(
1345
-                        esc_html__(
1346
-                            'You have selected "%s" as your method of payment. Please note the important payment information below.',
1347
-                            'event_espresso'
1348
-                        ),
1349
-                        $this->checkout->payment_method->name()
1350
-                    )
1351
-                )
1352
-            );
1353
-        }
1354
-        // generate billing form for selected method of payment if it hasn't been done already
1355
-        if ($this->checkout->payment_method->type_obj()->has_billing_form()) {
1356
-            $this->checkout->billing_form = $this->_get_billing_form_for_payment_method(
1357
-                $this->checkout->payment_method
1358
-            );
1359
-        }
1360
-        // fill form with attendee info if applicable
1361
-        if (
1362
-            apply_filters(
1363
-                'FHEE__populate_billing_form_fields_from_attendee',
1364
-                (
1365
-                    $this->checkout->billing_form instanceof EE_Billing_Attendee_Info_Form
1366
-                    && $this->checkout->transaction_has_primary_registrant()
1367
-                ),
1368
-                $this->checkout->billing_form,
1369
-                $this->checkout->transaction
1370
-            )
1371
-        ) {
1372
-            $this->checkout->billing_form->populate_from_attendee(
1373
-                $this->checkout->transaction->primary_registration()->attendee()
1374
-            );
1375
-        }
1376
-        // and debug content
1377
-        if (
1378
-            $this->checkout->billing_form instanceof EE_Billing_Info_Form
1379
-            && $this->checkout->payment_method->type_obj() instanceof EE_PMT_Base
1380
-        ) {
1381
-            $this->checkout->billing_form =
1382
-                $this->checkout->payment_method->type_obj()->apply_billing_form_debug_settings(
1383
-                    $this->checkout->billing_form
1384
-                );
1385
-        }
1386
-        // get HTML and validation rules for form
1387
-        if ($this->checkout->billing_form instanceof EE_Form_Section_Proper) {
1388
-            $this->checkout->json_response->set_return_data(
1389
-                ['payment_method_info' => $this->checkout->billing_form->get_html()]
1390
-            );
1391
-            // localize validation rules for main form
1392
-            $this->checkout->billing_form->localize_validation_rules(true);
1393
-            $this->checkout->json_response->add_validation_rules(EE_Form_Section_Proper::js_localization());
1394
-        } else {
1395
-            $this->checkout->json_response->set_return_data(['payment_method_info' => '']);
1396
-        }
1397
-        // prevents advancement to next step
1398
-        $this->checkout->continue_reg = false;
1399
-        return true;
1400
-    }
1401
-
1402
-
1403
-    /**
1404
-     * @return bool
1405
-     * @throws EE_Error
1406
-     * @throws InvalidArgumentException
1407
-     * @throws ReflectionException
1408
-     * @throws InvalidDataTypeException
1409
-     * @throws InvalidInterfaceException
1410
-     */
1411
-    protected function _verify_payment_method_is_set(): bool
1412
-    {
1413
-        // generate billing form for selected method of payment if it hasn't been done already
1414
-        if (empty($this->checkout->selected_method_of_payment)) {
1415
-            // how have they chosen to pay?
1416
-            $this->checkout->selected_method_of_payment = $this->_get_selected_method_of_payment(true);
1417
-        } else {
1418
-            // choose your own adventure based on method_of_payment
1419
-            switch ($this->checkout->selected_method_of_payment) {
1420
-                case 'events_sold_out':
1421
-                    EE_Error::add_attention(
1422
-                        apply_filters(
1423
-                            'FHEE__EE_SPCO_Reg_Step_Payment_Options___verify_payment_method_is_set__sold_out_events_msg',
1424
-                            esc_html__(
1425
-                                '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.',
1426
-                                'event_espresso'
1427
-                            )
1428
-                        ),
1429
-                        __FILE__,
1430
-                        __FUNCTION__,
1431
-                        __LINE__
1432
-                    );
1433
-                    return false;
1434
-                case 'payments_closed':
1435
-                    EE_Error::add_attention(
1436
-                        apply_filters(
1437
-                            'FHEE__EE_SPCO_Reg_Step_Payment_Options___verify_payment_method_is_set__payments_closed_msg',
1438
-                            esc_html__(
1439
-                                '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.',
1440
-                                'event_espresso'
1441
-                            )
1442
-                        ),
1443
-                        __FILE__,
1444
-                        __FUNCTION__,
1445
-                        __LINE__
1446
-                    );
1447
-                    return false;
1448
-                case 'no_payment_required':
1449
-                    EE_Error::add_attention(
1450
-                        apply_filters(
1451
-                            'FHEE__EE_SPCO_Reg_Step_Payment_Options___verify_payment_method_is_set__no_payment_required_msg',
1452
-                            esc_html__(
1453
-                                '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.',
1454
-                                'event_espresso'
1455
-                            )
1456
-                        ),
1457
-                        __FILE__,
1458
-                        __FUNCTION__,
1459
-                        __LINE__
1460
-                    );
1461
-                    return false;
1462
-                default:
1463
-            }
1464
-        }
1465
-        // verify payment method
1466
-        if (! $this->checkout->payment_method instanceof EE_Payment_Method) {
1467
-            // get payment method for selected method of payment
1468
-            $this->checkout->payment_method = $this->_get_payment_method_for_selected_method_of_payment();
1469
-        }
1470
-        return $this->checkout->payment_method instanceof EE_Payment_Method;
1471
-    }
1472
-
1473
-
1474
-
1475
-    /********************************************************************************************************/
1476
-    /***************************************  SAVE PAYER DETAILS  ****************************************/
1477
-    /********************************************************************************************************/
1478
-
1479
-
1480
-    /**
1481
-     * @return void
1482
-     * @throws EE_Error
1483
-     * @throws InvalidArgumentException
1484
-     * @throws ReflectionException
1485
-     * @throws RuntimeException
1486
-     * @throws InvalidDataTypeException
1487
-     * @throws InvalidInterfaceException
1488
-     */
1489
-    public function save_payer_details_via_ajax()
1490
-    {
1491
-        if (! $this->_verify_payment_method_is_set()) {
1492
-            return;
1493
-        }
1494
-        // generate billing form for selected method of payment if it hasn't been done already
1495
-        if ($this->checkout->payment_method->type_obj()->has_billing_form()) {
1496
-            $this->checkout->billing_form = $this->_get_billing_form_for_payment_method(
1497
-                $this->checkout->payment_method
1498
-            );
1499
-        }
1500
-        // generate primary attendee from payer info if applicable
1501
-        if (! $this->checkout->transaction_has_primary_registrant()) {
1502
-            $attendee = $this->_create_attendee_from_request_data();
1503
-            if ($attendee instanceof EE_Attendee) {
1504
-                foreach ($this->checkout->transaction->registrations() as $registration) {
1505
-                    if ($registration->is_primary_registrant()) {
1506
-                        $this->checkout->primary_attendee_obj = $attendee;
1507
-                        $registration->_add_relation_to($attendee, 'Attendee');
1508
-                        $registration->set_attendee_id($attendee->ID());
1509
-                        $registration->update_cache_after_object_save('Attendee', $attendee);
1510
-                    }
1511
-                }
1512
-            }
1513
-        }
1514
-    }
1515
-
1516
-
1517
-    /**
1518
-     * uses info from alternate GET or POST data (such as AJAX) to create a new attendee
1519
-     *
1520
-     * @return EE_Attendee
1521
-     * @throws EE_Error
1522
-     * @throws InvalidArgumentException
1523
-     * @throws ReflectionException
1524
-     * @throws InvalidDataTypeException
1525
-     * @throws InvalidInterfaceException
1526
-     */
1527
-    protected function _create_attendee_from_request_data(): EE_Attendee
1528
-    {
1529
-        // get State ID
1530
-        $STA_ID = $this->request->getRequestParam('state');
1531
-        if (! empty($STA_ID)) {
1532
-            // can we get state object from name ?
1533
-            EE_Registry::instance()->load_model('State');
1534
-            $state  = EEM_State::instance()->get_col([['STA_name' => $STA_ID], 'limit' => 1], 'STA_ID');
1535
-            $STA_ID = is_array($state) && ! empty($state) ? reset($state) : $STA_ID;
1536
-        }
1537
-        // get Country ISO
1538
-        $CNT_ISO = $this->request->getRequestParam('country');
1539
-        if (! empty($CNT_ISO)) {
1540
-            // can we get country object from name ?
1541
-            EE_Registry::instance()->load_model('Country');
1542
-            $country = EEM_Country::instance()->get_col(
1543
-                [['CNT_name' => $CNT_ISO], 'limit' => 1],
1544
-                'CNT_ISO'
1545
-            );
1546
-            $CNT_ISO = is_array($country) && ! empty($country) ? reset($country) : $CNT_ISO;
1547
-        }
1548
-        // grab attendee data
1549
-        $attendee_data = [
1550
-            'ATT_fname'    => $this->request->getRequestParam('first_name'),
1551
-            'ATT_lname'    => $this->request->getRequestParam('last_name'),
1552
-            'ATT_email'    => $this->request->getRequestParam('email'),
1553
-            'ATT_address'  => $this->request->getRequestParam('address'),
1554
-            'ATT_address2' => $this->request->getRequestParam('address2'),
1555
-            'ATT_city'     => $this->request->getRequestParam('city'),
1556
-            'STA_ID'       => $STA_ID,
1557
-            'CNT_ISO'      => $CNT_ISO,
1558
-            'ATT_zip'      => $this->request->getRequestParam('zip'),
1559
-            'ATT_phone'    => $this->request->getRequestParam('phone'),
1560
-        ];
1561
-        // validate the email address since it is the most important piece of info
1562
-        if (empty($attendee_data['ATT_email'])) {
1563
-            EE_Error::add_error(
1564
-                esc_html__('An invalid email address was submitted.', 'event_espresso'),
1565
-                __FILE__,
1566
-                __FUNCTION__,
1567
-                __LINE__
1568
-            );
1569
-        }
1570
-        // does this attendee already exist in the db ? we're searching using a combination of first name, last name,
1571
-        // AND email address
1572
-        if (
1573
-            ! empty($attendee_data['ATT_fname'])
1574
-            && ! empty($attendee_data['ATT_lname'])
1575
-            && ! empty($attendee_data['ATT_email'])
1576
-        ) {
1577
-            $existing_attendee = EEM_Attendee::instance()->find_existing_attendee(
1578
-                [
1579
-                    'ATT_fname' => $attendee_data['ATT_fname'],
1580
-                    'ATT_lname' => $attendee_data['ATT_lname'],
1581
-                    'ATT_email' => $attendee_data['ATT_email'],
1582
-                ]
1583
-            );
1584
-            if ($existing_attendee instanceof EE_Attendee) {
1585
-                return $existing_attendee;
1586
-            }
1587
-        }
1588
-        // no existing attendee? kk let's create a new one
1589
-        // kinda lame, but we need a first and last name to create an attendee, so use the email address if those
1590
-        // don't exist
1591
-        $attendee_data['ATT_fname'] = ! empty($attendee_data['ATT_fname'])
1592
-            ? $attendee_data['ATT_fname']
1593
-            : $attendee_data['ATT_email'];
1594
-        $attendee_data['ATT_lname'] = ! empty($attendee_data['ATT_lname'])
1595
-            ? $attendee_data['ATT_lname']
1596
-            : $attendee_data['ATT_email'];
1597
-        return EE_Attendee::new_instance($attendee_data);
1598
-    }
1599
-
1600
-
1601
-
1602
-    /********************************************************************************************************/
1603
-    /****************************************  PROCESS REG STEP  *****************************************/
1604
-    /********************************************************************************************************/
1605
-
1606
-
1607
-    /**
1608
-     * @return bool
1609
-     * @throws EE_Error
1610
-     * @throws InvalidArgumentException
1611
-     * @throws ReflectionException
1612
-     * @throws EntityNotFoundException
1613
-     * @throws InvalidDataTypeException
1614
-     * @throws InvalidInterfaceException
1615
-     * @throws InvalidStatusException
1616
-     */
1617
-    public function process_reg_step(): bool
1618
-    {
1619
-        // how have they chosen to pay?
1620
-        $this->checkout->selected_method_of_payment = $this->checkout->transaction->is_free()
1621
-            ? 'no_payment_required'
1622
-            : $this->_get_selected_method_of_payment(true);
1623
-        // choose your own adventure based on method_of_payment
1624
-        switch ($this->checkout->selected_method_of_payment) {
1625
-            case 'events_sold_out':
1626
-                $this->checkout->redirect     = true;
1627
-                $this->checkout->redirect_url = $this->checkout->cancel_page_url;
1628
-                $this->checkout->json_response->set_redirect_url($this->checkout->redirect_url);
1629
-                // mark this reg step as completed
1630
-                $this->set_completed();
1631
-                return false;
1632
-
1633
-            case 'payments_closed':
1634
-                if (
1635
-                    apply_filters(
1636
-                        'FHEE__EE_SPCO_Reg_Step_Payment_Options__process_reg_step__payments_closed__display_success',
1637
-                        false
1638
-                    )
1639
-                ) {
1640
-                    EE_Error::add_success(
1641
-                        esc_html__('no payment required at this time.', 'event_espresso'),
1642
-                        __FILE__,
1643
-                        __FUNCTION__,
1644
-                        __LINE__
1645
-                    );
1646
-                }
1647
-                // mark this reg step as completed
1648
-                $this->set_completed();
1649
-                return true;
1650
-
1651
-            case 'no_payment_required':
1652
-                if (
1653
-                    apply_filters(
1654
-                        'FHEE__EE_SPCO_Reg_Step_Payment_Options__process_reg_step__no_payment_required__display_success',
1655
-                        false
1656
-                    )
1657
-                ) {
1658
-                    EE_Error::add_success(
1659
-                        esc_html__('no payment required.', 'event_espresso'),
1660
-                        __FILE__,
1661
-                        __FUNCTION__,
1662
-                        __LINE__
1663
-                    );
1664
-                }
1665
-                // mark this reg step as completed
1666
-                $this->set_completed();
1667
-                return true;
1668
-
1669
-            default:
1670
-                $registrations         = EE_Registry::instance()->SSN->checkout()->transaction->registrations(
1671
-                    EE_Registry::instance()->SSN->checkout()->reg_cache_where_params
1672
-                );
1673
-                $ejected_registrations = EE_SPCO_Reg_Step_Payment_Options::find_registrations_that_lost_their_space(
1674
-                    $registrations,
1675
-                    EE_Registry::instance()->SSN->checkout()->revisit
1676
-                );
1677
-                // calculate difference between the two arrays
1678
-                $registrations = array_diff($registrations, $ejected_registrations);
1679
-                if (empty($registrations)) {
1680
-                    $this->_redirect_because_event_sold_out();
1681
-                    return false;
1682
-                }
1683
-                $payment = $this->_process_payment();
1684
-                if ($payment instanceof EE_Payment) {
1685
-                    $this->checkout->continue_reg = true;
1686
-                    $this->_maybe_set_completed($payment);
1687
-                } else {
1688
-                    $this->checkout->continue_reg = false;
1689
-                }
1690
-                return $payment instanceof EE_Payment;
1691
-        }
1692
-    }
1693
-
1694
-
1695
-    /**
1696
-     * @return void
1697
-     */
1698
-    protected function _redirect_because_event_sold_out()
1699
-    {
1700
-        $this->checkout->continue_reg = false;
1701
-        // set redirect URL
1702
-        $this->checkout->redirect_url = add_query_arg(
1703
-            ['e_reg_url_link' => $this->checkout->reg_url_link],
1704
-            $this->checkout->current_step->reg_step_url()
1705
-        );
1706
-        $this->checkout->json_response->set_redirect_url($this->checkout->redirect_url);
1707
-    }
1708
-
1709
-
1710
-    /**
1711
-     * @param EE_Payment $payment
1712
-     * @return void
1713
-     * @throws EE_Error
1714
-     */
1715
-    protected function _maybe_set_completed(EE_Payment $payment)
1716
-    {
1717
-        // Do we need to redirect them? If so, there's more work to be done.
1718
-        if (! $payment->redirect_url()) {
1719
-            $this->set_completed();
1720
-        }
1721
-    }
1722
-
1723
-
1724
-    /**
1725
-     * this is the final step after a user  revisits the site to retry a payment
1726
-     *
1727
-     * @return bool
1728
-     * @throws EE_Error
1729
-     * @throws InvalidArgumentException
1730
-     * @throws ReflectionException
1731
-     * @throws EntityNotFoundException
1732
-     * @throws InvalidDataTypeException
1733
-     * @throws InvalidInterfaceException
1734
-     * @throws InvalidStatusException
1735
-     */
1736
-    public function update_reg_step(): bool
1737
-    {
1738
-        $success = true;
1739
-        // if payment required
1740
-        if ($this->checkout->transaction->total() > 0) {
1741
-            do_action(
1742
-                'AHEE__EE_Single_Page_Checkout__process_finalize_registration__before_gateway',
1743
-                $this->checkout->transaction
1744
-            );
1745
-            // attempt payment via payment method
1746
-            $success = $this->process_reg_step();
1747
-        }
1748
-        if ($success && ! $this->checkout->redirect) {
1749
-            $this->checkout->cart->get_grand_total()->save_this_and_descendants_to_txn(
1750
-                $this->checkout->transaction->ID()
1751
-            );
1752
-            // set return URL
1753
-            $this->checkout->redirect_url = add_query_arg(
1754
-                ['e_reg_url_link' => $this->checkout->reg_url_link],
1755
-                $this->checkout->thank_you_page_url
1756
-            );
1757
-        }
1758
-        return $success;
1759
-    }
1760
-
1761
-
1762
-    /**
1763
-     * @return EE_Payment|bool|null
1764
-     * @throws EE_Error
1765
-     * @throws InvalidArgumentException
1766
-     * @throws ReflectionException
1767
-     * @throws RuntimeException
1768
-     * @throws InvalidDataTypeException
1769
-     * @throws InvalidInterfaceException
1770
-     */
1771
-    private function _process_payment()
1772
-    {
1773
-        // basically confirm that the event hasn't sold out since they hit the page
1774
-        if (! $this->_last_second_ticket_verifications()) {
1775
-            return null;
1776
-        }
1777
-        // ya gotta make a choice man
1778
-        if (empty($this->checkout->selected_method_of_payment)) {
1779
-            $this->checkout->json_response->set_plz_select_method_of_payment(
1780
-                esc_html__('Please select a method of payment before proceeding.', 'event_espresso')
1781
-            );
1782
-            return null;
1783
-        }
1784
-        // get EE_Payment_Method object
1785
-        if (! $this->checkout->payment_method = $this->_get_payment_method_for_selected_method_of_payment()) {
1786
-            return null;
1787
-        }
1788
-        // setup billing form
1789
-        if ($this->checkout->payment_method->type_obj()->has_billing_form()) {
1790
-            $this->checkout->billing_form = $this->_get_billing_form_for_payment_method(
1791
-                $this->checkout->payment_method
1792
-            );
1793
-            // bad billing form ?
1794
-            if (! $this->_billing_form_is_valid()) {
1795
-                return null;
1796
-            }
1797
-        }
1798
-        // ensure primary registrant has been fully processed
1799
-        if (! $this->_setup_primary_registrant_prior_to_payment()) {
1800
-            return null;
1801
-        }
1802
-        // if session is close to expiring (under 10 minutes by default)
1803
-        if ((time() - EE_Registry::instance()->SSN->expiration()) < EE_Registry::instance()->SSN->extension()) {
1804
-            // add some time to session expiration so that payment can be completed
1805
-            EE_Registry::instance()->SSN->extend_expiration();
1806
-        }
1807
-        /** @type EE_Transaction_Processor $transaction_processor */
1808
-        // $transaction_processor = EE_Registry::instance()->load_class( 'Transaction_Processor' );
1809
-        // in case a registrant leaves to an Off-Site Gateway and never returns, we want to approve any registrations
1810
-        // for events with a default reg status of Approved
1811
-        // $transaction_processor->toggle_registration_statuses_for_default_approved_events(
1812
-        //      $this->checkout->transaction, $this->checkout->reg_cache_where_params
1813
-        // );
1814
-        // attempt payment
1815
-        $payment = $this->_attempt_payment($this->checkout->payment_method);
1816
-        // process results
1817
-        $payment = $this->_validate_payment($payment);
1818
-        $payment = $this->_post_payment_processing($payment);
1819
-        // verify payment
1820
-        if ($payment instanceof EE_Payment) {
1821
-            // store that for later
1822
-            $this->checkout->payment = $payment;
1823
-            // we can also consider the TXN to not have been failed, so temporarily upgrade its status to abandoned
1824
-            $this->checkout->transaction->toggle_failed_transaction_status();
1825
-            $payment_status = $payment->status();
1826
-            if (
1827
-                $payment_status === EEM_Payment::status_id_approved
1828
-                || $payment_status === EEM_Payment::status_id_pending
1829
-            ) {
1830
-                return $payment;
1831
-            }
1832
-            return null;
1833
-        }
1834
-        if ($payment === true) {
1835
-            // please note that offline payment methods will NOT make a payment,
1836
-            // but instead just mark themselves as the PMD_ID on the transaction, and return true
1837
-            $this->checkout->payment = true;
1838
-            return true;
1839
-        }
1840
-        // where's my money?
1841
-        return null;
1842
-    }
1843
-
1844
-
1845
-    /**
1846
-     * @return bool
1847
-     * @throws EE_Error
1848
-     * @throws ReflectionException
1849
-     */
1850
-    protected function _last_second_ticket_verifications(): bool
1851
-    {
1852
-        // don't bother re-validating if not a return visit
1853
-        if (! $this->checkout->revisit) {
1854
-            return true;
1855
-        }
1856
-        $registrations = $this->checkout->transaction->registrations();
1857
-        if (empty($registrations)) {
1858
-            return false;
1859
-        }
1860
-        foreach ($registrations as $registration) {
1861
-            if ($registration instanceof EE_Registration && ! $registration->is_approved()) {
1862
-                $event = $registration->event_obj();
1863
-                if ($event instanceof EE_Event && $event->is_sold_out(true)) {
1864
-                    EE_Error::add_error(
1865
-                        apply_filters(
1866
-                            'FHEE__EE_SPCO_Reg_Step_Payment_Options___last_second_ticket_verifications__sold_out_events_msg',
1867
-                            sprintf(
1868
-                                esc_html__(
1869
-                                    '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.',
1870
-                                    'event_espresso'
1871
-                                ),
1872
-                                $event->name()
1873
-                            )
1874
-                        ),
1875
-                        __FILE__,
1876
-                        __FUNCTION__,
1877
-                        __LINE__
1878
-                    );
1879
-                    return false;
1880
-                }
1881
-            }
1882
-        }
1883
-        return true;
1884
-    }
1885
-
1886
-
1887
-    /**
1888
-     * @return bool
1889
-     * @throws EE_Error
1890
-     * @throws InvalidArgumentException
1891
-     * @throws ReflectionException
1892
-     * @throws InvalidDataTypeException
1893
-     * @throws InvalidInterfaceException
1894
-     */
1895
-    public function redirect_form(): bool
1896
-    {
1897
-        $payment_method_billing_info = $this->_payment_method_billing_info(
1898
-            $this->_get_payment_method_for_selected_method_of_payment()
1899
-        );
1900
-        $html                        = $payment_method_billing_info->get_html();
1901
-        $html                        .= $this->checkout->redirect_form;
1902
-        /** @var ResponseInterface $response */
1903
-        $response = LoaderFactory::getLoader()->getShared(ResponseInterface::class);
1904
-        $response->addOutput($html);
1905
-        return true;
1906
-    }
1907
-
1908
-
1909
-    /**
1910
-     * @return bool
1911
-     * @throws EE_Error
1912
-     * @throws ReflectionException
1913
-     */
1914
-    private function _billing_form_is_valid(): bool
1915
-    {
1916
-        if (! $this->checkout->payment_method->type_obj()->has_billing_form()) {
1917
-            return true;
1918
-        }
1919
-        if ($this->checkout->billing_form instanceof EE_Billing_Info_Form) {
1920
-            if ($this->checkout->billing_form->was_submitted()) {
1921
-                $this->checkout->billing_form->receive_form_submission();
1922
-                if ($this->checkout->billing_form->is_valid()) {
1923
-                    return true;
1924
-                }
1925
-                $validation_errors = $this->checkout->billing_form->get_validation_errors_accumulated();
1926
-                $error_strings     = [];
1927
-                foreach ($validation_errors as $validation_error) {
1928
-                    if ($validation_error instanceof EE_Validation_Error) {
1929
-                        $form_section = $validation_error->get_form_section();
1930
-                        if ($form_section instanceof EE_Form_Input_Base) {
1931
-                            $label = $form_section->html_label_text();
1932
-                        } elseif ($form_section instanceof EE_Form_Section_Base) {
1933
-                            $label = $form_section->name();
1934
-                        } else {
1935
-                            $label = esc_html__('Validation Error', 'event_espresso');
1936
-                        }
1937
-                        $error_strings[] = sprintf('%1$s: %2$s', $label, $validation_error->getMessage());
1938
-                    }
1939
-                }
1940
-                EE_Error::add_error(
1941
-                    sprintf(
1942
-                        esc_html__(
1943
-                            'One or more billing form inputs are invalid and require correction before proceeding. %1$s %2$s',
1944
-                            'event_espresso'
1945
-                        ),
1946
-                        '<br/>',
1947
-                        implode('<br/>', $error_strings)
1948
-                    ),
1949
-                    __FILE__,
1950
-                    __FUNCTION__,
1951
-                    __LINE__
1952
-                );
1953
-            } else {
1954
-                EE_Error::add_error(
1955
-                    esc_html__(
1956
-                        'The billing form was not submitted or something prevented it\'s submission.',
1957
-                        'event_espresso'
1958
-                    ),
1959
-                    __FILE__,
1960
-                    __FUNCTION__,
1961
-                    __LINE__
1962
-                );
1963
-            }
1964
-        } else {
1965
-            EE_Error::add_error(
1966
-                esc_html__(
1967
-                    'The submitted billing form is invalid possibly due to a technical reason.',
1968
-                    'event_espresso'
1969
-                ),
1970
-                __FILE__,
1971
-                __FUNCTION__,
1972
-                __LINE__
1973
-            );
1974
-        }
1975
-        return false;
1976
-    }
1977
-
1978
-
1979
-    /**
1980
-     * ensures that the primary registrant has a valid attendee object created with the critical details populated
1981
-     * (first & last name & email) and that both the transaction object and primary registration object have been saved
1982
-     * plz note that any other registrations will NOT be saved at this point (because they may not have any details
1983
-     * yet)
1984
-     *
1985
-     * @return bool
1986
-     * @throws EE_Error
1987
-     * @throws InvalidArgumentException
1988
-     * @throws ReflectionException
1989
-     * @throws RuntimeException
1990
-     * @throws InvalidDataTypeException
1991
-     * @throws InvalidInterfaceException
1992
-     */
1993
-    private function _setup_primary_registrant_prior_to_payment(): bool
1994
-    {
1995
-        // check if transaction has a primary registrant and that it has a related Attendee object
1996
-        // if not, then we need to at least gather some primary registrant data before attempting payment
1997
-        if (
1998
-            $this->checkout->billing_form instanceof EE_Billing_Attendee_Info_Form
1999
-            && ! $this->checkout->transaction_has_primary_registrant()
2000
-            && ! $this->_capture_primary_registration_data_from_billing_form()
2001
-        ) {
2002
-            return false;
2003
-        }
2004
-        // because saving an object clears its cache, we need to do the Chevy Shuffle
2005
-        // grab the primary_registration object
2006
-        $primary_registration = $this->checkout->transaction->primary_registration();
2007
-        // at this point we'll consider a TXN to not have been failed
2008
-        $this->checkout->transaction->toggle_failed_transaction_status();
2009
-        // save the TXN ( which clears cached copy of primary_registration)
2010
-        $this->checkout->transaction->save();
2011
-        // grab TXN ID and save it to the primary_registration
2012
-        $primary_registration->set_transaction_id($this->checkout->transaction->ID());
2013
-        // save what we have so far
2014
-        $primary_registration->save();
2015
-        return true;
2016
-    }
2017
-
2018
-
2019
-    /**
2020
-     * Captures primary registration data from the billing form.
2021
-     *
2022
-     * This method is used to gather the primary registrant data before attempting payment.
2023
-     * It checks if the billing form is an instance of EE_Billing_Attendee_Info_Form and if the transaction
2024
-     * has a primary registrant. If not, it captures the primary registrant data from the billing form.
2025
-     *
2026
-     * @return bool
2027
-     * @throws EE_Error
2028
-     * @throws InvalidArgumentException
2029
-     * @throws ReflectionException
2030
-     * @throws InvalidDataTypeException
2031
-     * @throws InvalidInterfaceException
2032
-     */
2033
-    private function _capture_primary_registration_data_from_billing_form(): bool
2034
-    {
2035
-        $primary_registration = $this->checkout->transaction->primary_registration();
2036
-        if (! $this->validatePrimaryRegistration($primary_registration)) {
2037
-            return false;
2038
-        }
2039
-
2040
-        $primary_attendee = $this->getPrimaryAttendee($primary_registration);
2041
-        if (! $this->validatePrimaryAttendee($primary_attendee)) {
2042
-            return false;
2043
-        }
2044
-
2045
-        if (! $this->addAttendeeToPrimaryRegistration($primary_attendee, $primary_registration)) {
2046
-            return false;
2047
-        }
2048
-        // both the primary registration and primary attendee objects should be valid entities at this point
2049
-        $this->checkout->primary_attendee_obj = $primary_attendee;
2050
-
2051
-        /** @type EE_Registration_Processor $registration_processor */
2052
-        $registration_processor = EE_Registry::instance()->load_class('Registration_Processor');
2053
-        // at this point, we should have enough details about the registrant to consider the registration NOT incomplete
2054
-        $registration_processor->toggle_incomplete_registration_status_to_default(
2055
-            $primary_registration,
2056
-            false,
2057
-            new Context(
2058
-                __METHOD__,
2059
-                esc_html__(
2060
-                    'Executed when the primary registrant\'s status is updated during the registration process when processing a billing form.',
2061
-                    'event_espresso'
2062
-                )
2063
-            )
2064
-        );
2065
-        return true;
2066
-    }
2067
-
2068
-
2069
-    /**
2070
-     * returns true if the primary registration is a valid entity
2071
-     *
2072
-     * @param $primary_registration
2073
-     * @return bool
2074
-     * @throws EE_Error
2075
-     * @since 5.0.21.p
2076
-     */
2077
-    private function validatePrimaryRegistration($primary_registration): bool
2078
-    {
2079
-        if ($primary_registration instanceof EE_Registration) {
2080
-            return true;
2081
-        }
2082
-        EE_Error::add_error(
2083
-            sprintf(
2084
-                esc_html__(
2085
-                    'The primary registrant for this transaction could not be determined due to a technical issue.%sPlease try again or contact %s for assistance.',
2086
-                    'event_espresso'
2087
-                ),
2088
-                '<br/>',
2089
-                EE_Registry::instance()->CFG->organization->get_pretty('email')
2090
-            ),
2091
-            __FILE__,
2092
-            __FUNCTION__,
2093
-            __LINE__
2094
-        );
2095
-        return false;
2096
-    }
2097
-
2098
-
2099
-    /**
2100
-     * retrieves the primary attendee object for the primary registration and copies the billing form data to it.
2101
-     * if the primary registration does not have an attendee object, then one is created from the billing form info
2102
-     *
2103
-     * @param EE_Registration $primary_registration
2104
-     * @return EE_Attendee|null
2105
-     * @throws EE_Error
2106
-     * @throws ReflectionException
2107
-     * @since 5.0.21.p
2108
-     */
2109
-    private function getPrimaryAttendee(EE_Registration $primary_registration): ?EE_Attendee
2110
-    {
2111
-        // if we have a primary registration, then we should have a primary attendee
2112
-        $attendee = $primary_registration->attendee();
2113
-        if ($attendee instanceof EE_Attendee) {
2114
-            return $this->checkout->billing_form->copy_billing_form_data_to_attendee($attendee);
2115
-        }
2116
-        // if not, then we need to create one from the billing form
2117
-        return $this->checkout->billing_form->create_attendee_from_billing_form_data();
2118
-    }
2119
-
2120
-
2121
-    /**
2122
-     * returns true if the primary attendee is a valid entity
2123
-     *
2124
-     * @param $primary_attendee
2125
-     * @return bool
2126
-     * @throws EE_Error
2127
-     * @since 5.0.21.p
2128
-     */
2129
-    private function validatePrimaryAttendee($primary_attendee): bool
2130
-    {
2131
-        if ($primary_attendee instanceof EE_Attendee) {
2132
-            return true;
2133
-        }
2134
-        EE_Error::add_error(
2135
-            sprintf(
2136
-                esc_html__(
2137
-                    'The billing form details could not be used for attendee details due to a technical issue.%sPlease try again or contact %s for assistance.',
2138
-                    'event_espresso'
2139
-                ),
2140
-                '<br/>',
2141
-                EE_Registry::instance()->CFG->organization->get_pretty('email')
2142
-            ),
2143
-            __FILE__,
2144
-            __FUNCTION__,
2145
-            __LINE__
2146
-        );
2147
-        return false;
2148
-    }
2149
-
2150
-
2151
-    /**
2152
-     * returns true if the attendee was successfully added to the primary registration
2153
-     *
2154
-     * @param EE_Attendee     $primary_attendee
2155
-     * @param EE_Registration $primary_registration
2156
-     * @return bool
2157
-     * @throws EE_Error
2158
-     * @throws ReflectionException
2159
-     * @since 5.0.21.p
2160
-     */
2161
-    private function addAttendeeToPrimaryRegistration(
2162
-        EE_Attendee $primary_attendee,
2163
-        EE_Registration $primary_registration
2164
-    ): bool {
2165
-        // ensure attendee has an ID by saving
2166
-        $primary_attendee->save();
2167
-
2168
-        // compare attendee IDs
2169
-        if ($primary_registration->attendee_id() === $primary_attendee->ID()) {
2170
-            return true;
2171
-        }
2172
-
2173
-        $primary_attendee = $primary_registration->_add_relation_to($primary_attendee, 'Attendee');
2174
-        if ($primary_attendee instanceof EE_Attendee) {
2175
-            return true;
2176
-        }
2177
-
2178
-        EE_Error::add_error(
2179
-            sprintf(
2180
-                esc_html__(
2181
-                    'The primary registrant could not be associated with this transaction due to a technical issue.%sPlease try again or contact %s for assistance.',
2182
-                    'event_espresso'
2183
-                ),
2184
-                '<br/>',
2185
-                EE_Registry::instance()->CFG->organization->get_pretty('email')
2186
-            ),
2187
-            __FILE__,
2188
-            __FUNCTION__,
2189
-            __LINE__
2190
-        );
2191
-        return false;
2192
-    }
2193
-
2194
-
2195
-    /**
2196
-     * retrieves a valid payment method
2197
-     *
2198
-     * @return EE_Payment_Method
2199
-     * @throws EE_Error
2200
-     * @throws InvalidArgumentException
2201
-     * @throws ReflectionException
2202
-     * @throws InvalidDataTypeException
2203
-     * @throws InvalidInterfaceException
2204
-     */
2205
-    private function _get_payment_method_for_selected_method_of_payment(): ?EE_Payment_Method
2206
-    {
2207
-        if ($this->checkout->selected_method_of_payment === 'events_sold_out') {
2208
-            $this->_redirect_because_event_sold_out();
2209
-            return null;
2210
-        }
2211
-        // get EE_Payment_Method object
2212
-        $payment_method = $this->checkout->available_payment_methods[ $this->checkout->selected_method_of_payment ]
2213
-            ?? $this->payment_method_model->get_one_by_slug($this->checkout->selected_method_of_payment);
2214
-        // verify $payment_method
2215
-        if (! $payment_method instanceof EE_Payment_Method) {
2216
-            // not a payment
2217
-            EE_Error::add_error(
2218
-                sprintf(
2219
-                    esc_html__(
2220
-                        'The selected method of payment could not be determined due to a technical issue.%sPlease try again or contact %s for assistance.',
2221
-                        'event_espresso'
2222
-                    ),
2223
-                    '<br/>',
2224
-                    EE_Registry::instance()->CFG->organization->get_pretty('email')
2225
-                ),
2226
-                __FILE__,
2227
-                __FUNCTION__,
2228
-                __LINE__
2229
-            );
2230
-            return null;
2231
-        }
2232
-        // and verify it has a valid Payment_Method Type object
2233
-        if (! $payment_method->type_obj() instanceof EE_PMT_Base) {
2234
-            // not a payment
2235
-            EE_Error::add_error(
2236
-                sprintf(
2237
-                    esc_html__(
2238
-                        'A valid payment method could not be determined due to a technical issue.%sPlease try again or contact %s for assistance.',
2239
-                        'event_espresso'
2240
-                    ),
2241
-                    '<br/>',
2242
-                    EE_Registry::instance()->CFG->organization->get_pretty('email')
2243
-                ),
2244
-                __FILE__,
2245
-                __FUNCTION__,
2246
-                __LINE__
2247
-            );
2248
-            return null;
2249
-        }
2250
-        return $payment_method;
2251
-    }
2252
-
2253
-
2254
-    /**
2255
-     * @param EE_Payment_Method $payment_method
2256
-     * @return EE_Payment|null
2257
-     * @throws EE_Error
2258
-     * @throws ReflectionException
2259
-     */
2260
-    private function _attempt_payment(EE_Payment_Method $payment_method): ?EE_Payment
2261
-    {
2262
-        $this->checkout->transaction->save();
2263
-        /** @var PaymentProcessor $payment_processor */
2264
-        $payment_processor = LoaderFactory::getShared(PaymentProcessor::class);
2265
-        if (! $payment_processor instanceof PaymentProcessor) {
2266
-            return null;
2267
-        }
2268
-        /** @var EE_Transaction_Processor $transaction_processor */
2269
-        $transaction_processor = LoaderFactory::getShared(EE_Transaction_Processor::class);
2270
-        if ($transaction_processor instanceof EE_Transaction_Processor) {
2271
-            $transaction_processor->set_revisit($this->checkout->revisit);
2272
-        }
2273
-        try {
2274
-            // generate payment object
2275
-            return $payment_processor->processPayment(
2276
-                $payment_method,
2277
-                $this->checkout->transaction,
2278
-                $this->checkout->billing_form instanceof EE_Billing_Info_Form
2279
-                    ? $this->checkout->billing_form
2280
-                    : null,
2281
-                $this->checkout->amount_owing,
2282
-                $this->checkout->admin_request,
2283
-                true,
2284
-                $this->_get_return_url($payment_method),
2285
-                $this->reg_step_url()
2286
-            );
2287
-        } catch (Exception $e) {
2288
-            $this->_handle_payment_processor_exception($e);
2289
-        }
2290
-        return null;
2291
-    }
2292
-
2293
-
2294
-    /**
2295
-     * @param Exception $e
2296
-     * @return void
2297
-     * @throws EE_Error
2298
-     */
2299
-    protected function _handle_payment_processor_exception(Exception $e)
2300
-    {
2301
-        EE_Error::add_error(
2302
-            sprintf(
2303
-                esc_html__(
2304
-                    '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',
2305
-                    'event_espresso'
2306
-                ),
2307
-                '<br/>',
2308
-                EE_Registry::instance()->CFG->organization->get_pretty('email'),
2309
-                $e->getMessage(),
2310
-                $e->getFile(),
2311
-                $e->getLine()
2312
-            ),
2313
-            __FILE__,
2314
-            __FUNCTION__,
2315
-            __LINE__
2316
-        );
2317
-    }
2318
-
2319
-
2320
-    /**
2321
-     * @param EE_Payment_Method $payment_method
2322
-     * @return string
2323
-     * @throws EE_Error
2324
-     * @throws ReflectionException
2325
-     */
2326
-    protected function _get_return_url(EE_Payment_Method $payment_method): string
2327
-    {
2328
-        switch ($payment_method->type_obj()->payment_occurs()) {
2329
-            case EE_PMT_Base::offsite:
2330
-                return add_query_arg(
2331
-                    [
2332
-                        'action'                     => 'process_gateway_response',
2333
-                        'selected_method_of_payment' => $this->checkout->selected_method_of_payment,
2334
-                        'spco_txn'                   => $this->checkout->transaction->ID(),
2335
-                    ],
2336
-                    $this->reg_step_url()
2337
-                );
2338
-
2339
-            case EE_PMT_Base::onsite:
2340
-            case EE_PMT_Base::offline:
2341
-                return $this->checkout->next_step->reg_step_url();
2342
-        }
2343
-        return '';
2344
-    }
2345
-
2346
-
2347
-    /**
2348
-     * @param EE_Payment|null $payment
2349
-     * @return EE_Payment|bool
2350
-     * @throws EE_Error
2351
-     * @throws ReflectionException
2352
-     */
2353
-    private function _validate_payment(?EE_Payment $payment = null)
2354
-    {
2355
-        if ($this->checkout->payment_method->is_off_line()) {
2356
-            return true;
2357
-        }
2358
-        // verify payment object
2359
-        if (! $payment instanceof EE_Payment) {
2360
-            // not a payment
2361
-            EE_Error::add_error(
2362
-                sprintf(
2363
-                    esc_html__(
2364
-                        'A valid payment was not generated due to a technical issue.%1$sPlease try again or contact %2$s for assistance.',
2365
-                        'event_espresso'
2366
-                    ),
2367
-                    '<br/>',
2368
-                    EE_Registry::instance()->CFG->organization->get_pretty('email')
2369
-                ),
2370
-                __FILE__,
2371
-                __FUNCTION__,
2372
-                __LINE__
2373
-            );
2374
-            return false;
2375
-        }
2376
-        return $payment;
2377
-    }
2378
-
2379
-
2380
-    /**
2381
-     * @param EE_Payment|bool $payment
2382
-     * @return bool|EE_Payment
2383
-     * @throws EE_Error
2384
-     * @throws ReflectionException
2385
-     */
2386
-    private function _post_payment_processing($payment = null)
2387
-    {
2388
-        // Off-Line payment?
2389
-        if ($payment === true) {
2390
-            return true;
2391
-        }
2392
-        if ($payment instanceof EE_Payment) {
2393
-            // Should the user be redirected?
2394
-            if ($payment->redirect_url()) {
2395
-                do_action('AHEE_log', __CLASS__, __FUNCTION__, $payment->redirect_url(), '$payment->redirect_url()');
2396
-                $this->checkout->redirect      = true;
2397
-                $this->checkout->redirect_form = $payment->redirect_form();
2398
-                $this->checkout->redirect_url  = $this->reg_step_url('redirect_form');
2399
-                // set JSON response
2400
-                $this->checkout->json_response->set_redirect_form($this->checkout->redirect_form);
2401
-                // and lastly, let's bump the payment status to pending
2402
-                $payment->set_status(EEM_Payment::status_id_pending);
2403
-                $payment->save();
2404
-            } elseif (! $this->_process_payment_status($payment, EE_PMT_Base::onsite)) {
2405
-                // User shouldn't be redirected. So let's process it here.
2406
-                // $this->_setup_redirect_for_next_step();
2407
-                $this->checkout->continue_reg = false;
2408
-            }
2409
-            return $payment;
2410
-        }
2411
-        // ummm ya... not Off-Line, not On-Site, not off-Site ????
2412
-        $this->checkout->continue_reg = false;
2413
-        return false;
2414
-    }
2415
-
2416
-
2417
-    /**
2418
-     * @param EE_Payment|null $payment
2419
-     * @param string          $payment_occurs
2420
-     * @return bool
2421
-     * @throws EE_Error
2422
-     */
2423
-    private function _process_payment_status(?EE_Payment $payment, string $payment_occurs = EE_PMT_Base::offline): bool
2424
-    {
2425
-        // off-line payment? carry on
2426
-        if ($payment_occurs === EE_PMT_Base::offline) {
2427
-            return true;
2428
-        }
2429
-        // verify payment validity
2430
-        if ($payment instanceof EE_Payment) {
2431
-            do_action('AHEE_log', __CLASS__, __FUNCTION__, $payment->status(), '$payment->status()');
2432
-            $msg = $payment->gateway_response();
2433
-            // check results
2434
-            switch ($payment->status()) {
2435
-                // good payment
2436
-                case EEM_Payment::status_id_approved:
2437
-                    EE_Error::add_success(
2438
-                        esc_html__('Your payment was processed successfully.', 'event_espresso'),
2439
-                        __FILE__,
2440
-                        __FUNCTION__,
2441
-                        __LINE__
2442
-                    );
2443
-                    return true;
2444
-                // slow payment
2445
-                case EEM_Payment::status_id_pending:
2446
-                    if (empty($msg)) {
2447
-                        $msg = esc_html__(
2448
-                            'Your payment appears to have been processed successfully, but the Instant Payment Notification has not yet been received. It should arrive shortly.',
2449
-                            'event_espresso'
2450
-                        );
2451
-                    }
2452
-                    EE_Error::add_success($msg, __FILE__, __FUNCTION__, __LINE__);
2453
-                    return true;
2454
-                // don't wanna payment
2455
-                case EEM_Payment::status_id_cancelled:
2456
-                    if (empty($msg)) {
2457
-                        $msg = _n(
2458
-                            'Payment cancelled. Please try again.',
2459
-                            'Payment cancelled. Please try again or select another method of payment.',
2460
-                            count($this->checkout->available_payment_methods),
2461
-                            'event_espresso'
2462
-                        );
2463
-                    }
2464
-                    EE_Error::add_attention($msg, __FILE__, __FUNCTION__, __LINE__);
2465
-                    return false;
2466
-                // not enough payment
2467
-                case EEM_Payment::status_id_declined:
2468
-                    if (empty($msg)) {
2469
-                        $msg = _n(
2470
-                            'We\'re sorry but your payment was declined. Please try again.',
2471
-                            'We\'re sorry but your payment was declined. Please try again or select another method of payment.',
2472
-                            count($this->checkout->available_payment_methods),
2473
-                            'event_espresso'
2474
-                        );
2475
-                    }
2476
-                    EE_Error::add_attention($msg, __FILE__, __FUNCTION__, __LINE__);
2477
-                    return false;
2478
-                // bad payment
2479
-                case EEM_Payment::status_id_failed:
2480
-                    if (! empty($msg)) {
2481
-                        EE_Error::add_error($msg, __FILE__, __FUNCTION__, __LINE__);
2482
-                        return false;
2483
-                    }
2484
-                    // default to error below
2485
-                    break;
2486
-            }
2487
-        }
2488
-        // off-site payment gateway responses are too unreliable, so let's just assume that
2489
-        // the payment processing is just running slower than the registrant's request
2490
-        if ($payment_occurs === EE_PMT_Base::offsite) {
2491
-            return true;
2492
-        }
2493
-        EE_Error::add_error(
2494
-            sprintf(
2495
-                esc_html__(
2496
-                    'Your payment could not be processed successfully due to a technical issue.%sPlease try again or contact %s for assistance.',
2497
-                    'event_espresso'
2498
-                ),
2499
-                '<br/>',
2500
-                EE_Registry::instance()->CFG->organization->get_pretty('email')
2501
-            ),
2502
-            __FILE__,
2503
-            __FUNCTION__,
2504
-            __LINE__
2505
-        );
2506
-        return false;
2507
-    }
2508
-
2509
-
2510
-
2511
-
2512
-
2513
-
2514
-    /********************************************************************************************************/
2515
-    /**********************************  PROCESS GATEWAY RESPONSE  **********************************/
2516
-    /********************************************************************************************************/
2517
-
2518
-
2519
-    /**
2520
-     * This is the return point for Off-Site Payment Methods
2521
-     * It will attempt to "handle the IPN" if it appears that this has not already occurred,
2522
-     * otherwise, it will load up the last payment made for the TXN.
2523
-     * If the payment retrieved looks good, it will then either:
2524
-     *    complete the current step and allow advancement to the next reg step
2525
-     *        or present the payment options again
2526
-     *
2527
-     * @return bool
2528
-     * @throws EE_Error
2529
-     * @throws InvalidArgumentException
2530
-     * @throws ReflectionException
2531
-     * @throws InvalidDataTypeException
2532
-     * @throws InvalidInterfaceException
2533
-     */
2534
-    public function process_gateway_response()
2535
-    {
2536
-        // how have they chosen to pay?
2537
-        $this->checkout->selected_method_of_payment = $this->_get_selected_method_of_payment(true);
2538
-        // get EE_Payment_Method object
2539
-        if (! $this->checkout->payment_method = $this->_get_payment_method_for_selected_method_of_payment()) {
2540
-            $this->checkout->continue_reg = false;
2541
-            return false;
2542
-        }
2543
-        if (! $this->checkout->payment_method->is_off_site()) {
2544
-            return false;
2545
-        }
2546
-        $this->_validate_offsite_return();
2547
-        // verify TXN
2548
-        if ($this->checkout->transaction instanceof EE_Transaction) {
2549
-            $gateway = $this->checkout->payment_method->type_obj()->get_gateway();
2550
-            if (! $gateway instanceof EE_Offsite_Gateway) {
2551
-                $this->checkout->continue_reg = false;
2552
-                return false;
2553
-            }
2554
-            $payment = $this->_process_off_site_payment($gateway);
2555
-            $payment = $this->_process_cancelled_payments($payment);
2556
-            $payment = $this->_validate_payment($payment);
2557
-            // if payment was not declined by the payment gateway or cancelled by the registrant
2558
-            if ($this->_process_payment_status($payment, EE_PMT_Base::offsite)) {
2559
-                // $this->_setup_redirect_for_next_step();
2560
-                // store that for later
2561
-                $this->checkout->payment = $payment;
2562
-                // mark this reg step as completed, as long as gateway doesn't use a separate IPN request,
2563
-                // because we will complete this step during the IPN processing then
2564
-                if (! $this->handle_IPN_in_this_request()) {
2565
-                    $this->set_completed();
2566
-                }
2567
-                return true;
2568
-            }
2569
-        }
2570
-        // DEBUG LOG
2571
-        // $this->checkout->log(
2572
-        //     __CLASS__,
2573
-        //     __FUNCTION__,
2574
-        //     __LINE__,
2575
-        //     array('payment' => $payment)
2576
-        // );
2577
-        $this->checkout->continue_reg = false;
2578
-        return false;
2579
-    }
2580
-
2581
-
2582
-    /**
2583
-     * @return void
2584
-     * @throws EE_Error
2585
-     * @throws ReflectionException
2586
-     */
2587
-    private function _validate_offsite_return()
2588
-    {
2589
-        $TXN_ID = $this->request->getRequestParam('spco_txn', 0, 'int');
2590
-        if ($TXN_ID !== $this->checkout->transaction->ID()) {
2591
-            // Houston... we might have a problem
2592
-            $invalid_TXN = false;
2593
-            // first gather some info
2594
-            $valid_TXN          = EEM_Transaction::instance()->get_one_by_ID($TXN_ID);
2595
-            $primary_registrant = $valid_TXN instanceof EE_Transaction
2596
-                ? $valid_TXN->primary_registration()
2597
-                : null;
2598
-            // let's start by retrieving the cart for this TXN
2599
-            $cart = $this->checkout->get_cart_for_transaction($this->checkout->transaction);
2600
-            if ($cart instanceof EE_Cart) {
2601
-                // verify that the current cart has tickets
2602
-                $tickets = $cart->get_tickets();
2603
-                if (empty($tickets)) {
2604
-                    $invalid_TXN = true;
2605
-                }
2606
-            } else {
2607
-                $invalid_TXN = true;
2608
-            }
2609
-            $valid_TXN_SID = $primary_registrant instanceof EE_Registration
2610
-                ? $primary_registrant->session_ID()
2611
-                : null;
2612
-            // validate current Session ID and compare against valid TXN session ID
2613
-            if (
2614
-                $invalid_TXN // if this is already true, then skip other checks
2615
-                || EE_Session::instance()->id() === null
2616
-                || (
2617
-                    // WARNING !!!
2618
-                    // this could be PayPal sending back duplicate requests (ya they do that)
2619
-                    // or it **could** mean someone is simply registering AGAIN after having just done so,
2620
-                    // so now we need to determine if this current TXN looks valid or not
2621
-                    // and whether this reg step has even been started ?
2622
-                    EE_Session::instance()->id() === $valid_TXN_SID
2623
-                    // really? you're halfway through this reg step, but you never started it ?
2624
-                    && $this->checkout->transaction->reg_step_completed($this->slug()) === false
2625
-                )
2626
-            ) {
2627
-                $invalid_TXN = true;
2628
-            }
2629
-            if ($invalid_TXN) {
2630
-                // is the valid TXN completed ?
2631
-                if ($valid_TXN instanceof EE_Transaction) {
2632
-                    // has this step even been started ?
2633
-                    $reg_step_completed = $valid_TXN->reg_step_completed($this->slug());
2634
-                    if ($reg_step_completed !== false && $reg_step_completed !== true) {
2635
-                        // so it **looks** like this is a double request from PayPal
2636
-                        // so let's try to pick up where we left off
2637
-                        $this->checkout->transaction = $valid_TXN;
2638
-                        $this->checkout->refresh_all_entities(true);
2639
-                        return;
2640
-                    }
2641
-                }
2642
-                // you appear to be lost?
2643
-                $this->_redirect_wayward_request($primary_registrant);
2644
-            }
2645
-        }
2646
-    }
2647
-
2648
-
2649
-    /**
2650
-     * @param EE_Registration|null $primary_registrant
2651
-     * @return void
2652
-     * @throws EE_Error
2653
-     * @throws ReflectionException
2654
-     */
2655
-    private function _redirect_wayward_request(?EE_Registration $primary_registrant)
2656
-    {
2657
-        if (! $primary_registrant instanceof EE_Registration) {
2658
-            // try redirecting based on the current TXN
2659
-            $primary_registrant = $this->checkout->transaction instanceof EE_Transaction
2660
-                ? $this->checkout->transaction->primary_registration()
2661
-                : null;
2662
-        }
2663
-        if (! $primary_registrant instanceof EE_Registration) {
2664
-            EE_Error::add_error(
2665
-                sprintf(
2666
-                    esc_html__(
2667
-                        '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.',
2668
-                        'event_espresso'
2669
-                    ),
2670
-                    '<br/>',
2671
-                    EE_Registry::instance()->CFG->organization->get_pretty('email')
2672
-                ),
2673
-                __FILE__,
2674
-                __FUNCTION__,
2675
-                __LINE__
2676
-            );
2677
-            return;
2678
-        }
2679
-        // make sure transaction is not locked
2680
-        $this->checkout->transaction->unlock();
2681
-        wp_safe_redirect(
2682
-            add_query_arg(
2683
-                [
2684
-                    'e_reg_url_link' => $primary_registrant->reg_url_link(),
2685
-                ],
2686
-                $this->checkout->thank_you_page_url
2687
-            )
2688
-        );
2689
-        exit();
2690
-    }
2691
-
2692
-
2693
-    /**
2694
-     * @param EE_Offsite_Gateway $gateway
2695
-     * @return EE_Payment|null
2696
-     * @throws EE_Error
2697
-     * @throws ReflectionException
2698
-     */
2699
-    private function _process_off_site_payment(EE_Offsite_Gateway $gateway): ?EE_Payment
2700
-    {
2701
-        try {
2702
-            $request      = LoaderFactory::getLoader()->getShared(RequestInterface::class);
2703
-            $request_data = $request->requestParams();
2704
-            // if gateway uses_separate_IPN_request, then we don't have to process the IPN manually
2705
-            $this->set_handle_IPN_in_this_request(
2706
-                $gateway->handle_IPN_in_this_request($request_data, false)
2707
-            );
2708
-            if ($this->handle_IPN_in_this_request()) {
2709
-                // get payment details and process results
2710
-                /** @var IpnHandler $payment_processor */
2711
-                $payment_processor = LoaderFactory::getShared(IpnHandler::class);
2712
-                $payment           = $payment_processor->processIPN(
2713
-                    $request_data,
2714
-                    $this->checkout->transaction,
2715
-                    $this->checkout->payment_method,
2716
-                    true,
2717
-                    false
2718
-                );
2719
-                // $payment_source = 'process_ipn';
2720
-            } else {
2721
-                $payment = $this->checkout->transaction->last_payment();
2722
-                // $payment_source = 'last_payment';
2723
-            }
2724
-        } catch (Exception $e) {
2725
-            // let's just eat the exception and try to move on using any previously set payment info
2726
-            $payment = $this->checkout->transaction->last_payment();
2727
-            // $payment_source = 'last_payment after Exception';
2728
-            // but if we STILL don't have a payment object
2729
-            if (! $payment instanceof EE_Payment) {
2730
-                // then we'll object ! ( not object like a thing... but object like what a lawyer says ! )
2731
-                $this->_handle_payment_processor_exception($e);
2732
-            }
2733
-        }
2734
-        return $payment;
2735
-    }
2736
-
2737
-
2738
-    /**
2739
-     * just makes sure that the payment status gets updated correctly
2740
-     * so that an error isn't generated during payment validation
2741
-     *
2742
-     * @param EE_Payment|null $payment
2743
-     * @return EE_Payment|null
2744
-     * @throws EE_Error
2745
-     */
2746
-    private function _process_cancelled_payments(?EE_Payment $payment = null): ?EE_Payment
2747
-    {
2748
-        if (
2749
-            $payment instanceof EE_Payment
2750
-            && $this->request->requestParamIsSet('ee_cancel_payment')
2751
-            && $payment->status() === EEM_Payment::status_id_failed
2752
-        ) {
2753
-            $payment->set_status(EEM_Payment::status_id_cancelled);
2754
-        }
2755
-        return $payment;
2756
-    }
2757
-
2758
-
2759
-    /**
2760
-     * @return void
2761
-     * @throws EE_Error
2762
-     * @throws InvalidArgumentException
2763
-     * @throws ReflectionException
2764
-     * @throws InvalidDataTypeException
2765
-     * @throws InvalidInterfaceException
2766
-     */
2767
-    public function get_transaction_details_for_gateways()
2768
-    {
2769
-        $txn_details = [];
2770
-        // ya gotta make a choice man
2771
-        if (empty($this->checkout->selected_method_of_payment)) {
2772
-            $txn_details = [
2773
-                'error' => esc_html__('Please select a method of payment before proceeding.', 'event_espresso'),
2774
-            ];
2775
-        }
2776
-        // get EE_Payment_Method object
2777
-        if (
2778
-            empty($txn_details)
2779
-            && ! $this->checkout->payment_method = $this->_get_payment_method_for_selected_method_of_payment()
2780
-        ) {
2781
-            $txn_details = [
2782
-                'selected_method_of_payment' => $this->checkout->selected_method_of_payment,
2783
-                'error'                      => esc_html__(
2784
-                    'A valid Payment Method could not be determined.',
2785
-                    'event_espresso'
2786
-                ),
2787
-            ];
2788
-        }
2789
-        if (empty($txn_details) && $this->checkout->transaction instanceof EE_Transaction) {
2790
-            $return_url  = $this->_get_return_url($this->checkout->payment_method);
2791
-            $txn_details = [
2792
-                'TXN_ID'         => $this->checkout->transaction->ID(),
2793
-                'TXN_timestamp'  => $this->checkout->transaction->datetime(),
2794
-                'TXN_total'      => $this->checkout->transaction->total(),
2795
-                'TXN_paid'       => $this->checkout->transaction->paid(),
2796
-                'TXN_reg_steps'  => $this->checkout->transaction->reg_steps(),
2797
-                'STS_ID'         => $this->checkout->transaction->status_ID(),
2798
-                'PMD_ID'         => $this->checkout->transaction->payment_method_ID(),
2799
-                'payment_amount' => $this->checkout->amount_owing,
2800
-                'return_url'     => $return_url,
2801
-                'cancel_url'     => add_query_arg(['ee_cancel_payment' => true], $return_url),
2802
-                'notify_url'     => EE_Config::instance()->core->txn_page_url(
2803
-                    [
2804
-                        'e_reg_url_link'    => $this->checkout->transaction->primary_registration()->reg_url_link(),
2805
-                        'ee_payment_method' => $this->checkout->payment_method->slug(),
2806
-                    ]
2807
-                ),
2808
-            ];
2809
-        }
2810
-        echo wp_json_encode($txn_details);
2811
-        exit();
2812
-    }
2813
-
2814
-
2815
-    /**
2816
-     * to conserve db space, let's remove the reg_form and the EE_Checkout object from EE_SPCO_Reg_Step objects upon
2817
-     * serialization EE_Checkout will handle the reimplementation of itself upon waking, but we won't bother with the
2818
-     * reg form, because if needed, it will be regenerated anyways
2819
-     *
2820
-     * @return array
2821
-     */
2822
-    public function __sleep()
2823
-    {
2824
-        // remove the reg form and the checkout
2825
-        return array_diff(array_keys(get_object_vars($this)), ['reg_form', 'checkout', 'line_item_display']);
2826
-    }
26
+	protected ?EE_Line_Item_Display $line_item_display = null;
27
+
28
+	protected bool $handle_IPN_in_this_request = false;
29
+
30
+	/**
31
+	 * @var EEM_Payment_Method|EEM_Base
32
+	 * @since 5.0.42
33
+	 */
34
+	protected EEM_Payment_Method $payment_method_model;
35
+
36
+
37
+	/**
38
+	 * set_hooks - for hooking into EE Core, other modules, etc
39
+	 *
40
+	 * @return    void
41
+	 */
42
+	public static function set_hooks()
43
+	{
44
+		add_filter(
45
+			'FHEE__SPCO__EE_Line_Item_Filter_Collection',
46
+			['EE_SPCO_Reg_Step_Payment_Options', 'add_spco_line_item_filters']
47
+		);
48
+		add_action(
49
+			'wp_ajax_switch_spco_billing_form',
50
+			['EE_SPCO_Reg_Step_Payment_Options', 'switch_spco_billing_form']
51
+		);
52
+		add_action(
53
+			'wp_ajax_nopriv_switch_spco_billing_form',
54
+			['EE_SPCO_Reg_Step_Payment_Options', 'switch_spco_billing_form']
55
+		);
56
+		add_action('wp_ajax_save_payer_details', ['EE_SPCO_Reg_Step_Payment_Options', 'save_payer_details']);
57
+		add_action(
58
+			'wp_ajax_nopriv_save_payer_details',
59
+			['EE_SPCO_Reg_Step_Payment_Options', 'save_payer_details']
60
+		);
61
+		add_action(
62
+			'wp_ajax_get_transaction_details_for_gateways',
63
+			['EE_SPCO_Reg_Step_Payment_Options', 'get_transaction_details']
64
+		);
65
+		add_action(
66
+			'wp_ajax_nopriv_get_transaction_details_for_gateways',
67
+			['EE_SPCO_Reg_Step_Payment_Options', 'get_transaction_details']
68
+		);
69
+		add_filter(
70
+			'FHEE__EED_Recaptcha___bypass_recaptcha__bypass_request_params_array',
71
+			['EE_SPCO_Reg_Step_Payment_Options', 'bypass_recaptcha_for_load_payment_method']
72
+		);
73
+	}
74
+
75
+
76
+	/**
77
+	 * @throws EE_Error
78
+	 * @throws ReflectionException
79
+	 */
80
+	public static function switch_spco_billing_form()
81
+	{
82
+		EED_Single_Page_Checkout::process_ajax_request('switch_payment_method');
83
+	}
84
+
85
+
86
+	/**
87
+	 * @throws EE_Error
88
+	 * @throws ReflectionException
89
+	 */
90
+	public static function save_payer_details()
91
+	{
92
+		EED_Single_Page_Checkout::process_ajax_request('save_payer_details_via_ajax');
93
+	}
94
+
95
+
96
+	/**
97
+	 * @throws EE_Error
98
+	 * @throws ReflectionException
99
+	 */
100
+	public static function get_transaction_details()
101
+	{
102
+		EED_Single_Page_Checkout::process_ajax_request('get_transaction_details_for_gateways');
103
+	}
104
+
105
+
106
+	/**
107
+	 * @return array
108
+	 */
109
+	public static function bypass_recaptcha_for_load_payment_method(): array
110
+	{
111
+		return [
112
+			'EESID'  => EE_Registry::instance()->SSN->id(),
113
+			'step'   => 'payment_options',
114
+			'action' => 'spco_billing_form',
115
+		];
116
+	}
117
+
118
+
119
+	/**
120
+	 * @param EE_Checkout $checkout
121
+	 * @throws EE_Error
122
+	 * @throws ReflectionException
123
+	 */
124
+	public function __construct(EE_Checkout $checkout)
125
+	{
126
+		$this->request              = EED_Single_Page_Checkout::getRequest();
127
+		$this->_slug                = 'payment_options';
128
+		$this->_name                = esc_html__('Payment Options', 'event_espresso');
129
+		$this->_template            = SPCO_REG_STEPS_PATH . $this->_slug . '/payment_options_main.template.php';
130
+		$this->checkout             = $checkout;
131
+		$this->payment_method_model = EEM_Payment_Method::instance();
132
+		$this->_reset_success_message();
133
+		$this->set_instructions(
134
+			esc_html__(
135
+				'Please select a method of payment and provide any necessary billing information before proceeding.',
136
+				'event_espresso'
137
+			)
138
+		);
139
+	}
140
+
141
+
142
+	public function line_item_display(): ?EE_Line_Item_Display
143
+	{
144
+		return $this->line_item_display;
145
+	}
146
+
147
+
148
+	public function set_line_item_display(EE_Line_Item_Display $line_item_display)
149
+	{
150
+		$this->line_item_display = $line_item_display;
151
+	}
152
+
153
+
154
+	public function handle_IPN_in_this_request(): bool
155
+	{
156
+		return $this->handle_IPN_in_this_request;
157
+	}
158
+
159
+
160
+	public function set_handle_IPN_in_this_request(bool $handle_IPN_in_this_request)
161
+	{
162
+		$this->handle_IPN_in_this_request = filter_var($handle_IPN_in_this_request, FILTER_VALIDATE_BOOLEAN);
163
+	}
164
+
165
+
166
+	/**
167
+	 * @return void
168
+	 */
169
+	public function translate_js_strings()
170
+	{
171
+		EE_Registry::$i18n_js_strings['no_payment_method']      = esc_html__(
172
+			'Please select a method of payment in order to continue.',
173
+			'event_espresso'
174
+		);
175
+		EE_Registry::$i18n_js_strings['invalid_payment_method'] = esc_html__(
176
+			'A valid method of payment could not be determined. Please refresh the page and try again.',
177
+			'event_espresso'
178
+		);
179
+		EE_Registry::$i18n_js_strings['forwarding_to_offsite']  = esc_html__(
180
+			'Forwarding to Secure Payment Provider.',
181
+			'event_espresso'
182
+		);
183
+	}
184
+
185
+
186
+	/**
187
+	 * @return void
188
+	 * @throws EE_Error
189
+	 * @throws ReflectionException
190
+	 */
191
+	public function enqueue_styles_and_scripts()
192
+	{
193
+		$transaction = $this->checkout->transaction;
194
+		// if the transaction isn't set or nothing is owed on it, don't enqueue any JS
195
+		if (! $transaction instanceof EE_Transaction || EEH_Money::compare_floats($transaction->remaining(), 0)) {
196
+			return;
197
+		}
198
+		foreach ($this->checkout->available_payment_methods as $payment_method) {
199
+			$type_obj = $payment_method->type_obj();
200
+			if ($type_obj instanceof EE_PMT_Base) {
201
+				$billing_form = $type_obj->generate_new_billing_form($transaction);
202
+				if ($billing_form instanceof EE_Form_Section_Proper) {
203
+					$billing_form->enqueue_js();
204
+				}
205
+			}
206
+		}
207
+	}
208
+
209
+
210
+	/**
211
+	 * @return bool
212
+	 * @throws EE_Error
213
+	 * @throws InvalidArgumentException
214
+	 * @throws ReflectionException
215
+	 * @throws InvalidDataTypeException
216
+	 * @throws InvalidInterfaceException
217
+	 */
218
+	public function initialize_reg_step(): bool
219
+	{
220
+		// TODO: if /when we implement donations, then this will need overriding
221
+		if (
222
+			// don't need payment options for:
223
+			// registrations made via the admin
224
+			// completed transactions
225
+			// overpaid transactions
226
+			// $ 0.00 transactions(no payment required)
227
+			! $this->checkout->payment_required()
228
+			// but do NOT remove if current action being called belongs to this reg step
229
+			&& ! is_callable([$this, $this->checkout->action])
230
+			&& ! $this->completed()
231
+		) {
232
+			// and if so, then we no longer need the Payment Options step
233
+			if ($this->is_current_step()) {
234
+				$this->checkout->generate_reg_form = false;
235
+			}
236
+			$this->checkout->remove_reg_step($this->_slug);
237
+			// DEBUG LOG
238
+			// $this->checkout->log( __CLASS__, __FUNCTION__, __LINE__ );
239
+			return false;
240
+		}
241
+		// get all active payment methods
242
+		$this->checkout->available_payment_methods = $this->_get_available_payment_methods();
243
+		$this->setDefaultPaymentMethod($this->checkout->available_payment_methods);
244
+		return true;
245
+	}
246
+
247
+
248
+	/**
249
+	 * @param array $payment_methods
250
+	 * @return void
251
+	 * @throws EE_Error
252
+	 * @throws ReflectionException
253
+	 * @since 5.0.42
254
+	 */
255
+	private function setDefaultPaymentMethod(array $payment_methods): void {
256
+		foreach ($payment_methods as $payment_method) {
257
+			if ($payment_method instanceof EE_Payment_Method && $payment_method->open_by_default()) {
258
+				$this->checkout->default_payment_method = $payment_method;
259
+				return;
260
+			}
261
+		}
262
+	}
263
+
264
+
265
+	/**
266
+	 * @return EE_Form_Section_Proper
267
+	 * @throws EE_Error
268
+	 * @throws InvalidArgumentException
269
+	 * @throws ReflectionException
270
+	 * @throws EntityNotFoundException
271
+	 * @throws InvalidDataTypeException
272
+	 * @throws InvalidInterfaceException
273
+	 * @throws InvalidStatusException
274
+	 */
275
+	public function generate_reg_form(): EE_Form_Section_Proper
276
+	{
277
+		// reset in case someone changes their mind
278
+		$this->_reset_selected_method_of_payment();
279
+		// set some defaults
280
+		$this->checkout->selected_method_of_payment = 'payments_closed';
281
+		$registrations_requiring_payment            = [];
282
+		$registrations_for_free_events              = [];
283
+		$registrations_requiring_pre_approval       = [];
284
+		$sold_out_events                            = [];
285
+		$insufficient_spaces_available              = [];
286
+		$no_payment_required                        = true;
287
+		// loop thru registrations to gather info
288
+		$registrations         = $this->checkout->transaction->registrations($this->checkout->reg_cache_where_params);
289
+		$ejected_registrations = EE_SPCO_Reg_Step_Payment_Options::find_registrations_that_lost_their_space(
290
+			$registrations,
291
+			$this->checkout->revisit
292
+		);
293
+		foreach ($registrations as $REG_ID => $registration) {
294
+			/** @var $registration EE_Registration */
295
+			// Skip if the registration has been moved
296
+			if ($registration->wasMoved()) {
297
+				continue;
298
+			}
299
+			// has this registration lost it's space ?
300
+			if (isset($ejected_registrations[ $REG_ID ])) {
301
+				if ($registration->event()->is_sold_out() || $registration->event()->is_sold_out(true)) {
302
+					$sold_out_events[ $registration->event()->ID() ] = $registration->event();
303
+				} else {
304
+					$insufficient_spaces_available[ $registration->event()->ID() ] = $registration->event();
305
+				}
306
+				continue;
307
+			}
308
+			// event requires admin approval
309
+			if ($registration->status_ID() === RegStatus::AWAITING_REVIEW) {
310
+				// add event to list of events with pre-approval reg status
311
+				$registrations_requiring_pre_approval[ $REG_ID ] = $registration;
312
+				do_action(
313
+					'AHEE__EE_SPCO_Reg_Step_Payment_Options__generate_reg_form__event_requires_pre_approval',
314
+					$registration->event(),
315
+					$this
316
+				);
317
+				continue;
318
+			}
319
+			if (
320
+				$this->checkout->revisit
321
+				&& $registration->status_ID() !== RegStatus::APPROVED
322
+				&& (
323
+					$registration->event()->is_sold_out()
324
+					|| $registration->event()->is_sold_out(true)
325
+				)
326
+			) {
327
+				// add event to list of events that are sold out
328
+				$sold_out_events[ $registration->event()->ID() ] = $registration->event();
329
+				do_action(
330
+					'AHEE__EE_SPCO_Reg_Step_Payment_Options__generate_reg_form__sold_out_event',
331
+					$registration->event(),
332
+					$this
333
+				);
334
+				continue;
335
+			}
336
+			// are they allowed to pay now and is there monies owing?
337
+			if ($registration->owes_monies_and_can_pay()) {
338
+				$registrations_requiring_payment[ $REG_ID ] = $registration;
339
+				do_action(
340
+					'AHEE__EE_SPCO_Reg_Step_Payment_Options__generate_reg_form__event_requires_payment',
341
+					$registration->event(),
342
+					$this
343
+				);
344
+			} elseif (
345
+				! $this->checkout->revisit
346
+				&& $registration->status_ID() !== RegStatus::AWAITING_REVIEW
347
+				&& $registration->ticket()->is_free()
348
+			) {
349
+				$registrations_for_free_events[ $registration->ticket()->ID() ] = $registration;
350
+			}
351
+		}
352
+		$subsections = [];
353
+		// now decide which template to load
354
+		if (! empty($sold_out_events)) {
355
+			$subsections['sold_out_events'] = $this->_sold_out_events($sold_out_events);
356
+		}
357
+		if (! empty($insufficient_spaces_available)) {
358
+			$subsections['insufficient_space'] = $this->_insufficient_spaces_available(
359
+				$insufficient_spaces_available
360
+			);
361
+		}
362
+		if (! empty($registrations_requiring_pre_approval)) {
363
+			$subsections['registrations_requiring_pre_approval'] = $this->_registrations_requiring_pre_approval(
364
+				$registrations_requiring_pre_approval
365
+			);
366
+		}
367
+		if (! empty($registrations_for_free_events)) {
368
+			$subsections['no_payment_required'] = $this->_no_payment_required($registrations_for_free_events);
369
+		}
370
+		if (! empty($registrations_requiring_payment)) {
371
+			if ($this->checkout->amount_owing > 0) {
372
+				// check for method_of_payment before setting up line items
373
+				// so that surcharges can be applied to the line items based on the selected method of payment
374
+				$this->checkout->selected_method_of_payment = $this->_get_selected_method_of_payment();
375
+				do_action(
376
+					'AHEE__EE_SPCO_Reg_Step_Payment_Options__generate_reg_form__registrations_requiring_payment',
377
+					$this,
378
+					$registrations_requiring_payment
379
+				);
380
+				// autoload Line_Item_Display classes
381
+				EEH_Autoloader::register_line_item_filter_autoloaders();
382
+				$line_item_filter_processor = new EE_Line_Item_Filter_Processor(
383
+					apply_filters(
384
+						'FHEE__SPCO__EE_Line_Item_Filter_Collection',
385
+						new EE_Line_Item_Filter_Collection()
386
+					),
387
+					$this->checkout->cart->get_grand_total()
388
+				);
389
+				/** @var EE_Line_Item $filtered_line_item_tree */
390
+				$filtered_line_item_tree = $line_item_filter_processor->process();
391
+				EEH_Autoloader::register_line_item_display_autoloaders();
392
+				$this->set_line_item_display(new EE_Line_Item_Display('spco'));
393
+				$subsections['payment_options'] = $this->_display_payment_options(
394
+					$this->line_item_display->display_line_item(
395
+						$filtered_line_item_tree,
396
+						['registrations' => $registrations]
397
+					)
398
+				);
399
+				$this->checkout->amount_owing   = $filtered_line_item_tree->total();
400
+				$this->_apply_registration_payments_to_amount_owing($registrations);
401
+			}
402
+			$no_payment_required = false;
403
+		} else {
404
+			$this->_hide_reg_step_submit_button_if_revisit();
405
+		}
406
+		$this->_save_selected_method_of_payment();
407
+
408
+		$subsections['default_hidden_inputs'] = $this->reg_step_hidden_inputs();
409
+		$subsections['extra_hidden_inputs']   = $this->_extra_hidden_inputs($no_payment_required);
410
+
411
+		return new EE_Form_Section_Proper(
412
+			[
413
+				'name'            => $this->reg_form_name(),
414
+				'html_id'         => $this->reg_form_name(),
415
+				'subsections'     => $subsections,
416
+				'layout_strategy' => new EE_No_Layout(),
417
+			]
418
+		);
419
+	}
420
+
421
+
422
+	/**
423
+	 * add line item filters required for this reg step
424
+	 * these filters are applied via this line in EE_SPCO_Reg_Step_Payment_Options::set_hooks():
425
+	 *        add_filter( 'FHEE__SPCO__EE_Line_Item_Filter_Collection', array( 'EE_SPCO_Reg_Step_Payment_Options',
426
+	 *        'add_spco_line_item_filters' ) ); so any code that wants to use the same set of filters during the
427
+	 *        payment options reg step, can apply these filters via the following: apply_filters(
428
+	 *        'FHEE__SPCO__EE_Line_Item_Filter_Collection', new EE_Line_Item_Filter_Collection() ) or to an existing
429
+	 *        filter collection by passing that instead of instantiating a new collection
430
+	 *
431
+	 * @param EE_Line_Item_Filter_Collection $line_item_filter_collection
432
+	 * @return EE_Line_Item_Filter_Collection
433
+	 * @throws EE_Error
434
+	 * @throws InvalidArgumentException
435
+	 * @throws ReflectionException
436
+	 * @throws EntityNotFoundException
437
+	 * @throws InvalidDataTypeException
438
+	 * @throws InvalidInterfaceException
439
+	 * @throws InvalidStatusException
440
+	 */
441
+	public static function add_spco_line_item_filters(
442
+		EE_Line_Item_Filter_Collection $line_item_filter_collection
443
+	): EE_Line_Item_Filter_Collection {
444
+		if (! EE_Registry::instance()->SSN instanceof EE_Session) {
445
+			return $line_item_filter_collection;
446
+		}
447
+		if (! EE_Registry::instance()->SSN->checkout() instanceof EE_Checkout) {
448
+			return $line_item_filter_collection;
449
+		}
450
+		if (! EE_Registry::instance()->SSN->checkout()->transaction instanceof EE_Transaction) {
451
+			return $line_item_filter_collection;
452
+		}
453
+		$line_item_filter_collection->add(
454
+			new EE_Billable_Line_Item_Filter(
455
+				EE_SPCO_Reg_Step_Payment_Options::remove_ejected_registrations(
456
+					EE_Registry::instance()->SSN->checkout()->transaction->registrations(
457
+						EE_Registry::instance()->SSN->checkout()->reg_cache_where_params
458
+					)
459
+				)
460
+			)
461
+		);
462
+		$line_item_filter_collection->add(new EE_Non_Zero_Line_Item_Filter());
463
+		return $line_item_filter_collection;
464
+	}
465
+
466
+
467
+	/**
468
+	 * if a registrant has lost their potential space at an event due to lack of payment,
469
+	 * then this method removes them from the list of registrations being paid for during this request
470
+	 *
471
+	 * @param EE_Registration[] $registrations
472
+	 * @return EE_Registration[]
473
+	 * @throws EE_Error
474
+	 * @throws InvalidArgumentException
475
+	 * @throws ReflectionException
476
+	 * @throws EntityNotFoundException
477
+	 * @throws InvalidDataTypeException
478
+	 * @throws InvalidInterfaceException
479
+	 * @throws InvalidStatusException
480
+	 */
481
+	public static function remove_ejected_registrations(array $registrations): array
482
+	{
483
+		$ejected_registrations = EE_SPCO_Reg_Step_Payment_Options::find_registrations_that_lost_their_space(
484
+			$registrations,
485
+			EE_Registry::instance()->SSN->checkout()->revisit
486
+		);
487
+		foreach ($registrations as $REG_ID => $registration) {
488
+			// has this registration lost it's space ?
489
+			if (isset($ejected_registrations[ $REG_ID ])) {
490
+				unset($registrations[ $REG_ID ]);
491
+			}
492
+		}
493
+		return $registrations;
494
+	}
495
+
496
+
497
+	/**
498
+	 * If a registrant chooses an offline payment method like Invoice,
499
+	 * then no space is reserved for them at the event until they fully pay fo that site
500
+	 * (unless the event's default reg status is set to APPROVED)
501
+	 * if a registrant then later returns to pay, but the number of spaces available has been reduced due to sales,
502
+	 * then this method will determine which registrations have lost the ability to complete the reg process.
503
+	 *
504
+	 * @param EE_Registration[] $registrations
505
+	 * @param bool              $revisit
506
+	 * @return array
507
+	 * @throws EE_Error
508
+	 * @throws InvalidArgumentException
509
+	 * @throws ReflectionException
510
+	 * @throws EntityNotFoundException
511
+	 * @throws InvalidDataTypeException
512
+	 * @throws InvalidInterfaceException
513
+	 * @throws InvalidStatusException
514
+	 */
515
+	public static function find_registrations_that_lost_their_space(array $registrations, bool $revisit = false): array
516
+	{
517
+		// registrations per event
518
+		$event_reg_count = [];
519
+		// spaces left per event
520
+		$event_spaces_remaining = [];
521
+		// tickets left sorted by ID
522
+		$tickets_remaining = [];
523
+		// registrations that have lost their space
524
+		$ejected_registrations = [];
525
+		foreach ($registrations as $REG_ID => $registration) {
526
+			if (
527
+				$registration->status_ID() === RegStatus::APPROVED
528
+				|| apply_filters(
529
+					'FHEE__EE_SPCO_Reg_Step_Payment_Options__find_registrations_that_lost_their_space__allow_reg_payment',
530
+					false,
531
+					$registration,
532
+					$revisit
533
+				)
534
+			) {
535
+				continue;
536
+			}
537
+			$EVT_ID = $registration->event_ID();
538
+			$ticket = $registration->ticket();
539
+			if (! isset($tickets_remaining[ $ticket->ID() ])) {
540
+				$tickets_remaining[ $ticket->ID() ] = $ticket->remaining();
541
+			}
542
+			if ($tickets_remaining[ $ticket->ID() ] > 0) {
543
+				if (! isset($event_reg_count[ $EVT_ID ])) {
544
+					$event_reg_count[ $EVT_ID ] = 0;
545
+				}
546
+				$event_reg_count[ $EVT_ID ]++;
547
+				if (! isset($event_spaces_remaining[ $EVT_ID ])) {
548
+					$event_spaces_remaining[ $EVT_ID ] = $registration->event()->spaces_remaining_for_sale();
549
+				}
550
+			}
551
+			if (
552
+				$revisit
553
+				&& ($tickets_remaining[ $ticket->ID() ] === 0
554
+					|| $event_reg_count[ $EVT_ID ] > $event_spaces_remaining[ $EVT_ID ]
555
+				)
556
+			) {
557
+				$ejected_registrations[ $REG_ID ] = $registration->event();
558
+				if ($registration->status_ID() !== RegStatus::WAIT_LIST) {
559
+					/** @type EE_Registration_Processor $registration_processor */
560
+					$registration_processor = EE_Registry::instance()->load_class('Registration_Processor');
561
+					// at this point, we should have enough details about the registrant to consider the registration
562
+					// NOT incomplete
563
+					$registration_processor->manually_update_registration_status(
564
+						$registration,
565
+						RegStatus::WAIT_LIST
566
+					);
567
+				}
568
+			}
569
+		}
570
+		return $ejected_registrations;
571
+	}
572
+
573
+
574
+	/**
575
+	 * removes the HTML for the reg step submit button
576
+	 * by replacing it with an empty string via filter callback
577
+	 *
578
+	 * @return void
579
+	 */
580
+	protected function _hide_reg_step_submit_button_if_revisit()
581
+	{
582
+		if ($this->checkout->revisit) {
583
+			add_filter('FHEE__EE_SPCO_Reg_Step__reg_step_submit_button__sbmt_btn_html', '__return_empty_string');
584
+		}
585
+	}
586
+
587
+
588
+	/**
589
+	 * displays notices regarding events that have sold out since the registrant first signed up
590
+	 *
591
+	 * @param EE_Event[] $sold_out_events_array
592
+	 * @return EE_Form_Section_Proper
593
+	 * @throws EE_Error
594
+	 * @throws ReflectionException
595
+	 */
596
+	private function _sold_out_events(array $sold_out_events_array = []): EE_Form_Section_Proper
597
+	{
598
+		// set some defaults
599
+		$this->checkout->selected_method_of_payment = 'events_sold_out';
600
+		$sold_out_events                            = '';
601
+		foreach ($sold_out_events_array as $sold_out_event) {
602
+			$sold_out_events .= EEH_HTML::li(
603
+				EEH_HTML::span(
604
+					'  ' . $sold_out_event->name(),
605
+					'',
606
+					'dashicons dashicons-marker ee-icon-size-16 pink-text'
607
+				)
608
+			);
609
+		}
610
+		return new EE_Form_Section_Proper(
611
+			[
612
+				'layout_strategy' => new EE_Template_Layout(
613
+					[
614
+						'layout_template_file' => SPCO_REG_STEPS_PATH
615
+							. $this->_slug
616
+							. '/sold_out_events.template.php',
617
+						'template_args'        => apply_filters(
618
+							'FHEE__EE_SPCO_Reg_Step_Payment_Options___sold_out_events__template_args',
619
+							[
620
+								'sold_out_events'     => $sold_out_events,
621
+								'sold_out_events_msg' => apply_filters(
622
+									'FHEE__EE_SPCO_Reg_Step_Payment_Options___sold_out_events__sold_out_events_msg',
623
+									sprintf(
624
+										esc_html__(
625
+											'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',
626
+											'event_espresso'
627
+										),
628
+										'<strong>',
629
+										'</strong>',
630
+										'<br />'
631
+									)
632
+								),
633
+							]
634
+						),
635
+					]
636
+				),
637
+			]
638
+		);
639
+	}
640
+
641
+
642
+	/**
643
+	 * displays notices regarding events that do not have enough remaining spaces
644
+	 * to satisfy the current number of registrations looking to pay
645
+	 *
646
+	 * @param EE_Event[] $insufficient_spaces_events_array
647
+	 * @return EE_Form_Section_Proper
648
+	 * @throws EE_Error
649
+	 * @throws ReflectionException
650
+	 */
651
+	private function _insufficient_spaces_available(
652
+		array $insufficient_spaces_events_array = []
653
+	): EE_Form_Section_Proper {
654
+		// set some defaults
655
+		$this->checkout->selected_method_of_payment = 'invoice';
656
+		$insufficient_space_events                  = '';
657
+		foreach ($insufficient_spaces_events_array as $event) {
658
+			if ($event instanceof EE_Event) {
659
+				$insufficient_space_events .= EEH_HTML::li(
660
+					EEH_HTML::span(' ' . $event->name(), '', 'dashicons dashicons-marker ee-icon-size-16 pink-text')
661
+				);
662
+			}
663
+		}
664
+		return new EE_Form_Section_Proper(
665
+			[
666
+				'subsections'     => [
667
+					'default_hidden_inputs' => $this->reg_step_hidden_inputs(),
668
+					'extra_hidden_inputs'   => $this->_extra_hidden_inputs(),
669
+				],
670
+				'layout_strategy' => new EE_Template_Layout(
671
+					[
672
+						'layout_template_file' => SPCO_REG_STEPS_PATH
673
+							. $this->_slug
674
+							. '/sold_out_events.template.php',
675
+						'template_args'        => apply_filters(
676
+							'FHEE__EE_SPCO_Reg_Step_Payment_Options___insufficient_spaces_available__template_args',
677
+							[
678
+								'sold_out_events'     => $insufficient_space_events,
679
+								'sold_out_events_msg' => apply_filters(
680
+									'FHEE__EE_SPCO_Reg_Step_Payment_Options___insufficient_spaces_available__insufficient_space_msg',
681
+									esc_html__(
682
+										'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.',
683
+										'event_espresso'
684
+									)
685
+								),
686
+							]
687
+						),
688
+					]
689
+				),
690
+			]
691
+		);
692
+	}
693
+
694
+
695
+	/**
696
+	 * @param array $registrations_requiring_pre_approval
697
+	 * @return EE_Form_Section_Proper
698
+	 * @throws EE_Error
699
+	 * @throws EntityNotFoundException
700
+	 * @throws ReflectionException
701
+	 */
702
+	private function _registrations_requiring_pre_approval(
703
+		array $registrations_requiring_pre_approval = []
704
+	): EE_Form_Section_Proper {
705
+		$events_requiring_pre_approval = [];
706
+		foreach ($registrations_requiring_pre_approval as $registration) {
707
+			if ($registration instanceof EE_Registration && $registration->event() instanceof EE_Event) {
708
+				$events_requiring_pre_approval[ $registration->event()->ID() ] = EEH_HTML::li(
709
+					EEH_HTML::span(
710
+						'',
711
+						'',
712
+						'dashicons dashicons-marker ee-icon-size-16 orange-text'
713
+					)
714
+					. EEH_HTML::span($registration->event()->name(), '', 'orange-text')
715
+				);
716
+			}
717
+		}
718
+		return new EE_Form_Section_Proper(
719
+			[
720
+				'layout_strategy' => new EE_Template_Layout(
721
+					[
722
+						'layout_template_file' => SPCO_REG_STEPS_PATH
723
+							. $this->_slug
724
+							. '/events_requiring_pre_approval.template.php', // layout_template
725
+						'template_args'        => apply_filters(
726
+							'FHEE__EE_SPCO_Reg_Step_Payment_Options___sold_out_events__template_args',
727
+							[
728
+								'events_requiring_pre_approval'     => implode('', $events_requiring_pre_approval),
729
+								'events_requiring_pre_approval_msg' => apply_filters(
730
+									'FHEE__EE_SPCO_Reg_Step_Payment_Options___events_requiring_pre_approval__events_requiring_pre_approval_msg',
731
+									esc_html__(
732
+										'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.',
733
+										'event_espresso'
734
+									)
735
+								),
736
+							]
737
+						),
738
+					]
739
+				),
740
+			]
741
+		);
742
+	}
743
+
744
+
745
+	/**
746
+	 * @param EE_Event[] $registrations_for_free_events
747
+	 * @return EE_Form_Section_Proper
748
+	 * @throws EE_Error
749
+	 */
750
+	private function _no_payment_required(array $registrations_for_free_events = []): EE_Form_Section_Proper
751
+	{
752
+		// set some defaults
753
+		$this->checkout->selected_method_of_payment = 'no_payment_required';
754
+		// generate no_payment_required form
755
+		return new EE_Form_Section_Proper(
756
+			[
757
+				'layout_strategy' => new EE_Template_Layout(
758
+					[
759
+						'layout_template_file' => SPCO_REG_STEPS_PATH
760
+							. $this->_slug
761
+							. '/no_payment_required.template.php', // layout_template
762
+						'template_args'        => apply_filters(
763
+							'FHEE__EE_SPCO_Reg_Step_Payment_Options___no_payment_required__template_args',
764
+							[
765
+								'revisit'                       => $this->checkout->revisit,
766
+								'registrations'                 => [],
767
+								'ticket_count'                  => [],
768
+								'registrations_for_free_events' => $registrations_for_free_events,
769
+								'no_payment_required_msg'       => EEH_HTML::p(
770
+									esc_html__('This is a free event, so no billing will occur.', 'event_espresso')
771
+								),
772
+							]
773
+						),
774
+					]
775
+				),
776
+			]
777
+		);
778
+	}
779
+
780
+
781
+	/**
782
+	 * @param string $transaction_details HTML from EE_SPCO_Line_Item_Display_Strategy
783
+	 * @return EE_Form_Section_Proper
784
+	 * @throws EE_Error
785
+	 * @throws ReflectionException
786
+	 */
787
+	private function _display_payment_options(string $transaction_details = ''): EE_Form_Section_Proper
788
+	{
789
+		// build payment options form
790
+		return apply_filters(
791
+			'FHEE__EE_SPCO_Reg_Step_Payment_Options___display_payment_options__payment_options_form',
792
+			new EE_Form_Section_Proper(
793
+				[
794
+					'subsections'     => [
795
+						'before_payment_options' => apply_filters(
796
+							'FHEE__EE_SPCO_Reg_Step_Payment_Options___display_payment_options__before_payment_options',
797
+							new EE_Form_Section_Proper(
798
+								['layout_strategy' => new EE_Div_Per_Section_Layout()]
799
+							)
800
+						),
801
+						'payment_options'        => $this->_setup_payment_options(),
802
+						'after_payment_options'  => apply_filters(
803
+							'FHEE__EE_SPCO_Reg_Step_Payment_Options___display_payment_options__after_payment_options',
804
+							new EE_Form_Section_Proper(
805
+								['layout_strategy' => new EE_Div_Per_Section_Layout()]
806
+							)
807
+						),
808
+					],
809
+					'layout_strategy' => new EE_Template_Layout(
810
+						[
811
+							'layout_template_file' => $this->_template,
812
+							'template_args'        => apply_filters(
813
+								'FHEE__EE_SPCO_Reg_Step_Payment_Options___display_payment_options__template_args',
814
+								[
815
+									'reg_count'                 => $this->line_item_display->total_items(),
816
+									'transaction_details'       => $transaction_details,
817
+									'available_payment_methods' => [],
818
+								]
819
+							),
820
+						]
821
+					),
822
+				]
823
+			)
824
+		);
825
+	}
826
+
827
+
828
+	/**
829
+	 * @param bool $no_payment_required
830
+	 * @return EE_Form_Section_Proper
831
+	 * @throws EE_Error
832
+	 * @throws ReflectionException
833
+	 */
834
+	private function _extra_hidden_inputs(bool $no_payment_required = true): EE_Form_Section_Proper
835
+	{
836
+		return new EE_Form_Section_Proper(
837
+			[
838
+				'html_id'         => 'ee-' . $this->slug() . '-extra-hidden-inputs',
839
+				'layout_strategy' => new EE_Div_Per_Section_Layout(),
840
+				'subsections'     => [
841
+					'spco_no_payment_required' => new EE_Hidden_Input(
842
+						[
843
+							'normalization_strategy' => new EE_Boolean_Normalization(),
844
+							'html_name'              => 'spco_no_payment_required',
845
+							'html_id'                => 'spco-no-payment-required-payment_options',
846
+							'default'                => $no_payment_required,
847
+						]
848
+					),
849
+					'spco_transaction_id'      => new EE_Fixed_Hidden_Input(
850
+						[
851
+							'normalization_strategy' => new EE_Int_Normalization(),
852
+							'html_name'              => 'spco_transaction_id',
853
+							'html_id'                => 'spco-transaction-id',
854
+							'default'                => $this->checkout->transaction->ID(),
855
+						]
856
+					),
857
+				],
858
+			]
859
+		);
860
+	}
861
+
862
+
863
+	/**
864
+	 * @param array $registrations
865
+	 * @throws EE_Error
866
+	 * @throws ReflectionException
867
+	 */
868
+	protected function _apply_registration_payments_to_amount_owing(array $registrations)
869
+	{
870
+		$payments = [];
871
+		foreach ($registrations as $registration) {
872
+			if ($registration instanceof EE_Registration && $registration->owes_monies_and_can_pay()) {
873
+				$payments += $registration->registration_payments();
874
+			}
875
+		}
876
+		if (! empty($payments)) {
877
+			foreach ($payments as $payment) {
878
+				if ($payment instanceof EE_Registration_Payment) {
879
+					$this->checkout->amount_owing -= $payment->amount();
880
+				}
881
+			}
882
+		}
883
+	}
884
+
885
+
886
+	/**
887
+	 * @param bool $force_reset
888
+	 * @return void
889
+	 */
890
+	private function _reset_selected_method_of_payment(bool $force_reset = false)
891
+	{
892
+		/** @var RequestInterface $request */
893
+		$request              = LoaderFactory::getLoader()->getShared(RequestInterface::class);
894
+		$reset_payment_method = $request->getRequestParam('reset_payment_method', $force_reset, 'bool');
895
+		if ($reset_payment_method) {
896
+			$this->checkout->selected_method_of_payment = null;
897
+			$this->checkout->payment_method             = null;
898
+			$this->checkout->billing_form               = null;
899
+			$this->_save_selected_method_of_payment();
900
+		}
901
+	}
902
+
903
+
904
+	/**
905
+	 * stores the selected_method_of_payment in the session
906
+	 * so that it's available for all subsequent requests including AJAX
907
+	 *
908
+	 * @param string $selected_method_of_payment
909
+	 * @return void
910
+	 */
911
+	private function _save_selected_method_of_payment(string $selected_method_of_payment = '')
912
+	{
913
+		$selected_method_of_payment = ! empty($selected_method_of_payment)
914
+			? $selected_method_of_payment
915
+			: $this->checkout->selected_method_of_payment;
916
+		EE_Registry::instance()->SSN->set_session_data(
917
+			['selected_method_of_payment' => $selected_method_of_payment]
918
+		);
919
+	}
920
+
921
+
922
+	/**
923
+	 * @return EE_Form_Section_Proper
924
+	 * @throws EE_Error
925
+	 * @throws ReflectionException
926
+	 */
927
+	public function _setup_payment_options(): EE_Form_Section_Proper
928
+	{
929
+		// load payment method classes
930
+		if (empty($this->checkout->available_payment_methods)) {
931
+			EE_Error::add_error(
932
+				apply_filters(
933
+					'FHEE__EE_SPCO_Reg_Step_Payment_Options___setup_payment_options__error_message_no_payment_methods',
934
+					sprintf(
935
+						esc_html__(
936
+							'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.',
937
+							'event_espresso'
938
+						),
939
+						'<br>',
940
+						EE_Registry::instance()->CFG->organization->get_pretty('email')
941
+					)
942
+				),
943
+				__FILE__,
944
+				__FUNCTION__,
945
+				__LINE__
946
+			);
947
+		}
948
+		// switch up header depending on number of available payment methods
949
+		$payment_method_header     = count($this->checkout->available_payment_methods) > 1
950
+			? apply_filters(
951
+				'FHEE__registration_page_payment_options__method_of_payment_hdr',
952
+				esc_html__('Please Select Your Method of Payment', 'event_espresso')
953
+			)
954
+			: apply_filters(
955
+				'FHEE__registration_page_payment_options__method_of_payment_hdr',
956
+				esc_html__('Method of Payment', 'event_espresso')
957
+			);
958
+		$available_payment_methods = [
959
+			// display the "Payment Method" header
960
+			'payment_method_header' => new EE_Form_Section_HTML(
961
+				apply_filters(
962
+					'FHEE__EE_SPCO_Reg_Step_Payment_Options___setup_payment_options__payment_method_header',
963
+					EEH_HTML::h4($payment_method_header, 'method-of-payment-hdr'),
964
+					$payment_method_header
965
+				)
966
+			),
967
+		];
968
+		// the list of actual payment methods ( invoice, PayPal, etc ) in a  ( slug => HTML )  format
969
+		$available_payment_method_options = [];
970
+		$default_payment_method_option    = [];
971
+		// additional instructions to be displayed and hidden below payment methods (adding a clearing div to start)
972
+		$payment_methods_billing_info = [
973
+			new EE_Form_Section_HTML(
974
+				EEH_HTML::div('<br />', '', '', 'clear:both;')
975
+			),
976
+		];
977
+		// loop through payment methods
978
+		foreach ($this->checkout->available_payment_methods as $payment_method) {
979
+			if (! $payment_method instanceof EE_Payment_Method) {
980
+				continue;
981
+			}
982
+
983
+			$payment_method_button = EEH_HTML::img(
984
+				$payment_method->button_url(),
985
+				$payment_method->name(),
986
+				'spco-payment-method-' . $payment_method->slug() . '-btn-img',
987
+				'spco-payment-method-btn-img'
988
+			);
989
+			// check if any payment methods are set as default
990
+			// if payment method is already selected
991
+			// OR nothing is selected and this payment method is the default
992
+			if (
993
+				($this->checkout->selected_method_of_payment === $payment_method->slug())
994
+				|| (
995
+					! $this->checkout->selected_method_of_payment
996
+					&& $payment_method->open_by_default()
997
+				)
998
+			) {
999
+				$this->checkout->selected_method_of_payment = $payment_method->slug();
1000
+				$this->_save_selected_method_of_payment();
1001
+				$default_payment_method_option[ $payment_method->slug() ] = $payment_method_button;
1002
+			} else {
1003
+				$available_payment_method_options[ $payment_method->slug() ] = $payment_method_button;
1004
+			}
1005
+			$payment_methods_billing_info[ $payment_method->slug() . '-info' ] =
1006
+				$this->_payment_method_billing_info(
1007
+					$payment_method
1008
+				);
1009
+		}
1010
+		// prepend available_payment_method_options with default_payment_method_option so that it appears first in list
1011
+		// of PMs
1012
+		$available_payment_method_options = $default_payment_method_option + $available_payment_method_options;
1013
+		// now generate the actual form  inputs
1014
+		$available_payment_methods['available_payment_methods'] = $this->_available_payment_method_inputs(
1015
+			$available_payment_method_options
1016
+		);
1017
+		$available_payment_methods                              += $payment_methods_billing_info;
1018
+		// build the available payment methods form
1019
+		return new EE_Form_Section_Proper(
1020
+			[
1021
+				'html_id'         => 'spco-available-methods-of-payment-dv',
1022
+				'subsections'     => $available_payment_methods,
1023
+				'layout_strategy' => new EE_Div_Per_Section_Layout(),
1024
+			]
1025
+		);
1026
+	}
1027
+
1028
+
1029
+	/**
1030
+	 * @return EE_Payment_Method[]
1031
+	 * @throws EE_Error
1032
+	 * @throws ReflectionException
1033
+	 */
1034
+	protected function _get_available_payment_methods(): array
1035
+	{
1036
+		if (! empty($this->checkout->available_payment_methods)) {
1037
+			return $this->checkout->available_payment_methods;
1038
+		}
1039
+		$available_payment_methods = [];
1040
+		// get all active payment methods
1041
+		$payment_methods = $this->payment_method_model->get_all_for_transaction(
1042
+			$this->checkout->transaction,
1043
+			EEM_Payment_Method::scope_cart
1044
+		);
1045
+		foreach ($payment_methods as $payment_method) {
1046
+			if ($payment_method instanceof EE_Payment_Method) {
1047
+				$available_payment_methods[ $payment_method->slug() ] = $payment_method;
1048
+			}
1049
+		}
1050
+		return $available_payment_methods;
1051
+	}
1052
+
1053
+
1054
+	/**
1055
+	 * @param array $available_payment_method_options
1056
+	 * @return EE_Form_Section_Proper
1057
+	 * @throws EE_Error
1058
+	 * @throws EE_Error
1059
+	 */
1060
+	private function _available_payment_method_inputs(
1061
+		array $available_payment_method_options = []
1062
+	): EE_Form_Section_Proper {
1063
+		// generate inputs
1064
+		return new EE_Form_Section_Proper(
1065
+			[
1066
+				'html_id'         => 'ee-available-payment-method-inputs',
1067
+				'layout_strategy' => new EE_Div_Per_Section_Layout(),
1068
+				'subsections'     => [
1069
+					'' => new EE_Radio_Button_Input(
1070
+						$available_payment_method_options,
1071
+						[
1072
+							'html_name'          => 'selected_method_of_payment',
1073
+							'html_class'         => 'spco-payment-method',
1074
+							'default'            => $this->checkout->selected_method_of_payment,
1075
+							'label_size'         => 11,
1076
+							'enforce_label_size' => true,
1077
+						]
1078
+					),
1079
+				],
1080
+			]
1081
+		);
1082
+	}
1083
+
1084
+
1085
+	/**
1086
+	 * @param EE_Payment_Method $payment_method
1087
+	 * @return EE_Form_Section_Proper
1088
+	 * @throws EE_Error
1089
+	 * @throws ReflectionException
1090
+	 */
1091
+	private function _payment_method_billing_info(EE_Payment_Method $payment_method): EE_Form_Section_Proper
1092
+	{
1093
+		$currently_selected = $this->checkout->selected_method_of_payment === $payment_method->slug();
1094
+		// generate the billing form for payment method
1095
+		$billing_form                 = $currently_selected
1096
+			? $this->_get_billing_form_for_payment_method($payment_method)
1097
+			: new EE_Form_Section_HTML();
1098
+		$this->checkout->billing_form = $currently_selected
1099
+			? $billing_form
1100
+			: $this->checkout->billing_form;
1101
+		// it's all in the details
1102
+		$info_html = EEH_HTML::h3(
1103
+			esc_html__('Important information regarding your payment', 'event_espresso'),
1104
+			'',
1105
+			'spco-payment-method-hdr'
1106
+		);
1107
+		// add some info regarding the step, either from what's saved in the admin,
1108
+		// or a default string depending on whether the PM has a billing form or not
1109
+		if ($payment_method->description()) {
1110
+			$payment_method_info = $payment_method->description();
1111
+		} elseif ($billing_form instanceof EE_Billing_Info_Form) {
1112
+			$payment_method_info = sprintf(
1113
+				esc_html__(
1114
+					'Please provide the following billing information, then click the "%1$s" button below in order to proceed.',
1115
+					'event_espresso'
1116
+				),
1117
+				$this->submit_button_text()
1118
+			);
1119
+		} else {
1120
+			$payment_method_info = sprintf(
1121
+				esc_html__('Please click the "%1$s" button below in order to proceed.', 'event_espresso'),
1122
+				$this->submit_button_text()
1123
+			);
1124
+		}
1125
+		$info_html .= EEH_HTML::div(
1126
+			apply_filters(
1127
+				'FHEE__EE_SPCO_Reg_Step_Payment_Options___payment_method_billing_info__payment_method_info',
1128
+				$payment_method_info
1129
+			),
1130
+			'',
1131
+			'spco-payment-method-desc ee-attention'
1132
+		);
1133
+		return new EE_Form_Section_Proper(
1134
+			[
1135
+				'html_id'         => 'spco-payment-method-info-' . $payment_method->slug(),
1136
+				'html_class'      => 'spco-payment-method-info-dv',
1137
+				// only display the selected or default PM
1138
+				'html_style'      => $currently_selected ? '' : 'display:none;',
1139
+				'layout_strategy' => new EE_Div_Per_Section_Layout(),
1140
+				'subsections'     => [
1141
+					'info'         => new EE_Form_Section_HTML($info_html),
1142
+					'billing_form' => $currently_selected ? $billing_form : new EE_Form_Section_HTML(),
1143
+				],
1144
+			]
1145
+		);
1146
+	}
1147
+
1148
+
1149
+	/**
1150
+	 * @return bool
1151
+	 * @throws EE_Error
1152
+	 * @throws InvalidArgumentException
1153
+	 * @throws ReflectionException
1154
+	 * @throws InvalidDataTypeException
1155
+	 * @throws InvalidInterfaceException
1156
+	 */
1157
+	public function get_billing_form_html_for_payment_method(): bool
1158
+	{
1159
+		// how have they chosen to pay?
1160
+		$this->checkout->selected_method_of_payment = $this->_get_selected_method_of_payment(true);
1161
+		$this->checkout->payment_method             = $this->_get_payment_method_for_selected_method_of_payment();
1162
+		if (! $this->checkout->payment_method instanceof EE_Payment_Method) {
1163
+			return false;
1164
+		}
1165
+		if (
1166
+			apply_filters(
1167
+				'FHEE__EE_SPCO_Reg_Step_Payment_Options__registration_checkout__selected_payment_method__display_success',
1168
+				false
1169
+			)
1170
+		) {
1171
+			EE_Error::add_success(
1172
+				apply_filters(
1173
+					'FHEE__Single_Page_Checkout__registration_checkout__selected_payment_method',
1174
+					sprintf(
1175
+						esc_html__(
1176
+							'You have selected "%s" as your method of payment. Please note the important payment information below.',
1177
+							'event_espresso'
1178
+						),
1179
+						$this->checkout->payment_method->name()
1180
+					)
1181
+				)
1182
+			);
1183
+		}
1184
+		// now generate billing form for selected method of payment
1185
+		$payment_method_billing_form = $this->_get_billing_form_for_payment_method($this->checkout->payment_method);
1186
+		// fill form with attendee info if applicable
1187
+		if (
1188
+			$payment_method_billing_form instanceof EE_Billing_Attendee_Info_Form
1189
+			&& $this->checkout->transaction_has_primary_registrant()
1190
+		) {
1191
+			$payment_method_billing_form->populate_from_attendee(
1192
+				$this->checkout->transaction->primary_registration()->attendee()
1193
+			);
1194
+		}
1195
+		// and debug content
1196
+		if (
1197
+			$payment_method_billing_form instanceof EE_Billing_Info_Form
1198
+			&& $this->checkout->payment_method->type_obj() instanceof EE_PMT_Base
1199
+		) {
1200
+			$payment_method_billing_form =
1201
+				$this->checkout->payment_method->type_obj()->apply_billing_form_debug_settings(
1202
+					$payment_method_billing_form
1203
+				);
1204
+		}
1205
+		$billing_info = $payment_method_billing_form instanceof EE_Form_Section_Proper
1206
+			? $payment_method_billing_form->get_html()
1207
+			: '';
1208
+		$this->checkout->json_response->set_return_data(['payment_method_info' => $billing_info]);
1209
+		// localize validation rules for main form
1210
+		$this->checkout->current_step->reg_form->localize_validation_rules();
1211
+		$this->checkout->json_response->add_validation_rules(EE_Form_Section_Proper::js_localization());
1212
+		return true;
1213
+	}
1214
+
1215
+
1216
+	/**
1217
+	 * @param EE_Payment_Method $payment_method
1218
+	 * @return EE_Billing_Info_Form|EE_Billing_Attendee_Info_Form|EE_Form_Section_HTML
1219
+	 * @throws EE_Error
1220
+	 * @throws ReflectionException
1221
+	 */
1222
+	private function _get_billing_form_for_payment_method(EE_Payment_Method $payment_method)
1223
+	{
1224
+		$billing_form = $payment_method->type_obj()->billing_form(
1225
+			$this->checkout->transaction,
1226
+			['amount_owing' => $this->checkout->amount_owing]
1227
+		);
1228
+		if ($billing_form instanceof EE_Billing_Info_Form) {
1229
+			if (
1230
+				apply_filters(
1231
+					'FHEE__EE_SPCO_Reg_Step_Payment_Options__registration_checkout__selected_payment_method__display_success',
1232
+					false
1233
+				)
1234
+				&& $this->request->requestParamIsSet('payment_method')
1235
+			) {
1236
+				EE_Error::add_success(
1237
+					apply_filters(
1238
+						'FHEE__Single_Page_Checkout__registration_checkout__selected_payment_method',
1239
+						sprintf(
1240
+							esc_html__(
1241
+								'You have selected "%s" as your method of payment. Please note the important payment information below.',
1242
+								'event_espresso'
1243
+							),
1244
+							$payment_method->name()
1245
+						)
1246
+					)
1247
+				);
1248
+			}
1249
+			return apply_filters(
1250
+				'FHEE__EE_SPCO_Reg_Step_Payment_Options___get_billing_form_for_payment_method__billing_form',
1251
+				$billing_form,
1252
+				$payment_method
1253
+			);
1254
+		}
1255
+		// no actual billing form, so return empty HTML form section
1256
+		return new EE_Form_Section_HTML();
1257
+	}
1258
+
1259
+
1260
+	/**
1261
+	 * @param boolean $required whether to throw an error if the "selected_method_of_payment"
1262
+	 *                          is not found in the incoming request
1263
+	 * @param string  $request_param
1264
+	 * @return NULL|string
1265
+	 * @throws EE_Error
1266
+	 * @throws ReflectionException
1267
+	 */
1268
+	private function _get_selected_method_of_payment(
1269
+		bool $required = false,
1270
+		string $request_param = 'selected_method_of_payment'
1271
+	): ?string {
1272
+		// is selected_method_of_payment set in the request ?
1273
+		$selected_method_of_payment = $this->request->getRequestParam($request_param);
1274
+		if ($selected_method_of_payment) {
1275
+			// sanitize it
1276
+			$selected_method_of_payment = is_array($selected_method_of_payment)
1277
+				? array_shift($selected_method_of_payment)
1278
+				: $selected_method_of_payment;
1279
+			$selected_method_of_payment = sanitize_text_field($selected_method_of_payment);
1280
+			// store it in the session so that it's available for all subsequent requests including AJAX
1281
+			$this->_save_selected_method_of_payment($selected_method_of_payment);
1282
+		} else {
1283
+			// or is it set in the session ?
1284
+			$selected_method_of_payment = EE_Registry::instance()->SSN->get_session_data(
1285
+				'selected_method_of_payment'
1286
+			);
1287
+		}
1288
+		if (
1289
+			empty($selected_method_of_payment)
1290
+			&& $this->checkout->default_payment_method instanceof EE_Payment_Method
1291
+		) {
1292
+			$selected_method_of_payment = $this->checkout->default_payment_method->slug();
1293
+		}
1294
+		// still no payment method?
1295
+		if (empty($selected_method_of_payment) && $required) {
1296
+			EE_Error::add_error(
1297
+				sprintf(
1298
+					esc_html__(
1299
+						'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.',
1300
+						'event_espresso'
1301
+					),
1302
+					'<br/>',
1303
+					'<br/>',
1304
+					EE_Registry::instance()->CFG->organization->get_pretty('email')
1305
+				),
1306
+				__FILE__,
1307
+				__FUNCTION__,
1308
+				__LINE__
1309
+			);
1310
+			return null;
1311
+		}
1312
+		return $selected_method_of_payment;
1313
+	}
1314
+
1315
+
1316
+
1317
+
1318
+
1319
+
1320
+	/********************************************************************************************************/
1321
+	/***********************************  SWITCH PAYMENT METHOD  ************************************/
1322
+	/********************************************************************************************************/
1323
+
1324
+
1325
+	/**
1326
+	 * @return bool
1327
+	 * @throws EE_Error
1328
+	 * @throws ReflectionException
1329
+	 */
1330
+	public function switch_payment_method()
1331
+	{
1332
+		if (! $this->_verify_payment_method_is_set()) {
1333
+			return false;
1334
+		}
1335
+		if (
1336
+			apply_filters(
1337
+				'FHEE__EE_SPCO_Reg_Step_Payment_Options__registration_checkout__selected_payment_method__display_success',
1338
+				false
1339
+			)
1340
+		) {
1341
+			EE_Error::add_success(
1342
+				apply_filters(
1343
+					'FHEE__Single_Page_Checkout__registration_checkout__selected_payment_method',
1344
+					sprintf(
1345
+						esc_html__(
1346
+							'You have selected "%s" as your method of payment. Please note the important payment information below.',
1347
+							'event_espresso'
1348
+						),
1349
+						$this->checkout->payment_method->name()
1350
+					)
1351
+				)
1352
+			);
1353
+		}
1354
+		// generate billing form for selected method of payment if it hasn't been done already
1355
+		if ($this->checkout->payment_method->type_obj()->has_billing_form()) {
1356
+			$this->checkout->billing_form = $this->_get_billing_form_for_payment_method(
1357
+				$this->checkout->payment_method
1358
+			);
1359
+		}
1360
+		// fill form with attendee info if applicable
1361
+		if (
1362
+			apply_filters(
1363
+				'FHEE__populate_billing_form_fields_from_attendee',
1364
+				(
1365
+					$this->checkout->billing_form instanceof EE_Billing_Attendee_Info_Form
1366
+					&& $this->checkout->transaction_has_primary_registrant()
1367
+				),
1368
+				$this->checkout->billing_form,
1369
+				$this->checkout->transaction
1370
+			)
1371
+		) {
1372
+			$this->checkout->billing_form->populate_from_attendee(
1373
+				$this->checkout->transaction->primary_registration()->attendee()
1374
+			);
1375
+		}
1376
+		// and debug content
1377
+		if (
1378
+			$this->checkout->billing_form instanceof EE_Billing_Info_Form
1379
+			&& $this->checkout->payment_method->type_obj() instanceof EE_PMT_Base
1380
+		) {
1381
+			$this->checkout->billing_form =
1382
+				$this->checkout->payment_method->type_obj()->apply_billing_form_debug_settings(
1383
+					$this->checkout->billing_form
1384
+				);
1385
+		}
1386
+		// get HTML and validation rules for form
1387
+		if ($this->checkout->billing_form instanceof EE_Form_Section_Proper) {
1388
+			$this->checkout->json_response->set_return_data(
1389
+				['payment_method_info' => $this->checkout->billing_form->get_html()]
1390
+			);
1391
+			// localize validation rules for main form
1392
+			$this->checkout->billing_form->localize_validation_rules(true);
1393
+			$this->checkout->json_response->add_validation_rules(EE_Form_Section_Proper::js_localization());
1394
+		} else {
1395
+			$this->checkout->json_response->set_return_data(['payment_method_info' => '']);
1396
+		}
1397
+		// prevents advancement to next step
1398
+		$this->checkout->continue_reg = false;
1399
+		return true;
1400
+	}
1401
+
1402
+
1403
+	/**
1404
+	 * @return bool
1405
+	 * @throws EE_Error
1406
+	 * @throws InvalidArgumentException
1407
+	 * @throws ReflectionException
1408
+	 * @throws InvalidDataTypeException
1409
+	 * @throws InvalidInterfaceException
1410
+	 */
1411
+	protected function _verify_payment_method_is_set(): bool
1412
+	{
1413
+		// generate billing form for selected method of payment if it hasn't been done already
1414
+		if (empty($this->checkout->selected_method_of_payment)) {
1415
+			// how have they chosen to pay?
1416
+			$this->checkout->selected_method_of_payment = $this->_get_selected_method_of_payment(true);
1417
+		} else {
1418
+			// choose your own adventure based on method_of_payment
1419
+			switch ($this->checkout->selected_method_of_payment) {
1420
+				case 'events_sold_out':
1421
+					EE_Error::add_attention(
1422
+						apply_filters(
1423
+							'FHEE__EE_SPCO_Reg_Step_Payment_Options___verify_payment_method_is_set__sold_out_events_msg',
1424
+							esc_html__(
1425
+								'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.',
1426
+								'event_espresso'
1427
+							)
1428
+						),
1429
+						__FILE__,
1430
+						__FUNCTION__,
1431
+						__LINE__
1432
+					);
1433
+					return false;
1434
+				case 'payments_closed':
1435
+					EE_Error::add_attention(
1436
+						apply_filters(
1437
+							'FHEE__EE_SPCO_Reg_Step_Payment_Options___verify_payment_method_is_set__payments_closed_msg',
1438
+							esc_html__(
1439
+								'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.',
1440
+								'event_espresso'
1441
+							)
1442
+						),
1443
+						__FILE__,
1444
+						__FUNCTION__,
1445
+						__LINE__
1446
+					);
1447
+					return false;
1448
+				case 'no_payment_required':
1449
+					EE_Error::add_attention(
1450
+						apply_filters(
1451
+							'FHEE__EE_SPCO_Reg_Step_Payment_Options___verify_payment_method_is_set__no_payment_required_msg',
1452
+							esc_html__(
1453
+								'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.',
1454
+								'event_espresso'
1455
+							)
1456
+						),
1457
+						__FILE__,
1458
+						__FUNCTION__,
1459
+						__LINE__
1460
+					);
1461
+					return false;
1462
+				default:
1463
+			}
1464
+		}
1465
+		// verify payment method
1466
+		if (! $this->checkout->payment_method instanceof EE_Payment_Method) {
1467
+			// get payment method for selected method of payment
1468
+			$this->checkout->payment_method = $this->_get_payment_method_for_selected_method_of_payment();
1469
+		}
1470
+		return $this->checkout->payment_method instanceof EE_Payment_Method;
1471
+	}
1472
+
1473
+
1474
+
1475
+	/********************************************************************************************************/
1476
+	/***************************************  SAVE PAYER DETAILS  ****************************************/
1477
+	/********************************************************************************************************/
1478
+
1479
+
1480
+	/**
1481
+	 * @return void
1482
+	 * @throws EE_Error
1483
+	 * @throws InvalidArgumentException
1484
+	 * @throws ReflectionException
1485
+	 * @throws RuntimeException
1486
+	 * @throws InvalidDataTypeException
1487
+	 * @throws InvalidInterfaceException
1488
+	 */
1489
+	public function save_payer_details_via_ajax()
1490
+	{
1491
+		if (! $this->_verify_payment_method_is_set()) {
1492
+			return;
1493
+		}
1494
+		// generate billing form for selected method of payment if it hasn't been done already
1495
+		if ($this->checkout->payment_method->type_obj()->has_billing_form()) {
1496
+			$this->checkout->billing_form = $this->_get_billing_form_for_payment_method(
1497
+				$this->checkout->payment_method
1498
+			);
1499
+		}
1500
+		// generate primary attendee from payer info if applicable
1501
+		if (! $this->checkout->transaction_has_primary_registrant()) {
1502
+			$attendee = $this->_create_attendee_from_request_data();
1503
+			if ($attendee instanceof EE_Attendee) {
1504
+				foreach ($this->checkout->transaction->registrations() as $registration) {
1505
+					if ($registration->is_primary_registrant()) {
1506
+						$this->checkout->primary_attendee_obj = $attendee;
1507
+						$registration->_add_relation_to($attendee, 'Attendee');
1508
+						$registration->set_attendee_id($attendee->ID());
1509
+						$registration->update_cache_after_object_save('Attendee', $attendee);
1510
+					}
1511
+				}
1512
+			}
1513
+		}
1514
+	}
1515
+
1516
+
1517
+	/**
1518
+	 * uses info from alternate GET or POST data (such as AJAX) to create a new attendee
1519
+	 *
1520
+	 * @return EE_Attendee
1521
+	 * @throws EE_Error
1522
+	 * @throws InvalidArgumentException
1523
+	 * @throws ReflectionException
1524
+	 * @throws InvalidDataTypeException
1525
+	 * @throws InvalidInterfaceException
1526
+	 */
1527
+	protected function _create_attendee_from_request_data(): EE_Attendee
1528
+	{
1529
+		// get State ID
1530
+		$STA_ID = $this->request->getRequestParam('state');
1531
+		if (! empty($STA_ID)) {
1532
+			// can we get state object from name ?
1533
+			EE_Registry::instance()->load_model('State');
1534
+			$state  = EEM_State::instance()->get_col([['STA_name' => $STA_ID], 'limit' => 1], 'STA_ID');
1535
+			$STA_ID = is_array($state) && ! empty($state) ? reset($state) : $STA_ID;
1536
+		}
1537
+		// get Country ISO
1538
+		$CNT_ISO = $this->request->getRequestParam('country');
1539
+		if (! empty($CNT_ISO)) {
1540
+			// can we get country object from name ?
1541
+			EE_Registry::instance()->load_model('Country');
1542
+			$country = EEM_Country::instance()->get_col(
1543
+				[['CNT_name' => $CNT_ISO], 'limit' => 1],
1544
+				'CNT_ISO'
1545
+			);
1546
+			$CNT_ISO = is_array($country) && ! empty($country) ? reset($country) : $CNT_ISO;
1547
+		}
1548
+		// grab attendee data
1549
+		$attendee_data = [
1550
+			'ATT_fname'    => $this->request->getRequestParam('first_name'),
1551
+			'ATT_lname'    => $this->request->getRequestParam('last_name'),
1552
+			'ATT_email'    => $this->request->getRequestParam('email'),
1553
+			'ATT_address'  => $this->request->getRequestParam('address'),
1554
+			'ATT_address2' => $this->request->getRequestParam('address2'),
1555
+			'ATT_city'     => $this->request->getRequestParam('city'),
1556
+			'STA_ID'       => $STA_ID,
1557
+			'CNT_ISO'      => $CNT_ISO,
1558
+			'ATT_zip'      => $this->request->getRequestParam('zip'),
1559
+			'ATT_phone'    => $this->request->getRequestParam('phone'),
1560
+		];
1561
+		// validate the email address since it is the most important piece of info
1562
+		if (empty($attendee_data['ATT_email'])) {
1563
+			EE_Error::add_error(
1564
+				esc_html__('An invalid email address was submitted.', 'event_espresso'),
1565
+				__FILE__,
1566
+				__FUNCTION__,
1567
+				__LINE__
1568
+			);
1569
+		}
1570
+		// does this attendee already exist in the db ? we're searching using a combination of first name, last name,
1571
+		// AND email address
1572
+		if (
1573
+			! empty($attendee_data['ATT_fname'])
1574
+			&& ! empty($attendee_data['ATT_lname'])
1575
+			&& ! empty($attendee_data['ATT_email'])
1576
+		) {
1577
+			$existing_attendee = EEM_Attendee::instance()->find_existing_attendee(
1578
+				[
1579
+					'ATT_fname' => $attendee_data['ATT_fname'],
1580
+					'ATT_lname' => $attendee_data['ATT_lname'],
1581
+					'ATT_email' => $attendee_data['ATT_email'],
1582
+				]
1583
+			);
1584
+			if ($existing_attendee instanceof EE_Attendee) {
1585
+				return $existing_attendee;
1586
+			}
1587
+		}
1588
+		// no existing attendee? kk let's create a new one
1589
+		// kinda lame, but we need a first and last name to create an attendee, so use the email address if those
1590
+		// don't exist
1591
+		$attendee_data['ATT_fname'] = ! empty($attendee_data['ATT_fname'])
1592
+			? $attendee_data['ATT_fname']
1593
+			: $attendee_data['ATT_email'];
1594
+		$attendee_data['ATT_lname'] = ! empty($attendee_data['ATT_lname'])
1595
+			? $attendee_data['ATT_lname']
1596
+			: $attendee_data['ATT_email'];
1597
+		return EE_Attendee::new_instance($attendee_data);
1598
+	}
1599
+
1600
+
1601
+
1602
+	/********************************************************************************************************/
1603
+	/****************************************  PROCESS REG STEP  *****************************************/
1604
+	/********************************************************************************************************/
1605
+
1606
+
1607
+	/**
1608
+	 * @return bool
1609
+	 * @throws EE_Error
1610
+	 * @throws InvalidArgumentException
1611
+	 * @throws ReflectionException
1612
+	 * @throws EntityNotFoundException
1613
+	 * @throws InvalidDataTypeException
1614
+	 * @throws InvalidInterfaceException
1615
+	 * @throws InvalidStatusException
1616
+	 */
1617
+	public function process_reg_step(): bool
1618
+	{
1619
+		// how have they chosen to pay?
1620
+		$this->checkout->selected_method_of_payment = $this->checkout->transaction->is_free()
1621
+			? 'no_payment_required'
1622
+			: $this->_get_selected_method_of_payment(true);
1623
+		// choose your own adventure based on method_of_payment
1624
+		switch ($this->checkout->selected_method_of_payment) {
1625
+			case 'events_sold_out':
1626
+				$this->checkout->redirect     = true;
1627
+				$this->checkout->redirect_url = $this->checkout->cancel_page_url;
1628
+				$this->checkout->json_response->set_redirect_url($this->checkout->redirect_url);
1629
+				// mark this reg step as completed
1630
+				$this->set_completed();
1631
+				return false;
1632
+
1633
+			case 'payments_closed':
1634
+				if (
1635
+					apply_filters(
1636
+						'FHEE__EE_SPCO_Reg_Step_Payment_Options__process_reg_step__payments_closed__display_success',
1637
+						false
1638
+					)
1639
+				) {
1640
+					EE_Error::add_success(
1641
+						esc_html__('no payment required at this time.', 'event_espresso'),
1642
+						__FILE__,
1643
+						__FUNCTION__,
1644
+						__LINE__
1645
+					);
1646
+				}
1647
+				// mark this reg step as completed
1648
+				$this->set_completed();
1649
+				return true;
1650
+
1651
+			case 'no_payment_required':
1652
+				if (
1653
+					apply_filters(
1654
+						'FHEE__EE_SPCO_Reg_Step_Payment_Options__process_reg_step__no_payment_required__display_success',
1655
+						false
1656
+					)
1657
+				) {
1658
+					EE_Error::add_success(
1659
+						esc_html__('no payment required.', 'event_espresso'),
1660
+						__FILE__,
1661
+						__FUNCTION__,
1662
+						__LINE__
1663
+					);
1664
+				}
1665
+				// mark this reg step as completed
1666
+				$this->set_completed();
1667
+				return true;
1668
+
1669
+			default:
1670
+				$registrations         = EE_Registry::instance()->SSN->checkout()->transaction->registrations(
1671
+					EE_Registry::instance()->SSN->checkout()->reg_cache_where_params
1672
+				);
1673
+				$ejected_registrations = EE_SPCO_Reg_Step_Payment_Options::find_registrations_that_lost_their_space(
1674
+					$registrations,
1675
+					EE_Registry::instance()->SSN->checkout()->revisit
1676
+				);
1677
+				// calculate difference between the two arrays
1678
+				$registrations = array_diff($registrations, $ejected_registrations);
1679
+				if (empty($registrations)) {
1680
+					$this->_redirect_because_event_sold_out();
1681
+					return false;
1682
+				}
1683
+				$payment = $this->_process_payment();
1684
+				if ($payment instanceof EE_Payment) {
1685
+					$this->checkout->continue_reg = true;
1686
+					$this->_maybe_set_completed($payment);
1687
+				} else {
1688
+					$this->checkout->continue_reg = false;
1689
+				}
1690
+				return $payment instanceof EE_Payment;
1691
+		}
1692
+	}
1693
+
1694
+
1695
+	/**
1696
+	 * @return void
1697
+	 */
1698
+	protected function _redirect_because_event_sold_out()
1699
+	{
1700
+		$this->checkout->continue_reg = false;
1701
+		// set redirect URL
1702
+		$this->checkout->redirect_url = add_query_arg(
1703
+			['e_reg_url_link' => $this->checkout->reg_url_link],
1704
+			$this->checkout->current_step->reg_step_url()
1705
+		);
1706
+		$this->checkout->json_response->set_redirect_url($this->checkout->redirect_url);
1707
+	}
1708
+
1709
+
1710
+	/**
1711
+	 * @param EE_Payment $payment
1712
+	 * @return void
1713
+	 * @throws EE_Error
1714
+	 */
1715
+	protected function _maybe_set_completed(EE_Payment $payment)
1716
+	{
1717
+		// Do we need to redirect them? If so, there's more work to be done.
1718
+		if (! $payment->redirect_url()) {
1719
+			$this->set_completed();
1720
+		}
1721
+	}
1722
+
1723
+
1724
+	/**
1725
+	 * this is the final step after a user  revisits the site to retry a payment
1726
+	 *
1727
+	 * @return bool
1728
+	 * @throws EE_Error
1729
+	 * @throws InvalidArgumentException
1730
+	 * @throws ReflectionException
1731
+	 * @throws EntityNotFoundException
1732
+	 * @throws InvalidDataTypeException
1733
+	 * @throws InvalidInterfaceException
1734
+	 * @throws InvalidStatusException
1735
+	 */
1736
+	public function update_reg_step(): bool
1737
+	{
1738
+		$success = true;
1739
+		// if payment required
1740
+		if ($this->checkout->transaction->total() > 0) {
1741
+			do_action(
1742
+				'AHEE__EE_Single_Page_Checkout__process_finalize_registration__before_gateway',
1743
+				$this->checkout->transaction
1744
+			);
1745
+			// attempt payment via payment method
1746
+			$success = $this->process_reg_step();
1747
+		}
1748
+		if ($success && ! $this->checkout->redirect) {
1749
+			$this->checkout->cart->get_grand_total()->save_this_and_descendants_to_txn(
1750
+				$this->checkout->transaction->ID()
1751
+			);
1752
+			// set return URL
1753
+			$this->checkout->redirect_url = add_query_arg(
1754
+				['e_reg_url_link' => $this->checkout->reg_url_link],
1755
+				$this->checkout->thank_you_page_url
1756
+			);
1757
+		}
1758
+		return $success;
1759
+	}
1760
+
1761
+
1762
+	/**
1763
+	 * @return EE_Payment|bool|null
1764
+	 * @throws EE_Error
1765
+	 * @throws InvalidArgumentException
1766
+	 * @throws ReflectionException
1767
+	 * @throws RuntimeException
1768
+	 * @throws InvalidDataTypeException
1769
+	 * @throws InvalidInterfaceException
1770
+	 */
1771
+	private function _process_payment()
1772
+	{
1773
+		// basically confirm that the event hasn't sold out since they hit the page
1774
+		if (! $this->_last_second_ticket_verifications()) {
1775
+			return null;
1776
+		}
1777
+		// ya gotta make a choice man
1778
+		if (empty($this->checkout->selected_method_of_payment)) {
1779
+			$this->checkout->json_response->set_plz_select_method_of_payment(
1780
+				esc_html__('Please select a method of payment before proceeding.', 'event_espresso')
1781
+			);
1782
+			return null;
1783
+		}
1784
+		// get EE_Payment_Method object
1785
+		if (! $this->checkout->payment_method = $this->_get_payment_method_for_selected_method_of_payment()) {
1786
+			return null;
1787
+		}
1788
+		// setup billing form
1789
+		if ($this->checkout->payment_method->type_obj()->has_billing_form()) {
1790
+			$this->checkout->billing_form = $this->_get_billing_form_for_payment_method(
1791
+				$this->checkout->payment_method
1792
+			);
1793
+			// bad billing form ?
1794
+			if (! $this->_billing_form_is_valid()) {
1795
+				return null;
1796
+			}
1797
+		}
1798
+		// ensure primary registrant has been fully processed
1799
+		if (! $this->_setup_primary_registrant_prior_to_payment()) {
1800
+			return null;
1801
+		}
1802
+		// if session is close to expiring (under 10 minutes by default)
1803
+		if ((time() - EE_Registry::instance()->SSN->expiration()) < EE_Registry::instance()->SSN->extension()) {
1804
+			// add some time to session expiration so that payment can be completed
1805
+			EE_Registry::instance()->SSN->extend_expiration();
1806
+		}
1807
+		/** @type EE_Transaction_Processor $transaction_processor */
1808
+		// $transaction_processor = EE_Registry::instance()->load_class( 'Transaction_Processor' );
1809
+		// in case a registrant leaves to an Off-Site Gateway and never returns, we want to approve any registrations
1810
+		// for events with a default reg status of Approved
1811
+		// $transaction_processor->toggle_registration_statuses_for_default_approved_events(
1812
+		//      $this->checkout->transaction, $this->checkout->reg_cache_where_params
1813
+		// );
1814
+		// attempt payment
1815
+		$payment = $this->_attempt_payment($this->checkout->payment_method);
1816
+		// process results
1817
+		$payment = $this->_validate_payment($payment);
1818
+		$payment = $this->_post_payment_processing($payment);
1819
+		// verify payment
1820
+		if ($payment instanceof EE_Payment) {
1821
+			// store that for later
1822
+			$this->checkout->payment = $payment;
1823
+			// we can also consider the TXN to not have been failed, so temporarily upgrade its status to abandoned
1824
+			$this->checkout->transaction->toggle_failed_transaction_status();
1825
+			$payment_status = $payment->status();
1826
+			if (
1827
+				$payment_status === EEM_Payment::status_id_approved
1828
+				|| $payment_status === EEM_Payment::status_id_pending
1829
+			) {
1830
+				return $payment;
1831
+			}
1832
+			return null;
1833
+		}
1834
+		if ($payment === true) {
1835
+			// please note that offline payment methods will NOT make a payment,
1836
+			// but instead just mark themselves as the PMD_ID on the transaction, and return true
1837
+			$this->checkout->payment = true;
1838
+			return true;
1839
+		}
1840
+		// where's my money?
1841
+		return null;
1842
+	}
1843
+
1844
+
1845
+	/**
1846
+	 * @return bool
1847
+	 * @throws EE_Error
1848
+	 * @throws ReflectionException
1849
+	 */
1850
+	protected function _last_second_ticket_verifications(): bool
1851
+	{
1852
+		// don't bother re-validating if not a return visit
1853
+		if (! $this->checkout->revisit) {
1854
+			return true;
1855
+		}
1856
+		$registrations = $this->checkout->transaction->registrations();
1857
+		if (empty($registrations)) {
1858
+			return false;
1859
+		}
1860
+		foreach ($registrations as $registration) {
1861
+			if ($registration instanceof EE_Registration && ! $registration->is_approved()) {
1862
+				$event = $registration->event_obj();
1863
+				if ($event instanceof EE_Event && $event->is_sold_out(true)) {
1864
+					EE_Error::add_error(
1865
+						apply_filters(
1866
+							'FHEE__EE_SPCO_Reg_Step_Payment_Options___last_second_ticket_verifications__sold_out_events_msg',
1867
+							sprintf(
1868
+								esc_html__(
1869
+									'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.',
1870
+									'event_espresso'
1871
+								),
1872
+								$event->name()
1873
+							)
1874
+						),
1875
+						__FILE__,
1876
+						__FUNCTION__,
1877
+						__LINE__
1878
+					);
1879
+					return false;
1880
+				}
1881
+			}
1882
+		}
1883
+		return true;
1884
+	}
1885
+
1886
+
1887
+	/**
1888
+	 * @return bool
1889
+	 * @throws EE_Error
1890
+	 * @throws InvalidArgumentException
1891
+	 * @throws ReflectionException
1892
+	 * @throws InvalidDataTypeException
1893
+	 * @throws InvalidInterfaceException
1894
+	 */
1895
+	public function redirect_form(): bool
1896
+	{
1897
+		$payment_method_billing_info = $this->_payment_method_billing_info(
1898
+			$this->_get_payment_method_for_selected_method_of_payment()
1899
+		);
1900
+		$html                        = $payment_method_billing_info->get_html();
1901
+		$html                        .= $this->checkout->redirect_form;
1902
+		/** @var ResponseInterface $response */
1903
+		$response = LoaderFactory::getLoader()->getShared(ResponseInterface::class);
1904
+		$response->addOutput($html);
1905
+		return true;
1906
+	}
1907
+
1908
+
1909
+	/**
1910
+	 * @return bool
1911
+	 * @throws EE_Error
1912
+	 * @throws ReflectionException
1913
+	 */
1914
+	private function _billing_form_is_valid(): bool
1915
+	{
1916
+		if (! $this->checkout->payment_method->type_obj()->has_billing_form()) {
1917
+			return true;
1918
+		}
1919
+		if ($this->checkout->billing_form instanceof EE_Billing_Info_Form) {
1920
+			if ($this->checkout->billing_form->was_submitted()) {
1921
+				$this->checkout->billing_form->receive_form_submission();
1922
+				if ($this->checkout->billing_form->is_valid()) {
1923
+					return true;
1924
+				}
1925
+				$validation_errors = $this->checkout->billing_form->get_validation_errors_accumulated();
1926
+				$error_strings     = [];
1927
+				foreach ($validation_errors as $validation_error) {
1928
+					if ($validation_error instanceof EE_Validation_Error) {
1929
+						$form_section = $validation_error->get_form_section();
1930
+						if ($form_section instanceof EE_Form_Input_Base) {
1931
+							$label = $form_section->html_label_text();
1932
+						} elseif ($form_section instanceof EE_Form_Section_Base) {
1933
+							$label = $form_section->name();
1934
+						} else {
1935
+							$label = esc_html__('Validation Error', 'event_espresso');
1936
+						}
1937
+						$error_strings[] = sprintf('%1$s: %2$s', $label, $validation_error->getMessage());
1938
+					}
1939
+				}
1940
+				EE_Error::add_error(
1941
+					sprintf(
1942
+						esc_html__(
1943
+							'One or more billing form inputs are invalid and require correction before proceeding. %1$s %2$s',
1944
+							'event_espresso'
1945
+						),
1946
+						'<br/>',
1947
+						implode('<br/>', $error_strings)
1948
+					),
1949
+					__FILE__,
1950
+					__FUNCTION__,
1951
+					__LINE__
1952
+				);
1953
+			} else {
1954
+				EE_Error::add_error(
1955
+					esc_html__(
1956
+						'The billing form was not submitted or something prevented it\'s submission.',
1957
+						'event_espresso'
1958
+					),
1959
+					__FILE__,
1960
+					__FUNCTION__,
1961
+					__LINE__
1962
+				);
1963
+			}
1964
+		} else {
1965
+			EE_Error::add_error(
1966
+				esc_html__(
1967
+					'The submitted billing form is invalid possibly due to a technical reason.',
1968
+					'event_espresso'
1969
+				),
1970
+				__FILE__,
1971
+				__FUNCTION__,
1972
+				__LINE__
1973
+			);
1974
+		}
1975
+		return false;
1976
+	}
1977
+
1978
+
1979
+	/**
1980
+	 * ensures that the primary registrant has a valid attendee object created with the critical details populated
1981
+	 * (first & last name & email) and that both the transaction object and primary registration object have been saved
1982
+	 * plz note that any other registrations will NOT be saved at this point (because they may not have any details
1983
+	 * yet)
1984
+	 *
1985
+	 * @return bool
1986
+	 * @throws EE_Error
1987
+	 * @throws InvalidArgumentException
1988
+	 * @throws ReflectionException
1989
+	 * @throws RuntimeException
1990
+	 * @throws InvalidDataTypeException
1991
+	 * @throws InvalidInterfaceException
1992
+	 */
1993
+	private function _setup_primary_registrant_prior_to_payment(): bool
1994
+	{
1995
+		// check if transaction has a primary registrant and that it has a related Attendee object
1996
+		// if not, then we need to at least gather some primary registrant data before attempting payment
1997
+		if (
1998
+			$this->checkout->billing_form instanceof EE_Billing_Attendee_Info_Form
1999
+			&& ! $this->checkout->transaction_has_primary_registrant()
2000
+			&& ! $this->_capture_primary_registration_data_from_billing_form()
2001
+		) {
2002
+			return false;
2003
+		}
2004
+		// because saving an object clears its cache, we need to do the Chevy Shuffle
2005
+		// grab the primary_registration object
2006
+		$primary_registration = $this->checkout->transaction->primary_registration();
2007
+		// at this point we'll consider a TXN to not have been failed
2008
+		$this->checkout->transaction->toggle_failed_transaction_status();
2009
+		// save the TXN ( which clears cached copy of primary_registration)
2010
+		$this->checkout->transaction->save();
2011
+		// grab TXN ID and save it to the primary_registration
2012
+		$primary_registration->set_transaction_id($this->checkout->transaction->ID());
2013
+		// save what we have so far
2014
+		$primary_registration->save();
2015
+		return true;
2016
+	}
2017
+
2018
+
2019
+	/**
2020
+	 * Captures primary registration data from the billing form.
2021
+	 *
2022
+	 * This method is used to gather the primary registrant data before attempting payment.
2023
+	 * It checks if the billing form is an instance of EE_Billing_Attendee_Info_Form and if the transaction
2024
+	 * has a primary registrant. If not, it captures the primary registrant data from the billing form.
2025
+	 *
2026
+	 * @return bool
2027
+	 * @throws EE_Error
2028
+	 * @throws InvalidArgumentException
2029
+	 * @throws ReflectionException
2030
+	 * @throws InvalidDataTypeException
2031
+	 * @throws InvalidInterfaceException
2032
+	 */
2033
+	private function _capture_primary_registration_data_from_billing_form(): bool
2034
+	{
2035
+		$primary_registration = $this->checkout->transaction->primary_registration();
2036
+		if (! $this->validatePrimaryRegistration($primary_registration)) {
2037
+			return false;
2038
+		}
2039
+
2040
+		$primary_attendee = $this->getPrimaryAttendee($primary_registration);
2041
+		if (! $this->validatePrimaryAttendee($primary_attendee)) {
2042
+			return false;
2043
+		}
2044
+
2045
+		if (! $this->addAttendeeToPrimaryRegistration($primary_attendee, $primary_registration)) {
2046
+			return false;
2047
+		}
2048
+		// both the primary registration and primary attendee objects should be valid entities at this point
2049
+		$this->checkout->primary_attendee_obj = $primary_attendee;
2050
+
2051
+		/** @type EE_Registration_Processor $registration_processor */
2052
+		$registration_processor = EE_Registry::instance()->load_class('Registration_Processor');
2053
+		// at this point, we should have enough details about the registrant to consider the registration NOT incomplete
2054
+		$registration_processor->toggle_incomplete_registration_status_to_default(
2055
+			$primary_registration,
2056
+			false,
2057
+			new Context(
2058
+				__METHOD__,
2059
+				esc_html__(
2060
+					'Executed when the primary registrant\'s status is updated during the registration process when processing a billing form.',
2061
+					'event_espresso'
2062
+				)
2063
+			)
2064
+		);
2065
+		return true;
2066
+	}
2067
+
2068
+
2069
+	/**
2070
+	 * returns true if the primary registration is a valid entity
2071
+	 *
2072
+	 * @param $primary_registration
2073
+	 * @return bool
2074
+	 * @throws EE_Error
2075
+	 * @since 5.0.21.p
2076
+	 */
2077
+	private function validatePrimaryRegistration($primary_registration): bool
2078
+	{
2079
+		if ($primary_registration instanceof EE_Registration) {
2080
+			return true;
2081
+		}
2082
+		EE_Error::add_error(
2083
+			sprintf(
2084
+				esc_html__(
2085
+					'The primary registrant for this transaction could not be determined due to a technical issue.%sPlease try again or contact %s for assistance.',
2086
+					'event_espresso'
2087
+				),
2088
+				'<br/>',
2089
+				EE_Registry::instance()->CFG->organization->get_pretty('email')
2090
+			),
2091
+			__FILE__,
2092
+			__FUNCTION__,
2093
+			__LINE__
2094
+		);
2095
+		return false;
2096
+	}
2097
+
2098
+
2099
+	/**
2100
+	 * retrieves the primary attendee object for the primary registration and copies the billing form data to it.
2101
+	 * if the primary registration does not have an attendee object, then one is created from the billing form info
2102
+	 *
2103
+	 * @param EE_Registration $primary_registration
2104
+	 * @return EE_Attendee|null
2105
+	 * @throws EE_Error
2106
+	 * @throws ReflectionException
2107
+	 * @since 5.0.21.p
2108
+	 */
2109
+	private function getPrimaryAttendee(EE_Registration $primary_registration): ?EE_Attendee
2110
+	{
2111
+		// if we have a primary registration, then we should have a primary attendee
2112
+		$attendee = $primary_registration->attendee();
2113
+		if ($attendee instanceof EE_Attendee) {
2114
+			return $this->checkout->billing_form->copy_billing_form_data_to_attendee($attendee);
2115
+		}
2116
+		// if not, then we need to create one from the billing form
2117
+		return $this->checkout->billing_form->create_attendee_from_billing_form_data();
2118
+	}
2119
+
2120
+
2121
+	/**
2122
+	 * returns true if the primary attendee is a valid entity
2123
+	 *
2124
+	 * @param $primary_attendee
2125
+	 * @return bool
2126
+	 * @throws EE_Error
2127
+	 * @since 5.0.21.p
2128
+	 */
2129
+	private function validatePrimaryAttendee($primary_attendee): bool
2130
+	{
2131
+		if ($primary_attendee instanceof EE_Attendee) {
2132
+			return true;
2133
+		}
2134
+		EE_Error::add_error(
2135
+			sprintf(
2136
+				esc_html__(
2137
+					'The billing form details could not be used for attendee details due to a technical issue.%sPlease try again or contact %s for assistance.',
2138
+					'event_espresso'
2139
+				),
2140
+				'<br/>',
2141
+				EE_Registry::instance()->CFG->organization->get_pretty('email')
2142
+			),
2143
+			__FILE__,
2144
+			__FUNCTION__,
2145
+			__LINE__
2146
+		);
2147
+		return false;
2148
+	}
2149
+
2150
+
2151
+	/**
2152
+	 * returns true if the attendee was successfully added to the primary registration
2153
+	 *
2154
+	 * @param EE_Attendee     $primary_attendee
2155
+	 * @param EE_Registration $primary_registration
2156
+	 * @return bool
2157
+	 * @throws EE_Error
2158
+	 * @throws ReflectionException
2159
+	 * @since 5.0.21.p
2160
+	 */
2161
+	private function addAttendeeToPrimaryRegistration(
2162
+		EE_Attendee $primary_attendee,
2163
+		EE_Registration $primary_registration
2164
+	): bool {
2165
+		// ensure attendee has an ID by saving
2166
+		$primary_attendee->save();
2167
+
2168
+		// compare attendee IDs
2169
+		if ($primary_registration->attendee_id() === $primary_attendee->ID()) {
2170
+			return true;
2171
+		}
2172
+
2173
+		$primary_attendee = $primary_registration->_add_relation_to($primary_attendee, 'Attendee');
2174
+		if ($primary_attendee instanceof EE_Attendee) {
2175
+			return true;
2176
+		}
2177
+
2178
+		EE_Error::add_error(
2179
+			sprintf(
2180
+				esc_html__(
2181
+					'The primary registrant could not be associated with this transaction due to a technical issue.%sPlease try again or contact %s for assistance.',
2182
+					'event_espresso'
2183
+				),
2184
+				'<br/>',
2185
+				EE_Registry::instance()->CFG->organization->get_pretty('email')
2186
+			),
2187
+			__FILE__,
2188
+			__FUNCTION__,
2189
+			__LINE__
2190
+		);
2191
+		return false;
2192
+	}
2193
+
2194
+
2195
+	/**
2196
+	 * retrieves a valid payment method
2197
+	 *
2198
+	 * @return EE_Payment_Method
2199
+	 * @throws EE_Error
2200
+	 * @throws InvalidArgumentException
2201
+	 * @throws ReflectionException
2202
+	 * @throws InvalidDataTypeException
2203
+	 * @throws InvalidInterfaceException
2204
+	 */
2205
+	private function _get_payment_method_for_selected_method_of_payment(): ?EE_Payment_Method
2206
+	{
2207
+		if ($this->checkout->selected_method_of_payment === 'events_sold_out') {
2208
+			$this->_redirect_because_event_sold_out();
2209
+			return null;
2210
+		}
2211
+		// get EE_Payment_Method object
2212
+		$payment_method = $this->checkout->available_payment_methods[ $this->checkout->selected_method_of_payment ]
2213
+			?? $this->payment_method_model->get_one_by_slug($this->checkout->selected_method_of_payment);
2214
+		// verify $payment_method
2215
+		if (! $payment_method instanceof EE_Payment_Method) {
2216
+			// not a payment
2217
+			EE_Error::add_error(
2218
+				sprintf(
2219
+					esc_html__(
2220
+						'The selected method of payment could not be determined due to a technical issue.%sPlease try again or contact %s for assistance.',
2221
+						'event_espresso'
2222
+					),
2223
+					'<br/>',
2224
+					EE_Registry::instance()->CFG->organization->get_pretty('email')
2225
+				),
2226
+				__FILE__,
2227
+				__FUNCTION__,
2228
+				__LINE__
2229
+			);
2230
+			return null;
2231
+		}
2232
+		// and verify it has a valid Payment_Method Type object
2233
+		if (! $payment_method->type_obj() instanceof EE_PMT_Base) {
2234
+			// not a payment
2235
+			EE_Error::add_error(
2236
+				sprintf(
2237
+					esc_html__(
2238
+						'A valid payment method could not be determined due to a technical issue.%sPlease try again or contact %s for assistance.',
2239
+						'event_espresso'
2240
+					),
2241
+					'<br/>',
2242
+					EE_Registry::instance()->CFG->organization->get_pretty('email')
2243
+				),
2244
+				__FILE__,
2245
+				__FUNCTION__,
2246
+				__LINE__
2247
+			);
2248
+			return null;
2249
+		}
2250
+		return $payment_method;
2251
+	}
2252
+
2253
+
2254
+	/**
2255
+	 * @param EE_Payment_Method $payment_method
2256
+	 * @return EE_Payment|null
2257
+	 * @throws EE_Error
2258
+	 * @throws ReflectionException
2259
+	 */
2260
+	private function _attempt_payment(EE_Payment_Method $payment_method): ?EE_Payment
2261
+	{
2262
+		$this->checkout->transaction->save();
2263
+		/** @var PaymentProcessor $payment_processor */
2264
+		$payment_processor = LoaderFactory::getShared(PaymentProcessor::class);
2265
+		if (! $payment_processor instanceof PaymentProcessor) {
2266
+			return null;
2267
+		}
2268
+		/** @var EE_Transaction_Processor $transaction_processor */
2269
+		$transaction_processor = LoaderFactory::getShared(EE_Transaction_Processor::class);
2270
+		if ($transaction_processor instanceof EE_Transaction_Processor) {
2271
+			$transaction_processor->set_revisit($this->checkout->revisit);
2272
+		}
2273
+		try {
2274
+			// generate payment object
2275
+			return $payment_processor->processPayment(
2276
+				$payment_method,
2277
+				$this->checkout->transaction,
2278
+				$this->checkout->billing_form instanceof EE_Billing_Info_Form
2279
+					? $this->checkout->billing_form
2280
+					: null,
2281
+				$this->checkout->amount_owing,
2282
+				$this->checkout->admin_request,
2283
+				true,
2284
+				$this->_get_return_url($payment_method),
2285
+				$this->reg_step_url()
2286
+			);
2287
+		} catch (Exception $e) {
2288
+			$this->_handle_payment_processor_exception($e);
2289
+		}
2290
+		return null;
2291
+	}
2292
+
2293
+
2294
+	/**
2295
+	 * @param Exception $e
2296
+	 * @return void
2297
+	 * @throws EE_Error
2298
+	 */
2299
+	protected function _handle_payment_processor_exception(Exception $e)
2300
+	{
2301
+		EE_Error::add_error(
2302
+			sprintf(
2303
+				esc_html__(
2304
+					'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',
2305
+					'event_espresso'
2306
+				),
2307
+				'<br/>',
2308
+				EE_Registry::instance()->CFG->organization->get_pretty('email'),
2309
+				$e->getMessage(),
2310
+				$e->getFile(),
2311
+				$e->getLine()
2312
+			),
2313
+			__FILE__,
2314
+			__FUNCTION__,
2315
+			__LINE__
2316
+		);
2317
+	}
2318
+
2319
+
2320
+	/**
2321
+	 * @param EE_Payment_Method $payment_method
2322
+	 * @return string
2323
+	 * @throws EE_Error
2324
+	 * @throws ReflectionException
2325
+	 */
2326
+	protected function _get_return_url(EE_Payment_Method $payment_method): string
2327
+	{
2328
+		switch ($payment_method->type_obj()->payment_occurs()) {
2329
+			case EE_PMT_Base::offsite:
2330
+				return add_query_arg(
2331
+					[
2332
+						'action'                     => 'process_gateway_response',
2333
+						'selected_method_of_payment' => $this->checkout->selected_method_of_payment,
2334
+						'spco_txn'                   => $this->checkout->transaction->ID(),
2335
+					],
2336
+					$this->reg_step_url()
2337
+				);
2338
+
2339
+			case EE_PMT_Base::onsite:
2340
+			case EE_PMT_Base::offline:
2341
+				return $this->checkout->next_step->reg_step_url();
2342
+		}
2343
+		return '';
2344
+	}
2345
+
2346
+
2347
+	/**
2348
+	 * @param EE_Payment|null $payment
2349
+	 * @return EE_Payment|bool
2350
+	 * @throws EE_Error
2351
+	 * @throws ReflectionException
2352
+	 */
2353
+	private function _validate_payment(?EE_Payment $payment = null)
2354
+	{
2355
+		if ($this->checkout->payment_method->is_off_line()) {
2356
+			return true;
2357
+		}
2358
+		// verify payment object
2359
+		if (! $payment instanceof EE_Payment) {
2360
+			// not a payment
2361
+			EE_Error::add_error(
2362
+				sprintf(
2363
+					esc_html__(
2364
+						'A valid payment was not generated due to a technical issue.%1$sPlease try again or contact %2$s for assistance.',
2365
+						'event_espresso'
2366
+					),
2367
+					'<br/>',
2368
+					EE_Registry::instance()->CFG->organization->get_pretty('email')
2369
+				),
2370
+				__FILE__,
2371
+				__FUNCTION__,
2372
+				__LINE__
2373
+			);
2374
+			return false;
2375
+		}
2376
+		return $payment;
2377
+	}
2378
+
2379
+
2380
+	/**
2381
+	 * @param EE_Payment|bool $payment
2382
+	 * @return bool|EE_Payment
2383
+	 * @throws EE_Error
2384
+	 * @throws ReflectionException
2385
+	 */
2386
+	private function _post_payment_processing($payment = null)
2387
+	{
2388
+		// Off-Line payment?
2389
+		if ($payment === true) {
2390
+			return true;
2391
+		}
2392
+		if ($payment instanceof EE_Payment) {
2393
+			// Should the user be redirected?
2394
+			if ($payment->redirect_url()) {
2395
+				do_action('AHEE_log', __CLASS__, __FUNCTION__, $payment->redirect_url(), '$payment->redirect_url()');
2396
+				$this->checkout->redirect      = true;
2397
+				$this->checkout->redirect_form = $payment->redirect_form();
2398
+				$this->checkout->redirect_url  = $this->reg_step_url('redirect_form');
2399
+				// set JSON response
2400
+				$this->checkout->json_response->set_redirect_form($this->checkout->redirect_form);
2401
+				// and lastly, let's bump the payment status to pending
2402
+				$payment->set_status(EEM_Payment::status_id_pending);
2403
+				$payment->save();
2404
+			} elseif (! $this->_process_payment_status($payment, EE_PMT_Base::onsite)) {
2405
+				// User shouldn't be redirected. So let's process it here.
2406
+				// $this->_setup_redirect_for_next_step();
2407
+				$this->checkout->continue_reg = false;
2408
+			}
2409
+			return $payment;
2410
+		}
2411
+		// ummm ya... not Off-Line, not On-Site, not off-Site ????
2412
+		$this->checkout->continue_reg = false;
2413
+		return false;
2414
+	}
2415
+
2416
+
2417
+	/**
2418
+	 * @param EE_Payment|null $payment
2419
+	 * @param string          $payment_occurs
2420
+	 * @return bool
2421
+	 * @throws EE_Error
2422
+	 */
2423
+	private function _process_payment_status(?EE_Payment $payment, string $payment_occurs = EE_PMT_Base::offline): bool
2424
+	{
2425
+		// off-line payment? carry on
2426
+		if ($payment_occurs === EE_PMT_Base::offline) {
2427
+			return true;
2428
+		}
2429
+		// verify payment validity
2430
+		if ($payment instanceof EE_Payment) {
2431
+			do_action('AHEE_log', __CLASS__, __FUNCTION__, $payment->status(), '$payment->status()');
2432
+			$msg = $payment->gateway_response();
2433
+			// check results
2434
+			switch ($payment->status()) {
2435
+				// good payment
2436
+				case EEM_Payment::status_id_approved:
2437
+					EE_Error::add_success(
2438
+						esc_html__('Your payment was processed successfully.', 'event_espresso'),
2439
+						__FILE__,
2440
+						__FUNCTION__,
2441
+						__LINE__
2442
+					);
2443
+					return true;
2444
+				// slow payment
2445
+				case EEM_Payment::status_id_pending:
2446
+					if (empty($msg)) {
2447
+						$msg = esc_html__(
2448
+							'Your payment appears to have been processed successfully, but the Instant Payment Notification has not yet been received. It should arrive shortly.',
2449
+							'event_espresso'
2450
+						);
2451
+					}
2452
+					EE_Error::add_success($msg, __FILE__, __FUNCTION__, __LINE__);
2453
+					return true;
2454
+				// don't wanna payment
2455
+				case EEM_Payment::status_id_cancelled:
2456
+					if (empty($msg)) {
2457
+						$msg = _n(
2458
+							'Payment cancelled. Please try again.',
2459
+							'Payment cancelled. Please try again or select another method of payment.',
2460
+							count($this->checkout->available_payment_methods),
2461
+							'event_espresso'
2462
+						);
2463
+					}
2464
+					EE_Error::add_attention($msg, __FILE__, __FUNCTION__, __LINE__);
2465
+					return false;
2466
+				// not enough payment
2467
+				case EEM_Payment::status_id_declined:
2468
+					if (empty($msg)) {
2469
+						$msg = _n(
2470
+							'We\'re sorry but your payment was declined. Please try again.',
2471
+							'We\'re sorry but your payment was declined. Please try again or select another method of payment.',
2472
+							count($this->checkout->available_payment_methods),
2473
+							'event_espresso'
2474
+						);
2475
+					}
2476
+					EE_Error::add_attention($msg, __FILE__, __FUNCTION__, __LINE__);
2477
+					return false;
2478
+				// bad payment
2479
+				case EEM_Payment::status_id_failed:
2480
+					if (! empty($msg)) {
2481
+						EE_Error::add_error($msg, __FILE__, __FUNCTION__, __LINE__);
2482
+						return false;
2483
+					}
2484
+					// default to error below
2485
+					break;
2486
+			}
2487
+		}
2488
+		// off-site payment gateway responses are too unreliable, so let's just assume that
2489
+		// the payment processing is just running slower than the registrant's request
2490
+		if ($payment_occurs === EE_PMT_Base::offsite) {
2491
+			return true;
2492
+		}
2493
+		EE_Error::add_error(
2494
+			sprintf(
2495
+				esc_html__(
2496
+					'Your payment could not be processed successfully due to a technical issue.%sPlease try again or contact %s for assistance.',
2497
+					'event_espresso'
2498
+				),
2499
+				'<br/>',
2500
+				EE_Registry::instance()->CFG->organization->get_pretty('email')
2501
+			),
2502
+			__FILE__,
2503
+			__FUNCTION__,
2504
+			__LINE__
2505
+		);
2506
+		return false;
2507
+	}
2508
+
2509
+
2510
+
2511
+
2512
+
2513
+
2514
+	/********************************************************************************************************/
2515
+	/**********************************  PROCESS GATEWAY RESPONSE  **********************************/
2516
+	/********************************************************************************************************/
2517
+
2518
+
2519
+	/**
2520
+	 * This is the return point for Off-Site Payment Methods
2521
+	 * It will attempt to "handle the IPN" if it appears that this has not already occurred,
2522
+	 * otherwise, it will load up the last payment made for the TXN.
2523
+	 * If the payment retrieved looks good, it will then either:
2524
+	 *    complete the current step and allow advancement to the next reg step
2525
+	 *        or present the payment options again
2526
+	 *
2527
+	 * @return bool
2528
+	 * @throws EE_Error
2529
+	 * @throws InvalidArgumentException
2530
+	 * @throws ReflectionException
2531
+	 * @throws InvalidDataTypeException
2532
+	 * @throws InvalidInterfaceException
2533
+	 */
2534
+	public function process_gateway_response()
2535
+	{
2536
+		// how have they chosen to pay?
2537
+		$this->checkout->selected_method_of_payment = $this->_get_selected_method_of_payment(true);
2538
+		// get EE_Payment_Method object
2539
+		if (! $this->checkout->payment_method = $this->_get_payment_method_for_selected_method_of_payment()) {
2540
+			$this->checkout->continue_reg = false;
2541
+			return false;
2542
+		}
2543
+		if (! $this->checkout->payment_method->is_off_site()) {
2544
+			return false;
2545
+		}
2546
+		$this->_validate_offsite_return();
2547
+		// verify TXN
2548
+		if ($this->checkout->transaction instanceof EE_Transaction) {
2549
+			$gateway = $this->checkout->payment_method->type_obj()->get_gateway();
2550
+			if (! $gateway instanceof EE_Offsite_Gateway) {
2551
+				$this->checkout->continue_reg = false;
2552
+				return false;
2553
+			}
2554
+			$payment = $this->_process_off_site_payment($gateway);
2555
+			$payment = $this->_process_cancelled_payments($payment);
2556
+			$payment = $this->_validate_payment($payment);
2557
+			// if payment was not declined by the payment gateway or cancelled by the registrant
2558
+			if ($this->_process_payment_status($payment, EE_PMT_Base::offsite)) {
2559
+				// $this->_setup_redirect_for_next_step();
2560
+				// store that for later
2561
+				$this->checkout->payment = $payment;
2562
+				// mark this reg step as completed, as long as gateway doesn't use a separate IPN request,
2563
+				// because we will complete this step during the IPN processing then
2564
+				if (! $this->handle_IPN_in_this_request()) {
2565
+					$this->set_completed();
2566
+				}
2567
+				return true;
2568
+			}
2569
+		}
2570
+		// DEBUG LOG
2571
+		// $this->checkout->log(
2572
+		//     __CLASS__,
2573
+		//     __FUNCTION__,
2574
+		//     __LINE__,
2575
+		//     array('payment' => $payment)
2576
+		// );
2577
+		$this->checkout->continue_reg = false;
2578
+		return false;
2579
+	}
2580
+
2581
+
2582
+	/**
2583
+	 * @return void
2584
+	 * @throws EE_Error
2585
+	 * @throws ReflectionException
2586
+	 */
2587
+	private function _validate_offsite_return()
2588
+	{
2589
+		$TXN_ID = $this->request->getRequestParam('spco_txn', 0, 'int');
2590
+		if ($TXN_ID !== $this->checkout->transaction->ID()) {
2591
+			// Houston... we might have a problem
2592
+			$invalid_TXN = false;
2593
+			// first gather some info
2594
+			$valid_TXN          = EEM_Transaction::instance()->get_one_by_ID($TXN_ID);
2595
+			$primary_registrant = $valid_TXN instanceof EE_Transaction
2596
+				? $valid_TXN->primary_registration()
2597
+				: null;
2598
+			// let's start by retrieving the cart for this TXN
2599
+			$cart = $this->checkout->get_cart_for_transaction($this->checkout->transaction);
2600
+			if ($cart instanceof EE_Cart) {
2601
+				// verify that the current cart has tickets
2602
+				$tickets = $cart->get_tickets();
2603
+				if (empty($tickets)) {
2604
+					$invalid_TXN = true;
2605
+				}
2606
+			} else {
2607
+				$invalid_TXN = true;
2608
+			}
2609
+			$valid_TXN_SID = $primary_registrant instanceof EE_Registration
2610
+				? $primary_registrant->session_ID()
2611
+				: null;
2612
+			// validate current Session ID and compare against valid TXN session ID
2613
+			if (
2614
+				$invalid_TXN // if this is already true, then skip other checks
2615
+				|| EE_Session::instance()->id() === null
2616
+				|| (
2617
+					// WARNING !!!
2618
+					// this could be PayPal sending back duplicate requests (ya they do that)
2619
+					// or it **could** mean someone is simply registering AGAIN after having just done so,
2620
+					// so now we need to determine if this current TXN looks valid or not
2621
+					// and whether this reg step has even been started ?
2622
+					EE_Session::instance()->id() === $valid_TXN_SID
2623
+					// really? you're halfway through this reg step, but you never started it ?
2624
+					&& $this->checkout->transaction->reg_step_completed($this->slug()) === false
2625
+				)
2626
+			) {
2627
+				$invalid_TXN = true;
2628
+			}
2629
+			if ($invalid_TXN) {
2630
+				// is the valid TXN completed ?
2631
+				if ($valid_TXN instanceof EE_Transaction) {
2632
+					// has this step even been started ?
2633
+					$reg_step_completed = $valid_TXN->reg_step_completed($this->slug());
2634
+					if ($reg_step_completed !== false && $reg_step_completed !== true) {
2635
+						// so it **looks** like this is a double request from PayPal
2636
+						// so let's try to pick up where we left off
2637
+						$this->checkout->transaction = $valid_TXN;
2638
+						$this->checkout->refresh_all_entities(true);
2639
+						return;
2640
+					}
2641
+				}
2642
+				// you appear to be lost?
2643
+				$this->_redirect_wayward_request($primary_registrant);
2644
+			}
2645
+		}
2646
+	}
2647
+
2648
+
2649
+	/**
2650
+	 * @param EE_Registration|null $primary_registrant
2651
+	 * @return void
2652
+	 * @throws EE_Error
2653
+	 * @throws ReflectionException
2654
+	 */
2655
+	private function _redirect_wayward_request(?EE_Registration $primary_registrant)
2656
+	{
2657
+		if (! $primary_registrant instanceof EE_Registration) {
2658
+			// try redirecting based on the current TXN
2659
+			$primary_registrant = $this->checkout->transaction instanceof EE_Transaction
2660
+				? $this->checkout->transaction->primary_registration()
2661
+				: null;
2662
+		}
2663
+		if (! $primary_registrant instanceof EE_Registration) {
2664
+			EE_Error::add_error(
2665
+				sprintf(
2666
+					esc_html__(
2667
+						'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.',
2668
+						'event_espresso'
2669
+					),
2670
+					'<br/>',
2671
+					EE_Registry::instance()->CFG->organization->get_pretty('email')
2672
+				),
2673
+				__FILE__,
2674
+				__FUNCTION__,
2675
+				__LINE__
2676
+			);
2677
+			return;
2678
+		}
2679
+		// make sure transaction is not locked
2680
+		$this->checkout->transaction->unlock();
2681
+		wp_safe_redirect(
2682
+			add_query_arg(
2683
+				[
2684
+					'e_reg_url_link' => $primary_registrant->reg_url_link(),
2685
+				],
2686
+				$this->checkout->thank_you_page_url
2687
+			)
2688
+		);
2689
+		exit();
2690
+	}
2691
+
2692
+
2693
+	/**
2694
+	 * @param EE_Offsite_Gateway $gateway
2695
+	 * @return EE_Payment|null
2696
+	 * @throws EE_Error
2697
+	 * @throws ReflectionException
2698
+	 */
2699
+	private function _process_off_site_payment(EE_Offsite_Gateway $gateway): ?EE_Payment
2700
+	{
2701
+		try {
2702
+			$request      = LoaderFactory::getLoader()->getShared(RequestInterface::class);
2703
+			$request_data = $request->requestParams();
2704
+			// if gateway uses_separate_IPN_request, then we don't have to process the IPN manually
2705
+			$this->set_handle_IPN_in_this_request(
2706
+				$gateway->handle_IPN_in_this_request($request_data, false)
2707
+			);
2708
+			if ($this->handle_IPN_in_this_request()) {
2709
+				// get payment details and process results
2710
+				/** @var IpnHandler $payment_processor */
2711
+				$payment_processor = LoaderFactory::getShared(IpnHandler::class);
2712
+				$payment           = $payment_processor->processIPN(
2713
+					$request_data,
2714
+					$this->checkout->transaction,
2715
+					$this->checkout->payment_method,
2716
+					true,
2717
+					false
2718
+				);
2719
+				// $payment_source = 'process_ipn';
2720
+			} else {
2721
+				$payment = $this->checkout->transaction->last_payment();
2722
+				// $payment_source = 'last_payment';
2723
+			}
2724
+		} catch (Exception $e) {
2725
+			// let's just eat the exception and try to move on using any previously set payment info
2726
+			$payment = $this->checkout->transaction->last_payment();
2727
+			// $payment_source = 'last_payment after Exception';
2728
+			// but if we STILL don't have a payment object
2729
+			if (! $payment instanceof EE_Payment) {
2730
+				// then we'll object ! ( not object like a thing... but object like what a lawyer says ! )
2731
+				$this->_handle_payment_processor_exception($e);
2732
+			}
2733
+		}
2734
+		return $payment;
2735
+	}
2736
+
2737
+
2738
+	/**
2739
+	 * just makes sure that the payment status gets updated correctly
2740
+	 * so that an error isn't generated during payment validation
2741
+	 *
2742
+	 * @param EE_Payment|null $payment
2743
+	 * @return EE_Payment|null
2744
+	 * @throws EE_Error
2745
+	 */
2746
+	private function _process_cancelled_payments(?EE_Payment $payment = null): ?EE_Payment
2747
+	{
2748
+		if (
2749
+			$payment instanceof EE_Payment
2750
+			&& $this->request->requestParamIsSet('ee_cancel_payment')
2751
+			&& $payment->status() === EEM_Payment::status_id_failed
2752
+		) {
2753
+			$payment->set_status(EEM_Payment::status_id_cancelled);
2754
+		}
2755
+		return $payment;
2756
+	}
2757
+
2758
+
2759
+	/**
2760
+	 * @return void
2761
+	 * @throws EE_Error
2762
+	 * @throws InvalidArgumentException
2763
+	 * @throws ReflectionException
2764
+	 * @throws InvalidDataTypeException
2765
+	 * @throws InvalidInterfaceException
2766
+	 */
2767
+	public function get_transaction_details_for_gateways()
2768
+	{
2769
+		$txn_details = [];
2770
+		// ya gotta make a choice man
2771
+		if (empty($this->checkout->selected_method_of_payment)) {
2772
+			$txn_details = [
2773
+				'error' => esc_html__('Please select a method of payment before proceeding.', 'event_espresso'),
2774
+			];
2775
+		}
2776
+		// get EE_Payment_Method object
2777
+		if (
2778
+			empty($txn_details)
2779
+			&& ! $this->checkout->payment_method = $this->_get_payment_method_for_selected_method_of_payment()
2780
+		) {
2781
+			$txn_details = [
2782
+				'selected_method_of_payment' => $this->checkout->selected_method_of_payment,
2783
+				'error'                      => esc_html__(
2784
+					'A valid Payment Method could not be determined.',
2785
+					'event_espresso'
2786
+				),
2787
+			];
2788
+		}
2789
+		if (empty($txn_details) && $this->checkout->transaction instanceof EE_Transaction) {
2790
+			$return_url  = $this->_get_return_url($this->checkout->payment_method);
2791
+			$txn_details = [
2792
+				'TXN_ID'         => $this->checkout->transaction->ID(),
2793
+				'TXN_timestamp'  => $this->checkout->transaction->datetime(),
2794
+				'TXN_total'      => $this->checkout->transaction->total(),
2795
+				'TXN_paid'       => $this->checkout->transaction->paid(),
2796
+				'TXN_reg_steps'  => $this->checkout->transaction->reg_steps(),
2797
+				'STS_ID'         => $this->checkout->transaction->status_ID(),
2798
+				'PMD_ID'         => $this->checkout->transaction->payment_method_ID(),
2799
+				'payment_amount' => $this->checkout->amount_owing,
2800
+				'return_url'     => $return_url,
2801
+				'cancel_url'     => add_query_arg(['ee_cancel_payment' => true], $return_url),
2802
+				'notify_url'     => EE_Config::instance()->core->txn_page_url(
2803
+					[
2804
+						'e_reg_url_link'    => $this->checkout->transaction->primary_registration()->reg_url_link(),
2805
+						'ee_payment_method' => $this->checkout->payment_method->slug(),
2806
+					]
2807
+				),
2808
+			];
2809
+		}
2810
+		echo wp_json_encode($txn_details);
2811
+		exit();
2812
+	}
2813
+
2814
+
2815
+	/**
2816
+	 * to conserve db space, let's remove the reg_form and the EE_Checkout object from EE_SPCO_Reg_Step objects upon
2817
+	 * serialization EE_Checkout will handle the reimplementation of itself upon waking, but we won't bother with the
2818
+	 * reg form, because if needed, it will be regenerated anyways
2819
+	 *
2820
+	 * @return array
2821
+	 */
2822
+	public function __sleep()
2823
+	{
2824
+		// remove the reg form and the checkout
2825
+		return array_diff(array_keys(get_object_vars($this)), ['reg_form', 'checkout', 'line_item_display']);
2826
+	}
2827 2827
 }
Please login to merge, or discard this patch.
Spacing   +80 added lines, -80 removed lines patch added patch discarded remove patch
@@ -126,7 +126,7 @@  discard block
 block discarded – undo
126 126
         $this->request              = EED_Single_Page_Checkout::getRequest();
127 127
         $this->_slug                = 'payment_options';
128 128
         $this->_name                = esc_html__('Payment Options', 'event_espresso');
129
-        $this->_template            = SPCO_REG_STEPS_PATH . $this->_slug . '/payment_options_main.template.php';
129
+        $this->_template            = SPCO_REG_STEPS_PATH.$this->_slug.'/payment_options_main.template.php';
130 130
         $this->checkout             = $checkout;
131 131
         $this->payment_method_model = EEM_Payment_Method::instance();
132 132
         $this->_reset_success_message();
@@ -168,7 +168,7 @@  discard block
 block discarded – undo
168 168
      */
169 169
     public function translate_js_strings()
170 170
     {
171
-        EE_Registry::$i18n_js_strings['no_payment_method']      = esc_html__(
171
+        EE_Registry::$i18n_js_strings['no_payment_method'] = esc_html__(
172 172
             'Please select a method of payment in order to continue.',
173 173
             'event_espresso'
174 174
         );
@@ -176,7 +176,7 @@  discard block
 block discarded – undo
176 176
             'A valid method of payment could not be determined. Please refresh the page and try again.',
177 177
             'event_espresso'
178 178
         );
179
-        EE_Registry::$i18n_js_strings['forwarding_to_offsite']  = esc_html__(
179
+        EE_Registry::$i18n_js_strings['forwarding_to_offsite'] = esc_html__(
180 180
             'Forwarding to Secure Payment Provider.',
181 181
             'event_espresso'
182 182
         );
@@ -192,7 +192,7 @@  discard block
 block discarded – undo
192 192
     {
193 193
         $transaction = $this->checkout->transaction;
194 194
         // if the transaction isn't set or nothing is owed on it, don't enqueue any JS
195
-        if (! $transaction instanceof EE_Transaction || EEH_Money::compare_floats($transaction->remaining(), 0)) {
195
+        if ( ! $transaction instanceof EE_Transaction || EEH_Money::compare_floats($transaction->remaining(), 0)) {
196 196
             return;
197 197
         }
198 198
         foreach ($this->checkout->available_payment_methods as $payment_method) {
@@ -297,18 +297,18 @@  discard block
 block discarded – undo
297 297
                 continue;
298 298
             }
299 299
             // has this registration lost it's space ?
300
-            if (isset($ejected_registrations[ $REG_ID ])) {
300
+            if (isset($ejected_registrations[$REG_ID])) {
301 301
                 if ($registration->event()->is_sold_out() || $registration->event()->is_sold_out(true)) {
302
-                    $sold_out_events[ $registration->event()->ID() ] = $registration->event();
302
+                    $sold_out_events[$registration->event()->ID()] = $registration->event();
303 303
                 } else {
304
-                    $insufficient_spaces_available[ $registration->event()->ID() ] = $registration->event();
304
+                    $insufficient_spaces_available[$registration->event()->ID()] = $registration->event();
305 305
                 }
306 306
                 continue;
307 307
             }
308 308
             // event requires admin approval
309 309
             if ($registration->status_ID() === RegStatus::AWAITING_REVIEW) {
310 310
                 // add event to list of events with pre-approval reg status
311
-                $registrations_requiring_pre_approval[ $REG_ID ] = $registration;
311
+                $registrations_requiring_pre_approval[$REG_ID] = $registration;
312 312
                 do_action(
313 313
                     'AHEE__EE_SPCO_Reg_Step_Payment_Options__generate_reg_form__event_requires_pre_approval',
314 314
                     $registration->event(),
@@ -325,7 +325,7 @@  discard block
 block discarded – undo
325 325
                 )
326 326
             ) {
327 327
                 // add event to list of events that are sold out
328
-                $sold_out_events[ $registration->event()->ID() ] = $registration->event();
328
+                $sold_out_events[$registration->event()->ID()] = $registration->event();
329 329
                 do_action(
330 330
                     'AHEE__EE_SPCO_Reg_Step_Payment_Options__generate_reg_form__sold_out_event',
331 331
                     $registration->event(),
@@ -335,7 +335,7 @@  discard block
 block discarded – undo
335 335
             }
336 336
             // are they allowed to pay now and is there monies owing?
337 337
             if ($registration->owes_monies_and_can_pay()) {
338
-                $registrations_requiring_payment[ $REG_ID ] = $registration;
338
+                $registrations_requiring_payment[$REG_ID] = $registration;
339 339
                 do_action(
340 340
                     'AHEE__EE_SPCO_Reg_Step_Payment_Options__generate_reg_form__event_requires_payment',
341 341
                     $registration->event(),
@@ -346,28 +346,28 @@  discard block
 block discarded – undo
346 346
                 && $registration->status_ID() !== RegStatus::AWAITING_REVIEW
347 347
                 && $registration->ticket()->is_free()
348 348
             ) {
349
-                $registrations_for_free_events[ $registration->ticket()->ID() ] = $registration;
349
+                $registrations_for_free_events[$registration->ticket()->ID()] = $registration;
350 350
             }
351 351
         }
352 352
         $subsections = [];
353 353
         // now decide which template to load
354
-        if (! empty($sold_out_events)) {
354
+        if ( ! empty($sold_out_events)) {
355 355
             $subsections['sold_out_events'] = $this->_sold_out_events($sold_out_events);
356 356
         }
357
-        if (! empty($insufficient_spaces_available)) {
357
+        if ( ! empty($insufficient_spaces_available)) {
358 358
             $subsections['insufficient_space'] = $this->_insufficient_spaces_available(
359 359
                 $insufficient_spaces_available
360 360
             );
361 361
         }
362
-        if (! empty($registrations_requiring_pre_approval)) {
362
+        if ( ! empty($registrations_requiring_pre_approval)) {
363 363
             $subsections['registrations_requiring_pre_approval'] = $this->_registrations_requiring_pre_approval(
364 364
                 $registrations_requiring_pre_approval
365 365
             );
366 366
         }
367
-        if (! empty($registrations_for_free_events)) {
367
+        if ( ! empty($registrations_for_free_events)) {
368 368
             $subsections['no_payment_required'] = $this->_no_payment_required($registrations_for_free_events);
369 369
         }
370
-        if (! empty($registrations_requiring_payment)) {
370
+        if ( ! empty($registrations_requiring_payment)) {
371 371
             if ($this->checkout->amount_owing > 0) {
372 372
                 // check for method_of_payment before setting up line items
373 373
                 // so that surcharges can be applied to the line items based on the selected method of payment
@@ -396,7 +396,7 @@  discard block
 block discarded – undo
396 396
                         ['registrations' => $registrations]
397 397
                     )
398 398
                 );
399
-                $this->checkout->amount_owing   = $filtered_line_item_tree->total();
399
+                $this->checkout->amount_owing = $filtered_line_item_tree->total();
400 400
                 $this->_apply_registration_payments_to_amount_owing($registrations);
401 401
             }
402 402
             $no_payment_required = false;
@@ -441,13 +441,13 @@  discard block
 block discarded – undo
441 441
     public static function add_spco_line_item_filters(
442 442
         EE_Line_Item_Filter_Collection $line_item_filter_collection
443 443
     ): EE_Line_Item_Filter_Collection {
444
-        if (! EE_Registry::instance()->SSN instanceof EE_Session) {
444
+        if ( ! EE_Registry::instance()->SSN instanceof EE_Session) {
445 445
             return $line_item_filter_collection;
446 446
         }
447
-        if (! EE_Registry::instance()->SSN->checkout() instanceof EE_Checkout) {
447
+        if ( ! EE_Registry::instance()->SSN->checkout() instanceof EE_Checkout) {
448 448
             return $line_item_filter_collection;
449 449
         }
450
-        if (! EE_Registry::instance()->SSN->checkout()->transaction instanceof EE_Transaction) {
450
+        if ( ! EE_Registry::instance()->SSN->checkout()->transaction instanceof EE_Transaction) {
451 451
             return $line_item_filter_collection;
452 452
         }
453 453
         $line_item_filter_collection->add(
@@ -486,8 +486,8 @@  discard block
 block discarded – undo
486 486
         );
487 487
         foreach ($registrations as $REG_ID => $registration) {
488 488
             // has this registration lost it's space ?
489
-            if (isset($ejected_registrations[ $REG_ID ])) {
490
-                unset($registrations[ $REG_ID ]);
489
+            if (isset($ejected_registrations[$REG_ID])) {
490
+                unset($registrations[$REG_ID]);
491 491
             }
492 492
         }
493 493
         return $registrations;
@@ -536,25 +536,25 @@  discard block
 block discarded – undo
536 536
             }
537 537
             $EVT_ID = $registration->event_ID();
538 538
             $ticket = $registration->ticket();
539
-            if (! isset($tickets_remaining[ $ticket->ID() ])) {
540
-                $tickets_remaining[ $ticket->ID() ] = $ticket->remaining();
539
+            if ( ! isset($tickets_remaining[$ticket->ID()])) {
540
+                $tickets_remaining[$ticket->ID()] = $ticket->remaining();
541 541
             }
542
-            if ($tickets_remaining[ $ticket->ID() ] > 0) {
543
-                if (! isset($event_reg_count[ $EVT_ID ])) {
544
-                    $event_reg_count[ $EVT_ID ] = 0;
542
+            if ($tickets_remaining[$ticket->ID()] > 0) {
543
+                if ( ! isset($event_reg_count[$EVT_ID])) {
544
+                    $event_reg_count[$EVT_ID] = 0;
545 545
                 }
546
-                $event_reg_count[ $EVT_ID ]++;
547
-                if (! isset($event_spaces_remaining[ $EVT_ID ])) {
548
-                    $event_spaces_remaining[ $EVT_ID ] = $registration->event()->spaces_remaining_for_sale();
546
+                $event_reg_count[$EVT_ID]++;
547
+                if ( ! isset($event_spaces_remaining[$EVT_ID])) {
548
+                    $event_spaces_remaining[$EVT_ID] = $registration->event()->spaces_remaining_for_sale();
549 549
                 }
550 550
             }
551 551
             if (
552 552
                 $revisit
553
-                && ($tickets_remaining[ $ticket->ID() ] === 0
554
-                    || $event_reg_count[ $EVT_ID ] > $event_spaces_remaining[ $EVT_ID ]
553
+                && ($tickets_remaining[$ticket->ID()] === 0
554
+                    || $event_reg_count[$EVT_ID] > $event_spaces_remaining[$EVT_ID]
555 555
                 )
556 556
             ) {
557
-                $ejected_registrations[ $REG_ID ] = $registration->event();
557
+                $ejected_registrations[$REG_ID] = $registration->event();
558 558
                 if ($registration->status_ID() !== RegStatus::WAIT_LIST) {
559 559
                     /** @type EE_Registration_Processor $registration_processor */
560 560
                     $registration_processor = EE_Registry::instance()->load_class('Registration_Processor');
@@ -601,7 +601,7 @@  discard block
 block discarded – undo
601 601
         foreach ($sold_out_events_array as $sold_out_event) {
602 602
             $sold_out_events .= EEH_HTML::li(
603 603
                 EEH_HTML::span(
604
-                    '  ' . $sold_out_event->name(),
604
+                    '  '.$sold_out_event->name(),
605 605
                     '',
606 606
                     'dashicons dashicons-marker ee-icon-size-16 pink-text'
607 607
                 )
@@ -657,7 +657,7 @@  discard block
 block discarded – undo
657 657
         foreach ($insufficient_spaces_events_array as $event) {
658 658
             if ($event instanceof EE_Event) {
659 659
                 $insufficient_space_events .= EEH_HTML::li(
660
-                    EEH_HTML::span(' ' . $event->name(), '', 'dashicons dashicons-marker ee-icon-size-16 pink-text')
660
+                    EEH_HTML::span(' '.$event->name(), '', 'dashicons dashicons-marker ee-icon-size-16 pink-text')
661 661
                 );
662 662
             }
663 663
         }
@@ -705,7 +705,7 @@  discard block
 block discarded – undo
705 705
         $events_requiring_pre_approval = [];
706 706
         foreach ($registrations_requiring_pre_approval as $registration) {
707 707
             if ($registration instanceof EE_Registration && $registration->event() instanceof EE_Event) {
708
-                $events_requiring_pre_approval[ $registration->event()->ID() ] = EEH_HTML::li(
708
+                $events_requiring_pre_approval[$registration->event()->ID()] = EEH_HTML::li(
709 709
                     EEH_HTML::span(
710 710
                         '',
711 711
                         '',
@@ -835,7 +835,7 @@  discard block
 block discarded – undo
835 835
     {
836 836
         return new EE_Form_Section_Proper(
837 837
             [
838
-                'html_id'         => 'ee-' . $this->slug() . '-extra-hidden-inputs',
838
+                'html_id'         => 'ee-'.$this->slug().'-extra-hidden-inputs',
839 839
                 'layout_strategy' => new EE_Div_Per_Section_Layout(),
840 840
                 'subsections'     => [
841 841
                     'spco_no_payment_required' => new EE_Hidden_Input(
@@ -873,7 +873,7 @@  discard block
 block discarded – undo
873 873
                 $payments += $registration->registration_payments();
874 874
             }
875 875
         }
876
-        if (! empty($payments)) {
876
+        if ( ! empty($payments)) {
877 877
             foreach ($payments as $payment) {
878 878
                 if ($payment instanceof EE_Registration_Payment) {
879 879
                     $this->checkout->amount_owing -= $payment->amount();
@@ -946,7 +946,7 @@  discard block
 block discarded – undo
946 946
             );
947 947
         }
948 948
         // switch up header depending on number of available payment methods
949
-        $payment_method_header     = count($this->checkout->available_payment_methods) > 1
949
+        $payment_method_header = count($this->checkout->available_payment_methods) > 1
950 950
             ? apply_filters(
951 951
                 'FHEE__registration_page_payment_options__method_of_payment_hdr',
952 952
                 esc_html__('Please Select Your Method of Payment', 'event_espresso')
@@ -976,14 +976,14 @@  discard block
 block discarded – undo
976 976
         ];
977 977
         // loop through payment methods
978 978
         foreach ($this->checkout->available_payment_methods as $payment_method) {
979
-            if (! $payment_method instanceof EE_Payment_Method) {
979
+            if ( ! $payment_method instanceof EE_Payment_Method) {
980 980
                 continue;
981 981
             }
982 982
 
983 983
             $payment_method_button = EEH_HTML::img(
984 984
                 $payment_method->button_url(),
985 985
                 $payment_method->name(),
986
-                'spco-payment-method-' . $payment_method->slug() . '-btn-img',
986
+                'spco-payment-method-'.$payment_method->slug().'-btn-img',
987 987
                 'spco-payment-method-btn-img'
988 988
             );
989 989
             // check if any payment methods are set as default
@@ -998,11 +998,11 @@  discard block
 block discarded – undo
998 998
             ) {
999 999
                 $this->checkout->selected_method_of_payment = $payment_method->slug();
1000 1000
                 $this->_save_selected_method_of_payment();
1001
-                $default_payment_method_option[ $payment_method->slug() ] = $payment_method_button;
1001
+                $default_payment_method_option[$payment_method->slug()] = $payment_method_button;
1002 1002
             } else {
1003
-                $available_payment_method_options[ $payment_method->slug() ] = $payment_method_button;
1003
+                $available_payment_method_options[$payment_method->slug()] = $payment_method_button;
1004 1004
             }
1005
-            $payment_methods_billing_info[ $payment_method->slug() . '-info' ] =
1005
+            $payment_methods_billing_info[$payment_method->slug().'-info'] =
1006 1006
                 $this->_payment_method_billing_info(
1007 1007
                     $payment_method
1008 1008
                 );
@@ -1014,7 +1014,7 @@  discard block
 block discarded – undo
1014 1014
         $available_payment_methods['available_payment_methods'] = $this->_available_payment_method_inputs(
1015 1015
             $available_payment_method_options
1016 1016
         );
1017
-        $available_payment_methods                              += $payment_methods_billing_info;
1017
+        $available_payment_methods += $payment_methods_billing_info;
1018 1018
         // build the available payment methods form
1019 1019
         return new EE_Form_Section_Proper(
1020 1020
             [
@@ -1033,7 +1033,7 @@  discard block
 block discarded – undo
1033 1033
      */
1034 1034
     protected function _get_available_payment_methods(): array
1035 1035
     {
1036
-        if (! empty($this->checkout->available_payment_methods)) {
1036
+        if ( ! empty($this->checkout->available_payment_methods)) {
1037 1037
             return $this->checkout->available_payment_methods;
1038 1038
         }
1039 1039
         $available_payment_methods = [];
@@ -1044,7 +1044,7 @@  discard block
 block discarded – undo
1044 1044
         );
1045 1045
         foreach ($payment_methods as $payment_method) {
1046 1046
             if ($payment_method instanceof EE_Payment_Method) {
1047
-                $available_payment_methods[ $payment_method->slug() ] = $payment_method;
1047
+                $available_payment_methods[$payment_method->slug()] = $payment_method;
1048 1048
             }
1049 1049
         }
1050 1050
         return $available_payment_methods;
@@ -1132,7 +1132,7 @@  discard block
 block discarded – undo
1132 1132
         );
1133 1133
         return new EE_Form_Section_Proper(
1134 1134
             [
1135
-                'html_id'         => 'spco-payment-method-info-' . $payment_method->slug(),
1135
+                'html_id'         => 'spco-payment-method-info-'.$payment_method->slug(),
1136 1136
                 'html_class'      => 'spco-payment-method-info-dv',
1137 1137
                 // only display the selected or default PM
1138 1138
                 'html_style'      => $currently_selected ? '' : 'display:none;',
@@ -1159,7 +1159,7 @@  discard block
 block discarded – undo
1159 1159
         // how have they chosen to pay?
1160 1160
         $this->checkout->selected_method_of_payment = $this->_get_selected_method_of_payment(true);
1161 1161
         $this->checkout->payment_method             = $this->_get_payment_method_for_selected_method_of_payment();
1162
-        if (! $this->checkout->payment_method instanceof EE_Payment_Method) {
1162
+        if ( ! $this->checkout->payment_method instanceof EE_Payment_Method) {
1163 1163
             return false;
1164 1164
         }
1165 1165
         if (
@@ -1329,7 +1329,7 @@  discard block
 block discarded – undo
1329 1329
      */
1330 1330
     public function switch_payment_method()
1331 1331
     {
1332
-        if (! $this->_verify_payment_method_is_set()) {
1332
+        if ( ! $this->_verify_payment_method_is_set()) {
1333 1333
             return false;
1334 1334
         }
1335 1335
         if (
@@ -1463,7 +1463,7 @@  discard block
 block discarded – undo
1463 1463
             }
1464 1464
         }
1465 1465
         // verify payment method
1466
-        if (! $this->checkout->payment_method instanceof EE_Payment_Method) {
1466
+        if ( ! $this->checkout->payment_method instanceof EE_Payment_Method) {
1467 1467
             // get payment method for selected method of payment
1468 1468
             $this->checkout->payment_method = $this->_get_payment_method_for_selected_method_of_payment();
1469 1469
         }
@@ -1488,7 +1488,7 @@  discard block
 block discarded – undo
1488 1488
      */
1489 1489
     public function save_payer_details_via_ajax()
1490 1490
     {
1491
-        if (! $this->_verify_payment_method_is_set()) {
1491
+        if ( ! $this->_verify_payment_method_is_set()) {
1492 1492
             return;
1493 1493
         }
1494 1494
         // generate billing form for selected method of payment if it hasn't been done already
@@ -1498,7 +1498,7 @@  discard block
 block discarded – undo
1498 1498
             );
1499 1499
         }
1500 1500
         // generate primary attendee from payer info if applicable
1501
-        if (! $this->checkout->transaction_has_primary_registrant()) {
1501
+        if ( ! $this->checkout->transaction_has_primary_registrant()) {
1502 1502
             $attendee = $this->_create_attendee_from_request_data();
1503 1503
             if ($attendee instanceof EE_Attendee) {
1504 1504
                 foreach ($this->checkout->transaction->registrations() as $registration) {
@@ -1528,7 +1528,7 @@  discard block
 block discarded – undo
1528 1528
     {
1529 1529
         // get State ID
1530 1530
         $STA_ID = $this->request->getRequestParam('state');
1531
-        if (! empty($STA_ID)) {
1531
+        if ( ! empty($STA_ID)) {
1532 1532
             // can we get state object from name ?
1533 1533
             EE_Registry::instance()->load_model('State');
1534 1534
             $state  = EEM_State::instance()->get_col([['STA_name' => $STA_ID], 'limit' => 1], 'STA_ID');
@@ -1536,7 +1536,7 @@  discard block
 block discarded – undo
1536 1536
         }
1537 1537
         // get Country ISO
1538 1538
         $CNT_ISO = $this->request->getRequestParam('country');
1539
-        if (! empty($CNT_ISO)) {
1539
+        if ( ! empty($CNT_ISO)) {
1540 1540
             // can we get country object from name ?
1541 1541
             EE_Registry::instance()->load_model('Country');
1542 1542
             $country = EEM_Country::instance()->get_col(
@@ -1715,7 +1715,7 @@  discard block
 block discarded – undo
1715 1715
     protected function _maybe_set_completed(EE_Payment $payment)
1716 1716
     {
1717 1717
         // Do we need to redirect them? If so, there's more work to be done.
1718
-        if (! $payment->redirect_url()) {
1718
+        if ( ! $payment->redirect_url()) {
1719 1719
             $this->set_completed();
1720 1720
         }
1721 1721
     }
@@ -1771,7 +1771,7 @@  discard block
 block discarded – undo
1771 1771
     private function _process_payment()
1772 1772
     {
1773 1773
         // basically confirm that the event hasn't sold out since they hit the page
1774
-        if (! $this->_last_second_ticket_verifications()) {
1774
+        if ( ! $this->_last_second_ticket_verifications()) {
1775 1775
             return null;
1776 1776
         }
1777 1777
         // ya gotta make a choice man
@@ -1782,7 +1782,7 @@  discard block
 block discarded – undo
1782 1782
             return null;
1783 1783
         }
1784 1784
         // get EE_Payment_Method object
1785
-        if (! $this->checkout->payment_method = $this->_get_payment_method_for_selected_method_of_payment()) {
1785
+        if ( ! $this->checkout->payment_method = $this->_get_payment_method_for_selected_method_of_payment()) {
1786 1786
             return null;
1787 1787
         }
1788 1788
         // setup billing form
@@ -1791,12 +1791,12 @@  discard block
 block discarded – undo
1791 1791
                 $this->checkout->payment_method
1792 1792
             );
1793 1793
             // bad billing form ?
1794
-            if (! $this->_billing_form_is_valid()) {
1794
+            if ( ! $this->_billing_form_is_valid()) {
1795 1795
                 return null;
1796 1796
             }
1797 1797
         }
1798 1798
         // ensure primary registrant has been fully processed
1799
-        if (! $this->_setup_primary_registrant_prior_to_payment()) {
1799
+        if ( ! $this->_setup_primary_registrant_prior_to_payment()) {
1800 1800
             return null;
1801 1801
         }
1802 1802
         // if session is close to expiring (under 10 minutes by default)
@@ -1850,7 +1850,7 @@  discard block
 block discarded – undo
1850 1850
     protected function _last_second_ticket_verifications(): bool
1851 1851
     {
1852 1852
         // don't bother re-validating if not a return visit
1853
-        if (! $this->checkout->revisit) {
1853
+        if ( ! $this->checkout->revisit) {
1854 1854
             return true;
1855 1855
         }
1856 1856
         $registrations = $this->checkout->transaction->registrations();
@@ -1898,7 +1898,7 @@  discard block
 block discarded – undo
1898 1898
             $this->_get_payment_method_for_selected_method_of_payment()
1899 1899
         );
1900 1900
         $html                        = $payment_method_billing_info->get_html();
1901
-        $html                        .= $this->checkout->redirect_form;
1901
+        $html .= $this->checkout->redirect_form;
1902 1902
         /** @var ResponseInterface $response */
1903 1903
         $response = LoaderFactory::getLoader()->getShared(ResponseInterface::class);
1904 1904
         $response->addOutput($html);
@@ -1913,7 +1913,7 @@  discard block
 block discarded – undo
1913 1913
      */
1914 1914
     private function _billing_form_is_valid(): bool
1915 1915
     {
1916
-        if (! $this->checkout->payment_method->type_obj()->has_billing_form()) {
1916
+        if ( ! $this->checkout->payment_method->type_obj()->has_billing_form()) {
1917 1917
             return true;
1918 1918
         }
1919 1919
         if ($this->checkout->billing_form instanceof EE_Billing_Info_Form) {
@@ -2033,16 +2033,16 @@  discard block
 block discarded – undo
2033 2033
     private function _capture_primary_registration_data_from_billing_form(): bool
2034 2034
     {
2035 2035
         $primary_registration = $this->checkout->transaction->primary_registration();
2036
-        if (! $this->validatePrimaryRegistration($primary_registration)) {
2036
+        if ( ! $this->validatePrimaryRegistration($primary_registration)) {
2037 2037
             return false;
2038 2038
         }
2039 2039
 
2040 2040
         $primary_attendee = $this->getPrimaryAttendee($primary_registration);
2041
-        if (! $this->validatePrimaryAttendee($primary_attendee)) {
2041
+        if ( ! $this->validatePrimaryAttendee($primary_attendee)) {
2042 2042
             return false;
2043 2043
         }
2044 2044
 
2045
-        if (! $this->addAttendeeToPrimaryRegistration($primary_attendee, $primary_registration)) {
2045
+        if ( ! $this->addAttendeeToPrimaryRegistration($primary_attendee, $primary_registration)) {
2046 2046
             return false;
2047 2047
         }
2048 2048
         // both the primary registration and primary attendee objects should be valid entities at this point
@@ -2209,10 +2209,10 @@  discard block
 block discarded – undo
2209 2209
             return null;
2210 2210
         }
2211 2211
         // get EE_Payment_Method object
2212
-        $payment_method = $this->checkout->available_payment_methods[ $this->checkout->selected_method_of_payment ]
2212
+        $payment_method = $this->checkout->available_payment_methods[$this->checkout->selected_method_of_payment]
2213 2213
             ?? $this->payment_method_model->get_one_by_slug($this->checkout->selected_method_of_payment);
2214 2214
         // verify $payment_method
2215
-        if (! $payment_method instanceof EE_Payment_Method) {
2215
+        if ( ! $payment_method instanceof EE_Payment_Method) {
2216 2216
             // not a payment
2217 2217
             EE_Error::add_error(
2218 2218
                 sprintf(
@@ -2230,7 +2230,7 @@  discard block
 block discarded – undo
2230 2230
             return null;
2231 2231
         }
2232 2232
         // and verify it has a valid Payment_Method Type object
2233
-        if (! $payment_method->type_obj() instanceof EE_PMT_Base) {
2233
+        if ( ! $payment_method->type_obj() instanceof EE_PMT_Base) {
2234 2234
             // not a payment
2235 2235
             EE_Error::add_error(
2236 2236
                 sprintf(
@@ -2262,7 +2262,7 @@  discard block
 block discarded – undo
2262 2262
         $this->checkout->transaction->save();
2263 2263
         /** @var PaymentProcessor $payment_processor */
2264 2264
         $payment_processor = LoaderFactory::getShared(PaymentProcessor::class);
2265
-        if (! $payment_processor instanceof PaymentProcessor) {
2265
+        if ( ! $payment_processor instanceof PaymentProcessor) {
2266 2266
             return null;
2267 2267
         }
2268 2268
         /** @var EE_Transaction_Processor $transaction_processor */
@@ -2356,7 +2356,7 @@  discard block
 block discarded – undo
2356 2356
             return true;
2357 2357
         }
2358 2358
         // verify payment object
2359
-        if (! $payment instanceof EE_Payment) {
2359
+        if ( ! $payment instanceof EE_Payment) {
2360 2360
             // not a payment
2361 2361
             EE_Error::add_error(
2362 2362
                 sprintf(
@@ -2401,7 +2401,7 @@  discard block
 block discarded – undo
2401 2401
                 // and lastly, let's bump the payment status to pending
2402 2402
                 $payment->set_status(EEM_Payment::status_id_pending);
2403 2403
                 $payment->save();
2404
-            } elseif (! $this->_process_payment_status($payment, EE_PMT_Base::onsite)) {
2404
+            } elseif ( ! $this->_process_payment_status($payment, EE_PMT_Base::onsite)) {
2405 2405
                 // User shouldn't be redirected. So let's process it here.
2406 2406
                 // $this->_setup_redirect_for_next_step();
2407 2407
                 $this->checkout->continue_reg = false;
@@ -2477,7 +2477,7 @@  discard block
 block discarded – undo
2477 2477
                     return false;
2478 2478
                 // bad payment
2479 2479
                 case EEM_Payment::status_id_failed:
2480
-                    if (! empty($msg)) {
2480
+                    if ( ! empty($msg)) {
2481 2481
                         EE_Error::add_error($msg, __FILE__, __FUNCTION__, __LINE__);
2482 2482
                         return false;
2483 2483
                     }
@@ -2536,18 +2536,18 @@  discard block
 block discarded – undo
2536 2536
         // how have they chosen to pay?
2537 2537
         $this->checkout->selected_method_of_payment = $this->_get_selected_method_of_payment(true);
2538 2538
         // get EE_Payment_Method object
2539
-        if (! $this->checkout->payment_method = $this->_get_payment_method_for_selected_method_of_payment()) {
2539
+        if ( ! $this->checkout->payment_method = $this->_get_payment_method_for_selected_method_of_payment()) {
2540 2540
             $this->checkout->continue_reg = false;
2541 2541
             return false;
2542 2542
         }
2543
-        if (! $this->checkout->payment_method->is_off_site()) {
2543
+        if ( ! $this->checkout->payment_method->is_off_site()) {
2544 2544
             return false;
2545 2545
         }
2546 2546
         $this->_validate_offsite_return();
2547 2547
         // verify TXN
2548 2548
         if ($this->checkout->transaction instanceof EE_Transaction) {
2549 2549
             $gateway = $this->checkout->payment_method->type_obj()->get_gateway();
2550
-            if (! $gateway instanceof EE_Offsite_Gateway) {
2550
+            if ( ! $gateway instanceof EE_Offsite_Gateway) {
2551 2551
                 $this->checkout->continue_reg = false;
2552 2552
                 return false;
2553 2553
             }
@@ -2561,7 +2561,7 @@  discard block
 block discarded – undo
2561 2561
                 $this->checkout->payment = $payment;
2562 2562
                 // mark this reg step as completed, as long as gateway doesn't use a separate IPN request,
2563 2563
                 // because we will complete this step during the IPN processing then
2564
-                if (! $this->handle_IPN_in_this_request()) {
2564
+                if ( ! $this->handle_IPN_in_this_request()) {
2565 2565
                     $this->set_completed();
2566 2566
                 }
2567 2567
                 return true;
@@ -2654,13 +2654,13 @@  discard block
 block discarded – undo
2654 2654
      */
2655 2655
     private function _redirect_wayward_request(?EE_Registration $primary_registrant)
2656 2656
     {
2657
-        if (! $primary_registrant instanceof EE_Registration) {
2657
+        if ( ! $primary_registrant instanceof EE_Registration) {
2658 2658
             // try redirecting based on the current TXN
2659 2659
             $primary_registrant = $this->checkout->transaction instanceof EE_Transaction
2660 2660
                 ? $this->checkout->transaction->primary_registration()
2661 2661
                 : null;
2662 2662
         }
2663
-        if (! $primary_registrant instanceof EE_Registration) {
2663
+        if ( ! $primary_registrant instanceof EE_Registration) {
2664 2664
             EE_Error::add_error(
2665 2665
                 sprintf(
2666 2666
                     esc_html__(
@@ -2726,7 +2726,7 @@  discard block
 block discarded – undo
2726 2726
             $payment = $this->checkout->transaction->last_payment();
2727 2727
             // $payment_source = 'last_payment after Exception';
2728 2728
             // but if we STILL don't have a payment object
2729
-            if (! $payment instanceof EE_Payment) {
2729
+            if ( ! $payment instanceof EE_Payment) {
2730 2730
                 // then we'll object ! ( not object like a thing... but object like what a lawyer says ! )
2731 2731
                 $this->_handle_payment_processor_exception($e);
2732 2732
             }
Please login to merge, or discard this patch.
caffeinated/modules/recaptcha_invisible/InvisibleRecaptcha.php 2 patches
Indentation   +242 added lines, -242 removed lines patch added patch discarded remove patch
@@ -26,272 +26,272 @@
 block discarded – undo
26 26
  */
27 27
 class InvisibleRecaptcha
28 28
 {
29
-    const URL_GOOGLE_RECAPTCHA_API          = 'https://www.google.com/recaptcha/api/siteverify';
29
+	const URL_GOOGLE_RECAPTCHA_API          = 'https://www.google.com/recaptcha/api/siteverify';
30 30
 
31
-    const SESSION_DATA_KEY_RECAPTCHA_PASSED = 'recaptcha_passed';
31
+	const SESSION_DATA_KEY_RECAPTCHA_PASSED = 'recaptcha_passed';
32 32
 
33
-    /**
34
-     * @var EE_Registration_Config $config
35
-     */
36
-    private $config;
33
+	/**
34
+	 * @var EE_Registration_Config $config
35
+	 */
36
+	private $config;
37 37
 
38
-    /**
39
-     * @var EE_Session $session
40
-     */
41
-    private $session;
38
+	/**
39
+	 * @var EE_Session $session
40
+	 */
41
+	private $session;
42 42
 
43
-    /**
44
-     * @var boolean $recaptcha_passed
45
-     */
46
-    private $recaptcha_passed;
43
+	/**
44
+	 * @var boolean $recaptcha_passed
45
+	 */
46
+	private $recaptcha_passed;
47 47
 
48 48
 
49
-    /**
50
-     * InvisibleRecaptcha constructor.
51
-     *
52
-     * @param EE_Registration_Config $registration_config
53
-     * @param EE_Session             $session
54
-     */
55
-    public function __construct(EE_Registration_Config $registration_config, EE_Session $session = null)
56
-    {
57
-        $this->config = $registration_config;
58
-        $this->session = $session;
59
-    }
49
+	/**
50
+	 * InvisibleRecaptcha constructor.
51
+	 *
52
+	 * @param EE_Registration_Config $registration_config
53
+	 * @param EE_Session             $session
54
+	 */
55
+	public function __construct(EE_Registration_Config $registration_config, EE_Session $session = null)
56
+	{
57
+		$this->config = $registration_config;
58
+		$this->session = $session;
59
+	}
60 60
 
61 61
 
62
-    /**
63
-     * @return boolean
64
-     */
65
-    public function useInvisibleRecaptcha()
66
-    {
67
-        return $this->session instanceof EE_Session
68
-               && $this->config->use_captcha
69
-               && $this->config->recaptcha_theme === 'invisible';
70
-    }
62
+	/**
63
+	 * @return boolean
64
+	 */
65
+	public function useInvisibleRecaptcha()
66
+	{
67
+		return $this->session instanceof EE_Session
68
+			   && $this->config->use_captcha
69
+			   && $this->config->recaptcha_theme === 'invisible';
70
+	}
71 71
 
72 72
 
73
-    /**
74
-     * @param array $input_settings
75
-     * @return EE_Invisible_Recaptcha_Input
76
-     * @throws InvalidDataTypeException
77
-     * @throws InvalidInterfaceException
78
-     * @throws InvalidArgumentException
79
-     * @throws DomainException
80
-     */
81
-    public function getInput(array $input_settings = array())
82
-    {
83
-        return new EE_Invisible_Recaptcha_Input(
84
-            $input_settings,
85
-            $this->config
86
-        );
87
-    }
73
+	/**
74
+	 * @param array $input_settings
75
+	 * @return EE_Invisible_Recaptcha_Input
76
+	 * @throws InvalidDataTypeException
77
+	 * @throws InvalidInterfaceException
78
+	 * @throws InvalidArgumentException
79
+	 * @throws DomainException
80
+	 */
81
+	public function getInput(array $input_settings = array())
82
+	{
83
+		return new EE_Invisible_Recaptcha_Input(
84
+			$input_settings,
85
+			$this->config
86
+		);
87
+	}
88 88
 
89 89
 
90
-    /**
91
-     * @param array $input_settings
92
-     * @return string
93
-     * @throws EE_Error
94
-     * @throws InvalidDataTypeException
95
-     * @throws InvalidInterfaceException
96
-     * @throws InvalidArgumentException
97
-     * @throws DomainException
98
-     */
99
-    public function getInputHtml(array $input_settings = array())
100
-    {
101
-        return $this->getInput($input_settings)->get_html_for_input();
102
-    }
90
+	/**
91
+	 * @param array $input_settings
92
+	 * @return string
93
+	 * @throws EE_Error
94
+	 * @throws InvalidDataTypeException
95
+	 * @throws InvalidInterfaceException
96
+	 * @throws InvalidArgumentException
97
+	 * @throws DomainException
98
+	 */
99
+	public function getInputHtml(array $input_settings = array())
100
+	{
101
+		return $this->getInput($input_settings)->get_html_for_input();
102
+	}
103 103
 
104 104
 
105
-    /**
106
-     * @param EE_Form_Section_Proper $form
107
-     * @param array                  $input_settings
108
-     * @throws EE_Error
109
-     * @throws InvalidArgumentException
110
-     * @throws InvalidDataTypeException
111
-     * @throws InvalidInterfaceException
112
-     * @throws DomainException
113
-     */
114
-    public function addToFormSection(EE_Form_Section_Proper $form, array $input_settings = array())
115
-    {
116
-        $form->add_subsections(
117
-            array(
118
-                'espresso_recaptcha' => $this->getInput($input_settings),
119
-            ),
120
-            null,
121
-            false
122
-        );
123
-    }
105
+	/**
106
+	 * @param EE_Form_Section_Proper $form
107
+	 * @param array                  $input_settings
108
+	 * @throws EE_Error
109
+	 * @throws InvalidArgumentException
110
+	 * @throws InvalidDataTypeException
111
+	 * @throws InvalidInterfaceException
112
+	 * @throws DomainException
113
+	 */
114
+	public function addToFormSection(EE_Form_Section_Proper $form, array $input_settings = array())
115
+	{
116
+		$form->add_subsections(
117
+			array(
118
+				'espresso_recaptcha' => $this->getInput($input_settings),
119
+			),
120
+			null,
121
+			false
122
+		);
123
+	}
124 124
 
125 125
 
126
-    /**
127
-     * @param RequestInterface $request
128
-     * @return boolean
129
-     * @throws InvalidArgumentException
130
-     * @throws InvalidDataTypeException
131
-     * @throws InvalidInterfaceException
132
-     * @throws RuntimeException
133
-     */
134
-    public function verifyToken(RequestInterface $request)
135
-    {
136
-        static $previous_recaptcha_response = array();
137
-        $grecaptcha_response = $request->getRequestParam('g-recaptcha-response');
138
-        // if this token has already been verified, then return previous response
139
-        if (isset($previous_recaptcha_response[ $grecaptcha_response ])) {
140
-            return $previous_recaptcha_response[ $grecaptcha_response ];
141
-        }
142
-        // still here but no g-recaptcha-response ? - verification failed
143
-        if (! $grecaptcha_response) {
144
-            EE_Error::add_error(
145
-                sprintf(
146
-                    /* translators: 1: missing parameter */
147
-                    esc_html__(
148
-                        // @codingStandardsIgnoreStart
149
-                        'We\'re sorry but an attempt to verify the form\'s reCAPTCHA has failed. Missing "%1$s". Please try again.',
150
-                        // @codingStandardsIgnoreEnd
151
-                        'event_espresso'
152
-                    ),
153
-                    'g-recaptcha-response'
154
-                ),
155
-                __FILE__,
156
-                __FUNCTION__,
157
-                __LINE__
158
-            );
159
-            return false;
160
-        }
161
-        // will update to true if everything passes
162
-        $previous_recaptcha_response[ $grecaptcha_response ] = false;
163
-        $response                                            = wp_safe_remote_post(
164
-            InvisibleRecaptcha::URL_GOOGLE_RECAPTCHA_API,
165
-            array(
166
-                'body' => array(
167
-                    'secret'   => $this->config->recaptcha_privatekey,
168
-                    'response' => $grecaptcha_response,
169
-                    'remoteip' => $request->ipAddress(),
170
-                ),
171
-            )
172
-        );
173
-        if ($response instanceof WP_Error) {
174
-            $this->generateError($response->get_error_messages());
175
-            return false;
176
-        }
177
-        $results = json_decode(wp_remote_retrieve_body($response), true);
178
-        if (filter_var($results['success'], FILTER_VALIDATE_BOOLEAN) !== true) {
179
-            $errors = [];
180
-            if (! empty($results['error-codes'])) {
181
-                $errors = array_map([$this, 'getErrorCode'], $results['error-codes']);
182
-            }
183
-            if (isset($results['challenge_ts'])) {
184
-                $errors[] = 'challenge timestamp: ' . $results['challenge_ts'] . '.';
185
-            }
186
-            $this->generateError(implode(' ', $errors), true);
187
-        }
188
-        $previous_recaptcha_response[ $grecaptcha_response ] = true;
189
-        add_action('shutdown', array($this, 'setSessionData'));
190
-        return true;
191
-    }
126
+	/**
127
+	 * @param RequestInterface $request
128
+	 * @return boolean
129
+	 * @throws InvalidArgumentException
130
+	 * @throws InvalidDataTypeException
131
+	 * @throws InvalidInterfaceException
132
+	 * @throws RuntimeException
133
+	 */
134
+	public function verifyToken(RequestInterface $request)
135
+	{
136
+		static $previous_recaptcha_response = array();
137
+		$grecaptcha_response = $request->getRequestParam('g-recaptcha-response');
138
+		// if this token has already been verified, then return previous response
139
+		if (isset($previous_recaptcha_response[ $grecaptcha_response ])) {
140
+			return $previous_recaptcha_response[ $grecaptcha_response ];
141
+		}
142
+		// still here but no g-recaptcha-response ? - verification failed
143
+		if (! $grecaptcha_response) {
144
+			EE_Error::add_error(
145
+				sprintf(
146
+					/* translators: 1: missing parameter */
147
+					esc_html__(
148
+						// @codingStandardsIgnoreStart
149
+						'We\'re sorry but an attempt to verify the form\'s reCAPTCHA has failed. Missing "%1$s". Please try again.',
150
+						// @codingStandardsIgnoreEnd
151
+						'event_espresso'
152
+					),
153
+					'g-recaptcha-response'
154
+				),
155
+				__FILE__,
156
+				__FUNCTION__,
157
+				__LINE__
158
+			);
159
+			return false;
160
+		}
161
+		// will update to true if everything passes
162
+		$previous_recaptcha_response[ $grecaptcha_response ] = false;
163
+		$response                                            = wp_safe_remote_post(
164
+			InvisibleRecaptcha::URL_GOOGLE_RECAPTCHA_API,
165
+			array(
166
+				'body' => array(
167
+					'secret'   => $this->config->recaptcha_privatekey,
168
+					'response' => $grecaptcha_response,
169
+					'remoteip' => $request->ipAddress(),
170
+				),
171
+			)
172
+		);
173
+		if ($response instanceof WP_Error) {
174
+			$this->generateError($response->get_error_messages());
175
+			return false;
176
+		}
177
+		$results = json_decode(wp_remote_retrieve_body($response), true);
178
+		if (filter_var($results['success'], FILTER_VALIDATE_BOOLEAN) !== true) {
179
+			$errors = [];
180
+			if (! empty($results['error-codes'])) {
181
+				$errors = array_map([$this, 'getErrorCode'], $results['error-codes']);
182
+			}
183
+			if (isset($results['challenge_ts'])) {
184
+				$errors[] = 'challenge timestamp: ' . $results['challenge_ts'] . '.';
185
+			}
186
+			$this->generateError(implode(' ', $errors), true);
187
+		}
188
+		$previous_recaptcha_response[ $grecaptcha_response ] = true;
189
+		add_action('shutdown', array($this, 'setSessionData'));
190
+		return true;
191
+	}
192 192
 
193 193
 
194
-    /**
195
-     * @param string $error_response
196
-     * @param bool   $show_errors
197
-     * @return void
198
-     * @throws RuntimeException
199
-     */
200
-    public function generateError($error_response = '', $show_errors = false)
201
-    {
202
-        throw new RuntimeException(
203
-            sprintf(
204
-                esc_html__(
205
-                    'We\'re sorry but an attempt to verify the form\'s reCAPTCHA has failed. %1$s %2$s Please try again.',
206
-                    'event_espresso'
207
-                ),
208
-                '<br />',
209
-                $show_errors || current_user_can('manage_options') ? $error_response : ''
210
-            )
211
-        );
212
-    }
194
+	/**
195
+	 * @param string $error_response
196
+	 * @param bool   $show_errors
197
+	 * @return void
198
+	 * @throws RuntimeException
199
+	 */
200
+	public function generateError($error_response = '', $show_errors = false)
201
+	{
202
+		throw new RuntimeException(
203
+			sprintf(
204
+				esc_html__(
205
+					'We\'re sorry but an attempt to verify the form\'s reCAPTCHA has failed. %1$s %2$s Please try again.',
206
+					'event_espresso'
207
+				),
208
+				'<br />',
209
+				$show_errors || current_user_can('manage_options') ? $error_response : ''
210
+			)
211
+		);
212
+	}
213 213
 
214 214
 
215
-    /**
216
-     * @param string $error_code
217
-     * @return string
218
-     */
219
-    public function getErrorCode(&$error_code)
220
-    {
221
-        $error_codes = array(
222
-            'missing-input-secret'   => 'The secret parameter is missing.',
223
-            'invalid-input-secret'   => 'The secret parameter is invalid or malformed.',
224
-            'missing-input-response' => 'The response parameter is missing.',
225
-            'invalid-input-response' => 'The response parameter is invalid or malformed.',
226
-            'bad-request'            => 'The request is invalid or malformed.',
227
-            'timeout-or-duplicate'   => 'The request took too long to be sent or was a duplicate of a previous request.',
228
-        );
229
-        return isset($error_codes[ $error_code ]) ? $error_codes[ $error_code ] : '';
230
-    }
215
+	/**
216
+	 * @param string $error_code
217
+	 * @return string
218
+	 */
219
+	public function getErrorCode(&$error_code)
220
+	{
221
+		$error_codes = array(
222
+			'missing-input-secret'   => 'The secret parameter is missing.',
223
+			'invalid-input-secret'   => 'The secret parameter is invalid or malformed.',
224
+			'missing-input-response' => 'The response parameter is missing.',
225
+			'invalid-input-response' => 'The response parameter is invalid or malformed.',
226
+			'bad-request'            => 'The request is invalid or malformed.',
227
+			'timeout-or-duplicate'   => 'The request took too long to be sent or was a duplicate of a previous request.',
228
+		);
229
+		return isset($error_codes[ $error_code ]) ? $error_codes[ $error_code ] : '';
230
+	}
231 231
 
232 232
 
233
-    /**
234
-     * @return array
235
-     * @throws InvalidInterfaceException
236
-     * @throws InvalidDataTypeException
237
-     * @throws InvalidArgumentException
238
-     */
239
-    public function getLocalizedVars()
240
-    {
241
-        return (array) apply_filters(
242
-            'FHEE__EventEspresso_caffeinated_modules_recaptcha_invisible_InvisibleRecaptcha__getLocalizedVars__localized_vars',
243
-            array(
244
-                'siteKey'          => $this->config->recaptcha_publickey,
245
-                'recaptcha_passed' => $this->recaptchaPassed(),
246
-                'wp_debug'         => WP_DEBUG,
247
-                'disable_submit'   => defined('EE_EVENT_QUEUE_BASE_URL'),
248
-                'failed_message'   => wp_strip_all_tags(
249
-                    __(
250
-                        'We\'re sorry but an attempt to verify the form\'s reCAPTCHA has failed. Please try again.',
251
-                        'event_espresso'
252
-                    )
253
-                )
254
-            )
255
-        );
256
-    }
233
+	/**
234
+	 * @return array
235
+	 * @throws InvalidInterfaceException
236
+	 * @throws InvalidDataTypeException
237
+	 * @throws InvalidArgumentException
238
+	 */
239
+	public function getLocalizedVars()
240
+	{
241
+		return (array) apply_filters(
242
+			'FHEE__EventEspresso_caffeinated_modules_recaptcha_invisible_InvisibleRecaptcha__getLocalizedVars__localized_vars',
243
+			array(
244
+				'siteKey'          => $this->config->recaptcha_publickey,
245
+				'recaptcha_passed' => $this->recaptchaPassed(),
246
+				'wp_debug'         => WP_DEBUG,
247
+				'disable_submit'   => defined('EE_EVENT_QUEUE_BASE_URL'),
248
+				'failed_message'   => wp_strip_all_tags(
249
+					__(
250
+						'We\'re sorry but an attempt to verify the form\'s reCAPTCHA has failed. Please try again.',
251
+						'event_espresso'
252
+					)
253
+				)
254
+			)
255
+		);
256
+	}
257 257
 
258 258
 
259
-    /**
260
-     * @return boolean
261
-     * @throws InvalidInterfaceException
262
-     * @throws InvalidDataTypeException
263
-     * @throws InvalidArgumentException
264
-     */
265
-    public function recaptchaPassed()
266
-    {
267
-        if ($this->recaptcha_passed !== null) {
268
-            return $this->recaptcha_passed;
269
-        }
270
-        // logged in means you have already passed a turing test of sorts
271
-        if ($this->useInvisibleRecaptcha() === false || is_user_logged_in()) {
272
-            $this->recaptcha_passed = true;
273
-            return $this->recaptcha_passed;
274
-        }
275
-        // was test already passed?
276
-        $this->recaptcha_passed = filter_var(
277
-            $this->session->get_session_data(
278
-                InvisibleRecaptcha::SESSION_DATA_KEY_RECAPTCHA_PASSED
279
-            ),
280
-            FILTER_VALIDATE_BOOLEAN
281
-        );
282
-        return $this->recaptcha_passed;
283
-    }
259
+	/**
260
+	 * @return boolean
261
+	 * @throws InvalidInterfaceException
262
+	 * @throws InvalidDataTypeException
263
+	 * @throws InvalidArgumentException
264
+	 */
265
+	public function recaptchaPassed()
266
+	{
267
+		if ($this->recaptcha_passed !== null) {
268
+			return $this->recaptcha_passed;
269
+		}
270
+		// logged in means you have already passed a turing test of sorts
271
+		if ($this->useInvisibleRecaptcha() === false || is_user_logged_in()) {
272
+			$this->recaptcha_passed = true;
273
+			return $this->recaptcha_passed;
274
+		}
275
+		// was test already passed?
276
+		$this->recaptcha_passed = filter_var(
277
+			$this->session->get_session_data(
278
+				InvisibleRecaptcha::SESSION_DATA_KEY_RECAPTCHA_PASSED
279
+			),
280
+			FILTER_VALIDATE_BOOLEAN
281
+		);
282
+		return $this->recaptcha_passed;
283
+	}
284 284
 
285 285
 
286
-    /**
287
-     * @throws InvalidArgumentException
288
-     * @throws InvalidDataTypeException
289
-     * @throws InvalidInterfaceException
290
-     */
291
-    public function setSessionData()
292
-    {
293
-        if ($this->session instanceof EE_Session) {
294
-            $this->session->set_session_data([InvisibleRecaptcha::SESSION_DATA_KEY_RECAPTCHA_PASSED => true]);
295
-        }
296
-    }
286
+	/**
287
+	 * @throws InvalidArgumentException
288
+	 * @throws InvalidDataTypeException
289
+	 * @throws InvalidInterfaceException
290
+	 */
291
+	public function setSessionData()
292
+	{
293
+		if ($this->session instanceof EE_Session) {
294
+			$this->session->set_session_data([InvisibleRecaptcha::SESSION_DATA_KEY_RECAPTCHA_PASSED => true]);
295
+		}
296
+	}
297 297
 }
Please login to merge, or discard this patch.
Spacing   +8 added lines, -8 removed lines patch added patch discarded remove patch
@@ -136,11 +136,11 @@  discard block
 block discarded – undo
136 136
         static $previous_recaptcha_response = array();
137 137
         $grecaptcha_response = $request->getRequestParam('g-recaptcha-response');
138 138
         // if this token has already been verified, then return previous response
139
-        if (isset($previous_recaptcha_response[ $grecaptcha_response ])) {
140
-            return $previous_recaptcha_response[ $grecaptcha_response ];
139
+        if (isset($previous_recaptcha_response[$grecaptcha_response])) {
140
+            return $previous_recaptcha_response[$grecaptcha_response];
141 141
         }
142 142
         // still here but no g-recaptcha-response ? - verification failed
143
-        if (! $grecaptcha_response) {
143
+        if ( ! $grecaptcha_response) {
144 144
             EE_Error::add_error(
145 145
                 sprintf(
146 146
                     /* translators: 1: missing parameter */
@@ -159,7 +159,7 @@  discard block
 block discarded – undo
159 159
             return false;
160 160
         }
161 161
         // will update to true if everything passes
162
-        $previous_recaptcha_response[ $grecaptcha_response ] = false;
162
+        $previous_recaptcha_response[$grecaptcha_response] = false;
163 163
         $response                                            = wp_safe_remote_post(
164 164
             InvisibleRecaptcha::URL_GOOGLE_RECAPTCHA_API,
165 165
             array(
@@ -177,15 +177,15 @@  discard block
 block discarded – undo
177 177
         $results = json_decode(wp_remote_retrieve_body($response), true);
178 178
         if (filter_var($results['success'], FILTER_VALIDATE_BOOLEAN) !== true) {
179 179
             $errors = [];
180
-            if (! empty($results['error-codes'])) {
180
+            if ( ! empty($results['error-codes'])) {
181 181
                 $errors = array_map([$this, 'getErrorCode'], $results['error-codes']);
182 182
             }
183 183
             if (isset($results['challenge_ts'])) {
184
-                $errors[] = 'challenge timestamp: ' . $results['challenge_ts'] . '.';
184
+                $errors[] = 'challenge timestamp: '.$results['challenge_ts'].'.';
185 185
             }
186 186
             $this->generateError(implode(' ', $errors), true);
187 187
         }
188
-        $previous_recaptcha_response[ $grecaptcha_response ] = true;
188
+        $previous_recaptcha_response[$grecaptcha_response] = true;
189 189
         add_action('shutdown', array($this, 'setSessionData'));
190 190
         return true;
191 191
     }
@@ -226,7 +226,7 @@  discard block
 block discarded – undo
226 226
             'bad-request'            => 'The request is invalid or malformed.',
227 227
             'timeout-or-duplicate'   => 'The request took too long to be sent or was a duplicate of a previous request.',
228 228
         );
229
-        return isset($error_codes[ $error_code ]) ? $error_codes[ $error_code ] : '';
229
+        return isset($error_codes[$error_code]) ? $error_codes[$error_code] : '';
230 230
     }
231 231
 
232 232
 
Please login to merge, or discard this patch.