Completed
Branch master (d4ace2)
by
unknown
04:43
created
core/db_models/EEM_Ticket_Template.model.php 1 patch
Indentation   +63 added lines, -63 removed lines patch added patch discarded remove patch
@@ -9,70 +9,70 @@
 block discarded – undo
9 9
  */
10 10
 class EEM_Ticket_Template extends EEM_Base
11 11
 {
12
-    protected static ?EEM_Ticket_Template $_instance = null;
12
+	protected static ?EEM_Ticket_Template $_instance = null;
13 13
 
14 14
 
15
-    /**
16
-     * private constructor to prevent direct creation
17
-     *
18
-     * @param string|null $timezone string representing the timezone we want to set for returned Date Time Strings (and
19
-     *                              any incoming timezone data that gets saved).  Note this just sends the timezone
20
-     *                              info to the date time model field objects.  Default is NULL (and will be assumed
21
-     *                              using the set timezone in the 'timezone_string' wp option)
22
-     * @throws EE_Error
23
-     */
24
-    protected function __construct(?string $timezone = '')
25
-    {
26
-        $this->singular_item = esc_html__('Ticket Template', 'event_espresso');
27
-        $this->plural_item   = esc_html__('Ticket Templates', 'event_espresso');
15
+	/**
16
+	 * private constructor to prevent direct creation
17
+	 *
18
+	 * @param string|null $timezone string representing the timezone we want to set for returned Date Time Strings (and
19
+	 *                              any incoming timezone data that gets saved).  Note this just sends the timezone
20
+	 *                              info to the date time model field objects.  Default is NULL (and will be assumed
21
+	 *                              using the set timezone in the 'timezone_string' wp option)
22
+	 * @throws EE_Error
23
+	 */
24
+	protected function __construct(?string $timezone = '')
25
+	{
26
+		$this->singular_item = esc_html__('Ticket Template', 'event_espresso');
27
+		$this->plural_item   = esc_html__('Ticket Templates', 'event_espresso');
28 28
 
29
-        $this->_tables                                            = [
30
-            'Ticket_Template' => new EE_Primary_Table('esp_ticket_template', 'TTM_ID'),
31
-        ];
32
-        $this->_fields                                            = [
33
-            'Ticket_Template' => [
34
-                'TTM_ID'          => new EE_Primary_Key_Int_Field(
35
-                    'TTM_ID', esc_html__('Ticket Template ID', 'event_espresso')
36
-                ),
37
-                'TTM_name'        => new EE_Plain_Text_Field(
38
-                    'TTM_name',
39
-                    esc_html__('The name of the ticket template', 'event_espresso'),
40
-                    false,
41
-                    ''
42
-                ),
43
-                'TTM_description' => new EE_Plain_Text_Field(
44
-                    'TTM_description',
45
-                    esc_html__(
46
-                        'The description for the ticket template',
47
-                        'event_espresso'
48
-                    ),
49
-                    true,
50
-                    ''
51
-                ),
52
-                'TTM_file'        => new EE_Plain_Text_Field(
53
-                    'TTM_file',
54
-                    esc_html__(
55
-                        'The file name for the actual template file saved on disk',
56
-                        'event_espresso'
57
-                    ),
58
-                    true,
59
-                    ''
60
-                ),
61
-            ],
62
-        ];
63
-        $this->_model_relations                                   = [
64
-            'Ticket' => new EE_Has_Many_Relation(),
65
-        ];
66
-        $this->_model_chain_to_wp_user                            = 'Ticket';
67
-        $this->_cap_restriction_generators[ EEM_Base::caps_read ] =
68
-            new EE_Restriction_Generator_Default_Public('Ticket.TKT_is_default', 'Ticket.Datetime.Event');
69
-        // account for default tickets in the caps
70
-        $this->_cap_restriction_generators[ EEM_Base::caps_read_admin ] =
71
-            new EE_Restriction_Generator_Default_Protected('Ticket.TKT_is_default', 'Ticket.Datetime.Event');
72
-        $this->_cap_restriction_generators[ EEM_Base::caps_edit ]       =
73
-            new EE_Restriction_Generator_Default_Protected('Ticket.TKT_is_default', 'Ticket.Datetime.Event');
74
-        $this->_cap_restriction_generators[ EEM_Base::caps_delete ]     =
75
-            new EE_Restriction_Generator_Default_Protected('Ticket.TKT_is_default', 'Ticket.Datetime.Event');
76
-        parent::__construct($timezone);
77
-    }
29
+		$this->_tables                                            = [
30
+			'Ticket_Template' => new EE_Primary_Table('esp_ticket_template', 'TTM_ID'),
31
+		];
32
+		$this->_fields                                            = [
33
+			'Ticket_Template' => [
34
+				'TTM_ID'          => new EE_Primary_Key_Int_Field(
35
+					'TTM_ID', esc_html__('Ticket Template ID', 'event_espresso')
36
+				),
37
+				'TTM_name'        => new EE_Plain_Text_Field(
38
+					'TTM_name',
39
+					esc_html__('The name of the ticket template', 'event_espresso'),
40
+					false,
41
+					''
42
+				),
43
+				'TTM_description' => new EE_Plain_Text_Field(
44
+					'TTM_description',
45
+					esc_html__(
46
+						'The description for the ticket template',
47
+						'event_espresso'
48
+					),
49
+					true,
50
+					''
51
+				),
52
+				'TTM_file'        => new EE_Plain_Text_Field(
53
+					'TTM_file',
54
+					esc_html__(
55
+						'The file name for the actual template file saved on disk',
56
+						'event_espresso'
57
+					),
58
+					true,
59
+					''
60
+				),
61
+			],
62
+		];
63
+		$this->_model_relations                                   = [
64
+			'Ticket' => new EE_Has_Many_Relation(),
65
+		];
66
+		$this->_model_chain_to_wp_user                            = 'Ticket';
67
+		$this->_cap_restriction_generators[ EEM_Base::caps_read ] =
68
+			new EE_Restriction_Generator_Default_Public('Ticket.TKT_is_default', 'Ticket.Datetime.Event');
69
+		// account for default tickets in the caps
70
+		$this->_cap_restriction_generators[ EEM_Base::caps_read_admin ] =
71
+			new EE_Restriction_Generator_Default_Protected('Ticket.TKT_is_default', 'Ticket.Datetime.Event');
72
+		$this->_cap_restriction_generators[ EEM_Base::caps_edit ]       =
73
+			new EE_Restriction_Generator_Default_Protected('Ticket.TKT_is_default', 'Ticket.Datetime.Event');
74
+		$this->_cap_restriction_generators[ EEM_Base::caps_delete ]     =
75
+			new EE_Restriction_Generator_Default_Protected('Ticket.TKT_is_default', 'Ticket.Datetime.Event');
76
+		parent::__construct($timezone);
77
+	}
78 78
 }
Please login to merge, or discard this patch.
core/db_models/EEM_Country.model.php 2 patches
Indentation   +208 added lines, -208 removed lines patch added patch discarded remove patch
@@ -10,212 +10,212 @@
 block discarded – undo
10 10
  */
11 11
 class EEM_Country extends EEM_Base
12 12
 {
13
-    protected static ?EEM_Country $_instance = null;
14
-
15
-    // array of all countries
16
-    private static ?array $_all_countries = null;
17
-
18
-    // array of all active countries
19
-    private static ?array $_active_countries = null;
20
-
21
-
22
-    /**
23
-     * Resets the country
24
-     *
25
-     * @param string|null $timezone
26
-     * @return EEM_Country|EEM_Base|null
27
-     * @throws EE_Error
28
-     * @throws ReflectionException
29
-     */
30
-    public static function reset($timezone = '')
31
-    {
32
-        self::$_active_countries = null;
33
-        self::$_all_countries    = null;
34
-        return parent::reset($timezone);
35
-    }
36
-
37
-
38
-    /**
39
-     * @param string|null $timezone
40
-     * @throws EE_Error
41
-     */
42
-    protected function __construct(?string $timezone = '')
43
-    {
44
-        $this->singular_item = esc_html__('Country', 'event_espresso');
45
-        $this->plural_item   = esc_html__('Countries', 'event_espresso');
46
-
47
-        $this->_tables = [
48
-            'Country' => new EE_Primary_Table('esp_country', 'CNT_ISO'),
49
-        ];
50
-
51
-        $this->_fields          = [
52
-            'Country' => [
53
-                'CNT_active'      => new EE_Boolean_Field(
54
-                    'CNT_active',
55
-                    esc_html__('Country Appears in Dropdown Select Lists', 'event_espresso'),
56
-                    false,
57
-                    true
58
-                ),
59
-                'CNT_ISO'         => new EE_Primary_Key_String_Field(
60
-                    'CNT_ISO',
61
-                    esc_html__('Country ISO Code', 'event_espresso')
62
-                ),
63
-                'CNT_ISO3'        => new EE_All_Caps_Text_Field(
64
-                    'CNT_ISO3',
65
-                    esc_html__('Country ISO3 Code', 'event_espresso'),
66
-                    false,
67
-                    ''
68
-                ),
69
-                'RGN_ID'          => new EE_Integer_Field(
70
-                    'RGN_ID',
71
-                    esc_html__('Region ID', 'event_espresso'),
72
-                    false,
73
-                    0
74
-                ),
75
-                // should be a foreign key, but no region table exists yet
76
-                'CNT_name'        => new EE_Plain_Text_Field(
77
-                    'CNT_name',
78
-                    esc_html__('Country Name', 'event_espresso'),
79
-                    false,
80
-                    ''
81
-                ),
82
-                'CNT_cur_code'    => new EE_All_Caps_Text_Field(
83
-                    'CNT_cur_code',
84
-                    esc_html__('Country Currency Code', 'event_espresso'),
85
-                    false
86
-                ),
87
-                'CNT_cur_single'  => new EE_Plain_Text_Field(
88
-                    'CNT_cur_single',
89
-                    esc_html__('Currency Name Singular', 'event_espresso'),
90
-                    false
91
-                ),
92
-                'CNT_cur_plural'  => new EE_Plain_Text_Field(
93
-                    'CNT_cur_plural',
94
-                    esc_html__('Currency Name Plural', 'event_espresso'),
95
-                    false
96
-                ),
97
-                'CNT_cur_sign'    => new EE_Plain_Text_Field(
98
-                    'CNT_cur_sign',
99
-                    esc_html__('Currency Sign', 'event_espresso'),
100
-                    false
101
-                ),
102
-                'CNT_cur_sign_b4' => new EE_Boolean_Field(
103
-                    'CNT_cur_sign_b4',
104
-                    esc_html__('Currency Sign Before Number', 'event_espresso'),
105
-                    false,
106
-                    true
107
-                ),
108
-                'CNT_cur_dec_plc' => new EE_Integer_Field(
109
-                    'CNT_cur_dec_plc',
110
-                    esc_html__('Currency Decimal Places', 'event_espresso'),
111
-                    false,
112
-                    2
113
-                ),
114
-                'CNT_cur_dec_mrk' => new EE_Plain_Text_Field(
115
-                    'CNT_cur_dec_mrk',
116
-                    esc_html__('Currency Decimal Mark', 'event_espresso'),
117
-                    false,
118
-                    '.'
119
-                ),
120
-                'CNT_cur_thsnds'  => new EE_Plain_Text_Field(
121
-                    'CNT_cur_thsnds',
122
-                    esc_html__('Currency Thousands Separator', 'event_espresso'),
123
-                    false,
124
-                    ','
125
-                ),
126
-                'CNT_tel_code'    => new EE_Plain_Text_Field(
127
-                    'CNT_tel_code',
128
-                    esc_html__('Country Telephone Code', 'event_espresso'),
129
-                    false,
130
-                    ''
131
-                ),
132
-                'CNT_is_EU'       => new EE_Boolean_Field(
133
-                    'CNT_is_EU',
134
-                    esc_html__('Country is Member of EU', 'event_espresso'),
135
-                    false,
136
-                    false
137
-                ),
138
-            ],
139
-        ];
140
-        $this->_model_relations = [
141
-            'Attendee' => new EE_Has_Many_Relation(),
142
-            'State'    => new EE_Has_Many_Relation(),
143
-            'Venue'    => new EE_Has_Many_Relation(),
144
-        ];
145
-        // only anyone to view, but only those with the default role can do anything
146
-        $this->_cap_restriction_generators[ EEM_Base::caps_read ] = new EE_Restriction_Generator_Public();
147
-
148
-        parent::__construct($timezone);
149
-    }
150
-
151
-
152
-    /**
153
-     * @return EE_Country[]
154
-     * @throws EE_Error
155
-     */
156
-    public function get_all_countries()
157
-    {
158
-        if (! self::$_all_countries) {
159
-            self::$_all_countries = $this->get_all(['order_by' => ['CNT_name' => 'ASC'], 'limit' => [0, 99999]]);
160
-        }
161
-        return self::$_all_countries;
162
-    }
163
-
164
-
165
-    /**
166
-     * Gets and caches the list of active countries. If you know the list of active countries
167
-     * has changed during this request, first use EEM_Country::reset() to flush the cache
168
-     *
169
-     * @return EE_Country[]
170
-     * @throws EE_Error
171
-     */
172
-    public function get_all_active_countries()
173
-    {
174
-        if (! self::$_active_countries) {
175
-            self::$_active_countries =
176
-                $this->get_all([['CNT_active' => true], 'order_by' => ['CNT_name' => 'ASC'], 'limit' => [0, 99999]]);
177
-        }
178
-        return self::$_active_countries;
179
-    }
180
-
181
-
182
-    /**
183
-     * Gets the country's name by its ISO
184
-     *
185
-     * @param string $country_ISO
186
-     * @return string
187
-     * @throws EE_Error
188
-     */
189
-    public function get_country_name_by_ISO($country_ISO)
190
-    {
191
-        $countries = $this->get_all_countries();
192
-        if (isset($countries[ $country_ISO ]) && $countries[ $country_ISO ] instanceof EE_Country) {
193
-            return $countries[ $country_ISO ]->name();
194
-        }
195
-        $names = $this->get_col([['CNT_ISO' => $country_ISO], 'limit' => 1], 'CNT_name');
196
-        if (is_array($names) && ! empty($names)) {
197
-            return reset($names);
198
-        } else {
199
-            return '';
200
-        }
201
-    }
202
-
203
-
204
-    /**
205
-     * Gets the country's name by its name
206
-     *
207
-     * @param string $country_name
208
-     * @return EE_Country|null
209
-     * @throws EE_Error
210
-     */
211
-    public function getCountryByName($country_name)
212
-    {
213
-        $countries = $this->get_all_countries();
214
-        foreach ($countries as $country) {
215
-            if ($country instanceof EE_Country && $country->name() === $country_name) {
216
-                return $country;
217
-            }
218
-        }
219
-        return $this->get_one([['CNT_name' => $country_name]]);
220
-    }
13
+	protected static ?EEM_Country $_instance = null;
14
+
15
+	// array of all countries
16
+	private static ?array $_all_countries = null;
17
+
18
+	// array of all active countries
19
+	private static ?array $_active_countries = null;
20
+
21
+
22
+	/**
23
+	 * Resets the country
24
+	 *
25
+	 * @param string|null $timezone
26
+	 * @return EEM_Country|EEM_Base|null
27
+	 * @throws EE_Error
28
+	 * @throws ReflectionException
29
+	 */
30
+	public static function reset($timezone = '')
31
+	{
32
+		self::$_active_countries = null;
33
+		self::$_all_countries    = null;
34
+		return parent::reset($timezone);
35
+	}
36
+
37
+
38
+	/**
39
+	 * @param string|null $timezone
40
+	 * @throws EE_Error
41
+	 */
42
+	protected function __construct(?string $timezone = '')
43
+	{
44
+		$this->singular_item = esc_html__('Country', 'event_espresso');
45
+		$this->plural_item   = esc_html__('Countries', 'event_espresso');
46
+
47
+		$this->_tables = [
48
+			'Country' => new EE_Primary_Table('esp_country', 'CNT_ISO'),
49
+		];
50
+
51
+		$this->_fields          = [
52
+			'Country' => [
53
+				'CNT_active'      => new EE_Boolean_Field(
54
+					'CNT_active',
55
+					esc_html__('Country Appears in Dropdown Select Lists', 'event_espresso'),
56
+					false,
57
+					true
58
+				),
59
+				'CNT_ISO'         => new EE_Primary_Key_String_Field(
60
+					'CNT_ISO',
61
+					esc_html__('Country ISO Code', 'event_espresso')
62
+				),
63
+				'CNT_ISO3'        => new EE_All_Caps_Text_Field(
64
+					'CNT_ISO3',
65
+					esc_html__('Country ISO3 Code', 'event_espresso'),
66
+					false,
67
+					''
68
+				),
69
+				'RGN_ID'          => new EE_Integer_Field(
70
+					'RGN_ID',
71
+					esc_html__('Region ID', 'event_espresso'),
72
+					false,
73
+					0
74
+				),
75
+				// should be a foreign key, but no region table exists yet
76
+				'CNT_name'        => new EE_Plain_Text_Field(
77
+					'CNT_name',
78
+					esc_html__('Country Name', 'event_espresso'),
79
+					false,
80
+					''
81
+				),
82
+				'CNT_cur_code'    => new EE_All_Caps_Text_Field(
83
+					'CNT_cur_code',
84
+					esc_html__('Country Currency Code', 'event_espresso'),
85
+					false
86
+				),
87
+				'CNT_cur_single'  => new EE_Plain_Text_Field(
88
+					'CNT_cur_single',
89
+					esc_html__('Currency Name Singular', 'event_espresso'),
90
+					false
91
+				),
92
+				'CNT_cur_plural'  => new EE_Plain_Text_Field(
93
+					'CNT_cur_plural',
94
+					esc_html__('Currency Name Plural', 'event_espresso'),
95
+					false
96
+				),
97
+				'CNT_cur_sign'    => new EE_Plain_Text_Field(
98
+					'CNT_cur_sign',
99
+					esc_html__('Currency Sign', 'event_espresso'),
100
+					false
101
+				),
102
+				'CNT_cur_sign_b4' => new EE_Boolean_Field(
103
+					'CNT_cur_sign_b4',
104
+					esc_html__('Currency Sign Before Number', 'event_espresso'),
105
+					false,
106
+					true
107
+				),
108
+				'CNT_cur_dec_plc' => new EE_Integer_Field(
109
+					'CNT_cur_dec_plc',
110
+					esc_html__('Currency Decimal Places', 'event_espresso'),
111
+					false,
112
+					2
113
+				),
114
+				'CNT_cur_dec_mrk' => new EE_Plain_Text_Field(
115
+					'CNT_cur_dec_mrk',
116
+					esc_html__('Currency Decimal Mark', 'event_espresso'),
117
+					false,
118
+					'.'
119
+				),
120
+				'CNT_cur_thsnds'  => new EE_Plain_Text_Field(
121
+					'CNT_cur_thsnds',
122
+					esc_html__('Currency Thousands Separator', 'event_espresso'),
123
+					false,
124
+					','
125
+				),
126
+				'CNT_tel_code'    => new EE_Plain_Text_Field(
127
+					'CNT_tel_code',
128
+					esc_html__('Country Telephone Code', 'event_espresso'),
129
+					false,
130
+					''
131
+				),
132
+				'CNT_is_EU'       => new EE_Boolean_Field(
133
+					'CNT_is_EU',
134
+					esc_html__('Country is Member of EU', 'event_espresso'),
135
+					false,
136
+					false
137
+				),
138
+			],
139
+		];
140
+		$this->_model_relations = [
141
+			'Attendee' => new EE_Has_Many_Relation(),
142
+			'State'    => new EE_Has_Many_Relation(),
143
+			'Venue'    => new EE_Has_Many_Relation(),
144
+		];
145
+		// only anyone to view, but only those with the default role can do anything
146
+		$this->_cap_restriction_generators[ EEM_Base::caps_read ] = new EE_Restriction_Generator_Public();
147
+
148
+		parent::__construct($timezone);
149
+	}
150
+
151
+
152
+	/**
153
+	 * @return EE_Country[]
154
+	 * @throws EE_Error
155
+	 */
156
+	public function get_all_countries()
157
+	{
158
+		if (! self::$_all_countries) {
159
+			self::$_all_countries = $this->get_all(['order_by' => ['CNT_name' => 'ASC'], 'limit' => [0, 99999]]);
160
+		}
161
+		return self::$_all_countries;
162
+	}
163
+
164
+
165
+	/**
166
+	 * Gets and caches the list of active countries. If you know the list of active countries
167
+	 * has changed during this request, first use EEM_Country::reset() to flush the cache
168
+	 *
169
+	 * @return EE_Country[]
170
+	 * @throws EE_Error
171
+	 */
172
+	public function get_all_active_countries()
173
+	{
174
+		if (! self::$_active_countries) {
175
+			self::$_active_countries =
176
+				$this->get_all([['CNT_active' => true], 'order_by' => ['CNT_name' => 'ASC'], 'limit' => [0, 99999]]);
177
+		}
178
+		return self::$_active_countries;
179
+	}
180
+
181
+
182
+	/**
183
+	 * Gets the country's name by its ISO
184
+	 *
185
+	 * @param string $country_ISO
186
+	 * @return string
187
+	 * @throws EE_Error
188
+	 */
189
+	public function get_country_name_by_ISO($country_ISO)
190
+	{
191
+		$countries = $this->get_all_countries();
192
+		if (isset($countries[ $country_ISO ]) && $countries[ $country_ISO ] instanceof EE_Country) {
193
+			return $countries[ $country_ISO ]->name();
194
+		}
195
+		$names = $this->get_col([['CNT_ISO' => $country_ISO], 'limit' => 1], 'CNT_name');
196
+		if (is_array($names) && ! empty($names)) {
197
+			return reset($names);
198
+		} else {
199
+			return '';
200
+		}
201
+	}
202
+
203
+
204
+	/**
205
+	 * Gets the country's name by its name
206
+	 *
207
+	 * @param string $country_name
208
+	 * @return EE_Country|null
209
+	 * @throws EE_Error
210
+	 */
211
+	public function getCountryByName($country_name)
212
+	{
213
+		$countries = $this->get_all_countries();
214
+		foreach ($countries as $country) {
215
+			if ($country instanceof EE_Country && $country->name() === $country_name) {
216
+				return $country;
217
+			}
218
+		}
219
+		return $this->get_one([['CNT_name' => $country_name]]);
220
+	}
221 221
 }
Please login to merge, or discard this patch.
Spacing   +8 added lines, -8 removed lines patch added patch discarded remove patch
@@ -13,10 +13,10 @@  discard block
 block discarded – undo
13 13
     protected static ?EEM_Country $_instance = null;
14 14
 
15 15
     // array of all countries
16
-    private static ?array $_all_countries = null;
16
+    private static ? array $_all_countries = null;
17 17
 
18 18
     // array of all active countries
19
-    private static ?array $_active_countries = null;
19
+    private static ? array $_active_countries = null;
20 20
 
21 21
 
22 22
     /**
@@ -48,7 +48,7 @@  discard block
 block discarded – undo
48 48
             'Country' => new EE_Primary_Table('esp_country', 'CNT_ISO'),
49 49
         ];
50 50
 
51
-        $this->_fields          = [
51
+        $this->_fields = [
52 52
             'Country' => [
53 53
                 'CNT_active'      => new EE_Boolean_Field(
54 54
                     'CNT_active',
@@ -143,7 +143,7 @@  discard block
 block discarded – undo
143 143
             'Venue'    => new EE_Has_Many_Relation(),
144 144
         ];
145 145
         // only anyone to view, but only those with the default role can do anything
146
-        $this->_cap_restriction_generators[ EEM_Base::caps_read ] = new EE_Restriction_Generator_Public();
146
+        $this->_cap_restriction_generators[EEM_Base::caps_read] = new EE_Restriction_Generator_Public();
147 147
 
148 148
         parent::__construct($timezone);
149 149
     }
@@ -155,7 +155,7 @@  discard block
 block discarded – undo
155 155
      */
156 156
     public function get_all_countries()
157 157
     {
158
-        if (! self::$_all_countries) {
158
+        if ( ! self::$_all_countries) {
159 159
             self::$_all_countries = $this->get_all(['order_by' => ['CNT_name' => 'ASC'], 'limit' => [0, 99999]]);
160 160
         }
161 161
         return self::$_all_countries;
@@ -171,7 +171,7 @@  discard block
 block discarded – undo
171 171
      */
172 172
     public function get_all_active_countries()
173 173
     {
174
-        if (! self::$_active_countries) {
174
+        if ( ! self::$_active_countries) {
175 175
             self::$_active_countries =
176 176
                 $this->get_all([['CNT_active' => true], 'order_by' => ['CNT_name' => 'ASC'], 'limit' => [0, 99999]]);
177 177
         }
@@ -189,8 +189,8 @@  discard block
 block discarded – undo
189 189
     public function get_country_name_by_ISO($country_ISO)
190 190
     {
191 191
         $countries = $this->get_all_countries();
192
-        if (isset($countries[ $country_ISO ]) && $countries[ $country_ISO ] instanceof EE_Country) {
193
-            return $countries[ $country_ISO ]->name();
192
+        if (isset($countries[$country_ISO]) && $countries[$country_ISO] instanceof EE_Country) {
193
+            return $countries[$country_ISO]->name();
194 194
         }
195 195
         $names = $this->get_col([['CNT_ISO' => $country_ISO], 'limit' => 1], 'CNT_name');
196 196
         if (is_array($names) && ! empty($names)) {
Please login to merge, or discard this patch.
core/db_models/EEM_Registration_Payment.model.php 1 patch
Indentation   +49 added lines, -49 removed lines patch added patch discarded remove patch
@@ -12,59 +12,59 @@
 block discarded – undo
12 12
  */
13 13
 class EEM_Registration_Payment extends EEM_Base
14 14
 {
15
-    protected static ?EEM_Registration_Payment $_instance = null;
15
+	protected static ?EEM_Registration_Payment $_instance = null;
16 16
 
17 17
 
18
-    /**
19
-     * @param string|null $timezone
20
-     * @throws EE_Error
21
-     */
22
-    protected function __construct(?string $timezone = '')
23
-    {
24
-        $this->singular_item = esc_html__('Registration Payment', 'event_espresso');
25
-        $this->plural_item   = esc_html__('Registration Payments', 'event_espresso');
18
+	/**
19
+	 * @param string|null $timezone
20
+	 * @throws EE_Error
21
+	 */
22
+	protected function __construct(?string $timezone = '')
23
+	{
24
+		$this->singular_item = esc_html__('Registration Payment', 'event_espresso');
25
+		$this->plural_item   = esc_html__('Registration Payments', 'event_espresso');
26 26
 
27
-        $this->_tables = [
28
-            'Registration_Payment' => new EE_Primary_Table('esp_registration_payment', 'RPY_ID'),
29
-        ];
27
+		$this->_tables = [
28
+			'Registration_Payment' => new EE_Primary_Table('esp_registration_payment', 'RPY_ID'),
29
+		];
30 30
 
31
-        $this->_fields = [
32
-            'Registration_Payment' => [
33
-                'RPY_ID'     => new EE_Primary_Key_Int_Field(
34
-                    'RPY_ID',
35
-                    esc_html__('Registration Payment ID', 'event_espresso')
36
-                ),
37
-                'REG_ID'     => new EE_Foreign_Key_Int_Field(
38
-                    'REG_ID',
39
-                    esc_html__('Registration ID', 'event_espresso'),
40
-                    false,
41
-                    0,
42
-                    'Registration'
43
-                ),
44
-                'PAY_ID'     => new EE_Foreign_Key_Int_Field(
45
-                    'PAY_ID',
46
-                    esc_html__('Payment ID', 'event_espresso'),
47
-                    true,
48
-                    null,
49
-                    'Payment'
50
-                ),
51
-                'RPY_amount' => new EE_Money_Field(
52
-                    'RPY_amount',
53
-                    esc_html__(
54
-                        'Amount attributed to the registration',
55
-                        'event_espresso'
56
-                    ),
57
-                    false,
58
-                    0
59
-                ),
60
-            ],
61
-        ];
31
+		$this->_fields = [
32
+			'Registration_Payment' => [
33
+				'RPY_ID'     => new EE_Primary_Key_Int_Field(
34
+					'RPY_ID',
35
+					esc_html__('Registration Payment ID', 'event_espresso')
36
+				),
37
+				'REG_ID'     => new EE_Foreign_Key_Int_Field(
38
+					'REG_ID',
39
+					esc_html__('Registration ID', 'event_espresso'),
40
+					false,
41
+					0,
42
+					'Registration'
43
+				),
44
+				'PAY_ID'     => new EE_Foreign_Key_Int_Field(
45
+					'PAY_ID',
46
+					esc_html__('Payment ID', 'event_espresso'),
47
+					true,
48
+					null,
49
+					'Payment'
50
+				),
51
+				'RPY_amount' => new EE_Money_Field(
52
+					'RPY_amount',
53
+					esc_html__(
54
+						'Amount attributed to the registration',
55
+						'event_espresso'
56
+					),
57
+					false,
58
+					0
59
+				),
60
+			],
61
+		];
62 62
 
63
-        $this->_model_relations = [
64
-            'Registration' => new EE_Belongs_To_Relation(),
65
-            'Payment'      => new EE_Belongs_To_Relation(),
66
-        ];
63
+		$this->_model_relations = [
64
+			'Registration' => new EE_Belongs_To_Relation(),
65
+			'Payment'      => new EE_Belongs_To_Relation(),
66
+		];
67 67
 
68
-        parent::__construct($timezone);
69
-    }
68
+		parent::__construct($timezone);
69
+	}
70 70
 }
Please login to merge, or discard this patch.
core/db_models/EEM_Question.model.php 2 patches
Indentation   +565 added lines, -565 removed lines patch added patch discarded remove patch
@@ -9,569 +9,569 @@
 block discarded – undo
9 9
  */
10 10
 class EEM_Question extends EEM_Soft_Delete_Base
11 11
 {
12
-    // constant used to indicate that the question type is CHECKBOX
13
-    const QST_type_checkbox = 'CHECKBOX';
14
-
15
-    // constant used to indicate that the question type is COUNTRY
16
-    const QST_type_country = 'COUNTRY';
17
-
18
-    // constant used to indicate that the question type is DATE
19
-    const QST_type_date = 'DATE';
20
-
21
-    // constant used to indicate that the question type is a decimal (float)
22
-    const QST_type_decimal = 'DECIMAL';
23
-
24
-    // constant used to indicate that the question type is DROPDOWN
25
-    const QST_type_dropdown = 'DROPDOWN';
26
-
27
-    // constant used to indicate that the question type is an email input
28
-    const QST_type_email = 'EMAIL';
29
-
30
-    // constant used to indicate that the question type is an email input
31
-    const QST_type_email_confirm = 'EMAIL_CONFIRM';
32
-
33
-    // constant used to indicate that the question type is a TEXTAREA that allows simple html
34
-    const QST_type_html_textarea = 'HTML_TEXTAREA';
35
-
36
-    // constant used to indicate that the question type is an integer (whole number)
37
-    const QST_type_int = 'INTEGER';
38
-
39
-    // constant used to indicate that the question type is a multi-select
40
-    const QST_type_multi_select = 'MULTI_SELECT';
41
-
42
-    // constant used to indicate that the question type is RADIO_BTN
43
-    const QST_type_radio = 'RADIO_BTN';
44
-
45
-    // constant used to indicate that the question type is STATE
46
-    const QST_type_state = 'STATE';
47
-
48
-    // constant used to indicate that the question type is TEXT
49
-    const QST_type_text = 'TEXT';
50
-
51
-    // constant used to indicate that the question type is TEXTAREA
52
-    const QST_type_textarea = 'TEXTAREA';
53
-
54
-    // constant used to indicate that the question type is a valid URL
55
-    const QST_type_url = 'URL';
56
-
57
-    // constant used to indicate that the question type is a US-formatted phone number
58
-    const QST_type_us_phone = 'US_PHONE';
59
-
60
-    // constant used to indicate that the question type is a YEAR
61
-    const QST_type_year = 'YEAR';
62
-
63
-
64
-    protected static ?EEM_Question $_instance = null;
65
-
66
-    /**
67
-     * lists all the question types which should be allowed. Ideally, this will be extensible.
68
-     */
69
-    protected array $_allowed_question_types;
70
-
71
-    /**
72
-     * brief descriptions for all the question types
73
-     */
74
-    protected array $_question_descriptions;
75
-
76
-    /**
77
-     * Question types that are interchangeable, even after answers have been provided for them.
78
-     * Top-level keys are category slugs, next level is an array of question types. If question types
79
-     * aren't in this array, it is assumed they AREN'T interchangeable with any other question types.
80
-     *
81
-     * @var array   $_question_type_categories {
82
-     * @type string $text
83
-     * @type string $single                    -answer-enum
84
-     * @type string $multi                     -answer-enum
85
-     *                                         }
86
-     */
87
-    protected array $_question_type_categories;
88
-
89
-    /**
90
-     * Question types that should have an admin-defined max input length
91
-     */
92
-    protected array $question_types_with_max_length;
93
-
94
-
95
-    /**
96
-     * EEM_Question constructor.
97
-     *
98
-     * @param string|null $timezone
99
-     * @throws EE_Error
100
-     */
101
-    protected function __construct(?string $timezone = '')
102
-    {
103
-        $this->singular_item                  = esc_html__('Question', 'event_espresso');
104
-        $this->plural_item                    = esc_html__('Questions', 'event_espresso');
105
-        $this->_allowed_question_types        = (array) apply_filters(
106
-            'FHEE__EEM_Question__construct__allowed_question_types',
107
-            [
108
-                EEM_Question::QST_type_checkbox      => esc_html__('Checkboxes', 'event_espresso'),
109
-                EEM_Question::QST_type_country       => esc_html__('Country Dropdown', 'event_espresso'),
110
-                EEM_Question::QST_type_date          => esc_html__('Date Picker', 'event_espresso'),
111
-                EEM_Question::QST_type_decimal       => esc_html__('Number', 'event_espresso'),
112
-                EEM_Question::QST_type_dropdown      => esc_html__('Dropdown', 'event_espresso'),
113
-                EEM_Question::QST_type_email         => esc_html__('Email', 'event_espresso'),
114
-                EEM_Question::QST_type_email_confirm => esc_html__('Confirm Email', 'event_espresso'),
115
-                EEM_Question::QST_type_html_textarea => esc_html__('HTML Textarea', 'event_espresso'),
116
-                EEM_Question::QST_type_int           => esc_html__('Whole Number', 'event_espresso'),
117
-                EEM_Question::QST_type_multi_select  => esc_html__('Multi Select', 'event_espresso'),
118
-                EEM_Question::QST_type_radio         => esc_html__('Radio Buttons', 'event_espresso'),
119
-                EEM_Question::QST_type_state         => esc_html__('State/Province Dropdown', 'event_espresso'),
120
-                EEM_Question::QST_type_text          => esc_html__('Text', 'event_espresso'),
121
-                EEM_Question::QST_type_textarea      => esc_html__('Textarea', 'event_espresso'),
122
-                EEM_Question::QST_type_url           => esc_html__('URL', 'event_espresso'),
123
-                EEM_Question::QST_type_us_phone      => esc_html__('USA - Format Phone', 'event_espresso'),
124
-                EEM_Question::QST_type_year          => esc_html__('Year', 'event_espresso'),
125
-            ]
126
-        );
127
-        $this->_question_descriptions         = (array) apply_filters(
128
-            'FHEE__EEM_Question__construct__question_descriptions',
129
-            [
130
-                EEM_Question::QST_type_checkbox      => esc_html__(
131
-                    'Allows multiple preset options to be selected',
132
-                    'event_espresso'
133
-                ),
134
-                EEM_Question::QST_type_country       => esc_html__(
135
-                    'A dropdown that lists countries',
136
-                    'event_espresso'
137
-                ),
138
-                EEM_Question::QST_type_date          => esc_html__(
139
-                    'A popup calendar that allows date selections',
140
-                    'event_espresso'
141
-                ),
142
-                EEM_Question::QST_type_decimal       => esc_html__(
143
-                    'A text field that allows number values with decimals',
144
-                    'event_espresso'
145
-                ),
146
-                EEM_Question::QST_type_dropdown      => esc_html__(
147
-                    'A dropdown that allows a single selection',
148
-                    'event_espresso'
149
-                ),
150
-                EEM_Question::QST_type_email         => esc_html__(
151
-                    'A text field that must contain a valid Email address',
152
-                    'event_espresso'
153
-                ),
154
-                EEM_Question::QST_type_email_confirm => esc_html__(
155
-                    'A text field that must contain a valid Email address and be equal to Email field',
156
-                    'event_espresso'
157
-                ),
158
-                EEM_Question::QST_type_html_textarea => esc_html__(
159
-                    'A multi line text input field that allows HTML',
160
-                    'event_espresso'
161
-                ),
162
-                EEM_Question::QST_type_int           => esc_html__(
163
-                    'A text field that only allows whole numbers (no decimals)',
164
-                    'event_espresso'
165
-                ),
166
-                EEM_Question::QST_type_multi_select  => esc_html__(
167
-                    'A dropdown that allows multiple selections',
168
-                    'event_espresso'
169
-                ),
170
-                EEM_Question::QST_type_radio         => esc_html__(
171
-                    'Allows a single preset option to be selected',
172
-                    'event_espresso'
173
-                ),
174
-                EEM_Question::QST_type_state         => esc_html__(
175
-                    'A dropdown that lists states/provinces',
176
-                    'event_espresso'
177
-                ),
178
-                EEM_Question::QST_type_text          => esc_html__(
179
-                    'A single line text input field',
180
-                    'event_espresso'
181
-                ),
182
-                EEM_Question::QST_type_textarea      => esc_html__(
183
-                    'A multi line text input field',
184
-                    'event_espresso'
185
-                ),
186
-                EEM_Question::QST_type_url           => esc_html__(
187
-                    'A text field that must contain a valid URL',
188
-                    'event_espresso'
189
-                ),
190
-                EEM_Question::QST_type_us_phone      => esc_html__(
191
-                    'A text field that must contain a valid US phone number',
192
-                    'event_espresso'
193
-                ),
194
-                EEM_Question::QST_type_year          => esc_html__(
195
-                    'A dropdown that lists the last 100 years',
196
-                    'event_espresso'
197
-                ),
198
-            ]
199
-        );
200
-        $this->_question_type_categories      = (array) apply_filters(
201
-            'FHEE__EEM_Question__construct__question_type_categories',
202
-            [
203
-                'text'               => [
204
-                    EEM_Question::QST_type_date,
205
-                    EEM_Question::QST_type_decimal,
206
-                    EEM_Question::QST_type_email,
207
-                    EEM_Question::QST_type_email_confirm,
208
-                    EEM_Question::QST_type_html_textarea,
209
-                    EEM_Question::QST_type_int,
210
-                    EEM_Question::QST_type_text,
211
-                    EEM_Question::QST_type_textarea,
212
-                    EEM_Question::QST_type_url,
213
-                    EEM_Question::QST_type_us_phone,
214
-                    EEM_Question::QST_type_year,
215
-                ],
216
-                'single-answer-enum' => [
217
-                    EEM_Question::QST_type_dropdown,
218
-                    EEM_Question::QST_type_radio,
219
-                ],
220
-                'multi-answer-enum'  => [
221
-                    EEM_Question::QST_type_multi_select,
222
-                    EEM_Question::QST_type_checkbox,
223
-                ],
224
-            ]
225
-        );
226
-        $this->question_types_with_max_length = (array) apply_filters(
227
-            'FHEE__EEM_Question___construct__question_types_with_max_length',
228
-            [
229
-                EEM_Question::QST_type_html_textarea,
230
-                EEM_Question::QST_type_text,
231
-                EEM_Question::QST_type_textarea,
232
-            ]
233
-        );
234
-
235
-        $this->_tables          = [
236
-            'Question' => new EE_Primary_Table('esp_question', 'QST_ID'),
237
-        ];
238
-        $this->_fields          = [
239
-            'Question' => [
240
-                'QST_ID'            => new EE_Primary_Key_Int_Field(
241
-                    'QST_ID',
242
-                    esc_html__('Question ID', 'event_espresso')
243
-                ),
244
-                'QST_admin_label'   => new EE_Plain_Text_Field(
245
-                    'QST_admin_label',
246
-                    esc_html__('Question Label (admin-only)', 'event_espresso'),
247
-                    true,
248
-                    ''
249
-                ),
250
-                'QST_admin_only'    => new EE_Boolean_Field(
251
-                    'QST_admin_only',
252
-                    esc_html__('Admin-Only Question?', 'event_espresso'),
253
-                    false,
254
-                    false
255
-                ),
256
-                'QST_deleted'       => new EE_Trashed_Flag_Field(
257
-                    'QST_deleted',
258
-                    esc_html__('Flag Indicating question was deleted', 'event_espresso'),
259
-                    false,
260
-                    false
261
-                ),
262
-                'QST_display_text'  => new EE_Post_Content_Field(
263
-                    'QST_display_text',
264
-                    esc_html__('Question Text', 'event_espresso'),
265
-                    true,
266
-                    ''
267
-                ),
268
-                'QST_max'           => new EE_Infinite_Integer_Field(
269
-                    'QST_max',
270
-                    esc_html__('Max Size', 'event_espresso'),
271
-                    false,
272
-                    EE_INF
273
-                ),
274
-                'QST_order'         => new EE_Integer_Field(
275
-                    'QST_order',
276
-                    esc_html__('Question Order', 'event_espresso'),
277
-                    false,
278
-                    0
279
-                ),
280
-                'QST_required'      => new EE_Boolean_Field(
281
-                    'QST_required',
282
-                    esc_html__('Required Question?', 'event_espresso'),
283
-                    false,
284
-                    false
285
-                ),
286
-                'QST_required_text' => new EE_Simple_HTML_Field(
287
-                    'QST_required_text',
288
-                    esc_html__('Text to Display if Not Provided', 'event_espresso'),
289
-                    true,
290
-                    ''
291
-                ),
292
-                'QST_system'        => new EE_Plain_Text_Field(
293
-                    'QST_system',
294
-                    esc_html__('Internal string ID for question', 'event_espresso'),
295
-                    false,
296
-                    ''
297
-                ),
298
-                'QST_type'          => new EE_Enum_Text_Field(
299
-                    'QST_type',
300
-                    esc_html__('Question Type', 'event_espresso'),
301
-                    false,
302
-                    'TEXT',
303
-                    $this->_allowed_question_types
304
-                ),
305
-                'QST_wp_user'       => new EE_WP_User_Field(
306
-                    'QST_wp_user',
307
-                    esc_html__('Question Creator ID', 'event_espresso'),
308
-                    false
309
-                ),
310
-            ],
311
-        ];
312
-        $this->_model_relations = [
313
-            'Answer'                  => new EE_Has_Many_Relation(),
314
-            'Question_Group'          => new EE_HABTM_Relation('Question_Group_Question'),
315
-            // for QST_order column
316
-            'Question_Group_Question' => new EE_Has_Many_Relation(),
317
-            'Question_Option'         => new EE_Has_Many_Relation(),
318
-            'WP_User'                 => new EE_Belongs_To_Relation(),
319
-        ];
320
-        // this model is generally available for reading
321
-        $this->_cap_restriction_generators[ EEM_Base::caps_read ]       =
322
-            new EE_Restriction_Generator_Public();
323
-        $this->_cap_restriction_generators[ EEM_Base::caps_read_admin ] =
324
-            new EE_Restriction_Generator_Reg_Form('QST_system');
325
-        $this->_cap_restriction_generators[ EEM_Base::caps_edit ]       =
326
-            new EE_Restriction_Generator_Reg_Form('QST_system');
327
-        $this->_cap_restriction_generators[ EEM_Base::caps_delete ]     =
328
-            new EE_Restriction_Generator_Reg_Form('QST_system');
329
-
330
-        parent::__construct($timezone);
331
-    }
332
-
333
-
334
-    /**
335
-     * Returns the list of allowed question types, which are normally:
336
-     * 'TEXT','TEXTAREA','RADIO_BTN','DROPDOWN','CHECKBOX','DATE' but they can be extended
337
-     *
338
-     * @return string[]
339
-     */
340
-    public function allowed_question_types(): array
341
-    {
342
-        return $this->_allowed_question_types;
343
-    }
344
-
345
-
346
-    /**
347
-     * Gets all the question types in the same category
348
-     *
349
-     * @param string $question_type one of EEM_Question::allowed_question_types(
350
-     * @return string[] like EEM_Question::allowed_question_types()
351
-     */
352
-    public function question_types_in_same_category(string $question_type): array
353
-    {
354
-        $question_types = [$question_type];
355
-        foreach ($this->_question_type_categories as $category => $question_types_in_category) {
356
-            if (in_array($question_type, $question_types_in_category)) {
357
-                $question_types = $question_types_in_category;
358
-                break;
359
-            }
360
-        }
361
-
362
-        return array_intersect_key($this->allowed_question_types(), array_flip($question_types));
363
-    }
364
-
365
-
366
-    /**
367
-     * Determines if the given question type is in the given question type category
368
-     *
369
-     * @param string $question_type one of EEM_Question::allowed_question_types()
370
-     * @param string $category      one of the top-level keys of EEM_Question::question_type_categories()
371
-     * @return boolean
372
-     */
373
-    public function question_type_is_in_category(string $question_type, string $category): bool
374
-    {
375
-        if (! isset($this->_question_type_categories[ $category ])) {
376
-            return false;
377
-        }
378
-        return in_array($question_type, $this->_question_type_categories[ $category ]);
379
-    }
380
-
381
-
382
-    /**
383
-     * Returns all the question types in the given category
384
-     *
385
-     * @param string $category
386
-     * @return array|mixed
387
-     */
388
-    public function question_types_in_category(string $category): array
389
-    {
390
-        if (isset($this->_question_type_categories[ $category ])) {
391
-            return $this->_question_type_categories[ $category ];
392
-        }
393
-        return [];
394
-    }
395
-
396
-
397
-    /**
398
-     * Returns all the question types that should have question options
399
-     *
400
-     * @return array
401
-     */
402
-    public function question_types_with_options(): array
403
-    {
404
-        return array_merge(
405
-            $this->question_types_in_category('single-answer-enum'),
406
-            $this->question_types_in_category('multi-answer-enum')
407
-        );
408
-    }
409
-
410
-
411
-    /**
412
-     * Returns the question type categories 2d array
413
-     *
414
-     * @return array see EEM_Question::_question_type_categories
415
-     */
416
-    public function question_type_categories(): array
417
-    {
418
-        return $this->_question_type_categories;
419
-    }
420
-
421
-
422
-    /**
423
-     * Returns an array of all the QST_system values that can be allowed in the system question group
424
-     * identified by $system_question_group_id
425
-     *
426
-     * @param string $system_question_group_id QSG_system
427
-     * @return array of system question names (QST_system)
428
-     */
429
-    public function allowed_system_questions_in_system_question_group(string $system_question_group_id): array
430
-    {
431
-        $question_system_ids = [];
432
-        switch ($system_question_group_id) {
433
-            case EEM_Question_Group::system_personal:
434
-                $question_system_ids = [
435
-                    EEM_Attendee::system_question_fname,
436
-                    EEM_Attendee::system_question_lname,
437
-                    EEM_Attendee::system_question_email,
438
-                    EEM_Attendee::system_question_email_confirm,
439
-                    EEM_Attendee::system_question_phone,
440
-                ];
441
-                break;
442
-            case EEM_Question_Group::system_address:
443
-                $question_system_ids = [
444
-                    EEM_Attendee::system_question_address,
445
-                    EEM_Attendee::system_question_address2,
446
-                    EEM_Attendee::system_question_city,
447
-                    EEM_Attendee::system_question_state,
448
-                    EEM_Attendee::system_question_country,
449
-                    EEM_Attendee::system_question_zip,
450
-                    EEM_Attendee::system_question_phone,
451
-                ];
452
-                break;
453
-        }
454
-        return apply_filters(
455
-            'FHEE__EEM_Question__system_questions_allowed_in_system_question_group__return',
456
-            $question_system_ids,
457
-            $system_question_group_id
458
-        );
459
-    }
460
-
461
-
462
-    /**
463
-     * Returns an array of all the QST_system values that are required in the system question group
464
-     * identified by $system_question_group_id
465
-     *
466
-     * @param string $system_question_group_id QSG_system
467
-     * @return array of system question names (QST_system)
468
-     */
469
-    public function required_system_questions_in_system_question_group(string $system_question_group_id): array
470
-    {
471
-        $question_system_ids = null;
472
-        switch ($system_question_group_id) {
473
-            case EEM_Question_Group::system_personal:
474
-                $question_system_ids = [
475
-                    EEM_Attendee::system_question_fname,
476
-                    EEM_Attendee::system_question_email,
477
-                ];
478
-                break;
479
-            default:
480
-                $question_system_ids = [];
481
-        }
482
-        return apply_filters(
483
-            'FHEE__EEM_Question__system_questions_required_in_system_question_group',
484
-            $question_system_ids,
485
-            $system_question_group_id
486
-        );
487
-    }
488
-
489
-
490
-    /**
491
-     * Gets an array for converting between QST_system and QST_IDs for system questions. Eg, if you want to know
492
-     * which system question QST_ID corresponds to the QST_system 'city', use
493
-     * EEM_Question::instance()->get_Question_ID_from_system_string('city');
494
-     *
495
-     * @param $QST_system
496
-     * @return int of QST_ID for the question that corresponds to that QST_system
497
-     * @throws EE_Error
498
-     */
499
-    public function get_Question_ID_from_system_string($QST_system): int
500
-    {
501
-        return $this->get_var([['QST_system' => $QST_system]]);
502
-    }
503
-
504
-
505
-    /**
506
-     * searches the db for the question with the latest question order and returns that value.
507
-     *
508
-     * @return int
509
-     * @throws EE_Error
510
-     */
511
-    public function get_latest_question_order(): int
512
-    {
513
-        $columns_to_select = [
514
-            'max_order' => ["MAX(QST_order)", "%d"],
515
-        ];
516
-        $max               = $this->_get_all_wpdb_results([], ARRAY_A, $columns_to_select);
517
-        return isset($max[0], $max[0]['max_order']) ? $max[0]['max_order'] : 0;
518
-    }
519
-
520
-
521
-    /**
522
-     * Returns an array where keys are system question QST_system values,
523
-     * and values are the highest question max the admin can set on the question
524
-     * (aka the "max max"; eg, a site admin can change the zip question to have a max
525
-     * of 5, but no larger than 12)
526
-     *
527
-     * @return array
528
-     */
529
-    public function system_question_maxes(): array
530
-    {
531
-        return [
532
-            'fname'         => 45,
533
-            'lname'         => 45,
534
-            'address'       => 255,
535
-            'address2'      => 255,
536
-            'city'          => 45,
537
-            'zip'           => 12,
538
-            'email'         => 255,
539
-            'email_confirm' => 255,
540
-            'phone'         => 45,
541
-        ];
542
-    }
543
-
544
-
545
-    /**
546
-     * Given a QST_system value, gets the question's largest allowable max input.
547
-     *
548
-     * @param string $system_question_value
549
-     * @return int|float
550
-     * @see Registration_Form_Admin_Page::system_question_maxes()
551
-     */
552
-    public function absolute_max_for_system_question(string $system_question_value)
553
-    {
554
-        $maxes = $this->system_question_maxes();
555
-        return $maxes[ $system_question_value ] ?? EE_INF;
556
-    }
557
-
558
-
559
-    /**
560
-     * @return array
561
-     */
562
-    public function question_descriptions(): array
563
-    {
564
-        return $this->_question_descriptions;
565
-    }
566
-
567
-
568
-    /**
569
-     * Returns all the question types that should have an admin-defined max input length
570
-     *
571
-     * @return array
572
-     */
573
-    public function questionTypesWithMaxLength(): array
574
-    {
575
-        return (array) $this->question_types_with_max_length;
576
-    }
12
+	// constant used to indicate that the question type is CHECKBOX
13
+	const QST_type_checkbox = 'CHECKBOX';
14
+
15
+	// constant used to indicate that the question type is COUNTRY
16
+	const QST_type_country = 'COUNTRY';
17
+
18
+	// constant used to indicate that the question type is DATE
19
+	const QST_type_date = 'DATE';
20
+
21
+	// constant used to indicate that the question type is a decimal (float)
22
+	const QST_type_decimal = 'DECIMAL';
23
+
24
+	// constant used to indicate that the question type is DROPDOWN
25
+	const QST_type_dropdown = 'DROPDOWN';
26
+
27
+	// constant used to indicate that the question type is an email input
28
+	const QST_type_email = 'EMAIL';
29
+
30
+	// constant used to indicate that the question type is an email input
31
+	const QST_type_email_confirm = 'EMAIL_CONFIRM';
32
+
33
+	// constant used to indicate that the question type is a TEXTAREA that allows simple html
34
+	const QST_type_html_textarea = 'HTML_TEXTAREA';
35
+
36
+	// constant used to indicate that the question type is an integer (whole number)
37
+	const QST_type_int = 'INTEGER';
38
+
39
+	// constant used to indicate that the question type is a multi-select
40
+	const QST_type_multi_select = 'MULTI_SELECT';
41
+
42
+	// constant used to indicate that the question type is RADIO_BTN
43
+	const QST_type_radio = 'RADIO_BTN';
44
+
45
+	// constant used to indicate that the question type is STATE
46
+	const QST_type_state = 'STATE';
47
+
48
+	// constant used to indicate that the question type is TEXT
49
+	const QST_type_text = 'TEXT';
50
+
51
+	// constant used to indicate that the question type is TEXTAREA
52
+	const QST_type_textarea = 'TEXTAREA';
53
+
54
+	// constant used to indicate that the question type is a valid URL
55
+	const QST_type_url = 'URL';
56
+
57
+	// constant used to indicate that the question type is a US-formatted phone number
58
+	const QST_type_us_phone = 'US_PHONE';
59
+
60
+	// constant used to indicate that the question type is a YEAR
61
+	const QST_type_year = 'YEAR';
62
+
63
+
64
+	protected static ?EEM_Question $_instance = null;
65
+
66
+	/**
67
+	 * lists all the question types which should be allowed. Ideally, this will be extensible.
68
+	 */
69
+	protected array $_allowed_question_types;
70
+
71
+	/**
72
+	 * brief descriptions for all the question types
73
+	 */
74
+	protected array $_question_descriptions;
75
+
76
+	/**
77
+	 * Question types that are interchangeable, even after answers have been provided for them.
78
+	 * Top-level keys are category slugs, next level is an array of question types. If question types
79
+	 * aren't in this array, it is assumed they AREN'T interchangeable with any other question types.
80
+	 *
81
+	 * @var array   $_question_type_categories {
82
+	 * @type string $text
83
+	 * @type string $single                    -answer-enum
84
+	 * @type string $multi                     -answer-enum
85
+	 *                                         }
86
+	 */
87
+	protected array $_question_type_categories;
88
+
89
+	/**
90
+	 * Question types that should have an admin-defined max input length
91
+	 */
92
+	protected array $question_types_with_max_length;
93
+
94
+
95
+	/**
96
+	 * EEM_Question constructor.
97
+	 *
98
+	 * @param string|null $timezone
99
+	 * @throws EE_Error
100
+	 */
101
+	protected function __construct(?string $timezone = '')
102
+	{
103
+		$this->singular_item                  = esc_html__('Question', 'event_espresso');
104
+		$this->plural_item                    = esc_html__('Questions', 'event_espresso');
105
+		$this->_allowed_question_types        = (array) apply_filters(
106
+			'FHEE__EEM_Question__construct__allowed_question_types',
107
+			[
108
+				EEM_Question::QST_type_checkbox      => esc_html__('Checkboxes', 'event_espresso'),
109
+				EEM_Question::QST_type_country       => esc_html__('Country Dropdown', 'event_espresso'),
110
+				EEM_Question::QST_type_date          => esc_html__('Date Picker', 'event_espresso'),
111
+				EEM_Question::QST_type_decimal       => esc_html__('Number', 'event_espresso'),
112
+				EEM_Question::QST_type_dropdown      => esc_html__('Dropdown', 'event_espresso'),
113
+				EEM_Question::QST_type_email         => esc_html__('Email', 'event_espresso'),
114
+				EEM_Question::QST_type_email_confirm => esc_html__('Confirm Email', 'event_espresso'),
115
+				EEM_Question::QST_type_html_textarea => esc_html__('HTML Textarea', 'event_espresso'),
116
+				EEM_Question::QST_type_int           => esc_html__('Whole Number', 'event_espresso'),
117
+				EEM_Question::QST_type_multi_select  => esc_html__('Multi Select', 'event_espresso'),
118
+				EEM_Question::QST_type_radio         => esc_html__('Radio Buttons', 'event_espresso'),
119
+				EEM_Question::QST_type_state         => esc_html__('State/Province Dropdown', 'event_espresso'),
120
+				EEM_Question::QST_type_text          => esc_html__('Text', 'event_espresso'),
121
+				EEM_Question::QST_type_textarea      => esc_html__('Textarea', 'event_espresso'),
122
+				EEM_Question::QST_type_url           => esc_html__('URL', 'event_espresso'),
123
+				EEM_Question::QST_type_us_phone      => esc_html__('USA - Format Phone', 'event_espresso'),
124
+				EEM_Question::QST_type_year          => esc_html__('Year', 'event_espresso'),
125
+			]
126
+		);
127
+		$this->_question_descriptions         = (array) apply_filters(
128
+			'FHEE__EEM_Question__construct__question_descriptions',
129
+			[
130
+				EEM_Question::QST_type_checkbox      => esc_html__(
131
+					'Allows multiple preset options to be selected',
132
+					'event_espresso'
133
+				),
134
+				EEM_Question::QST_type_country       => esc_html__(
135
+					'A dropdown that lists countries',
136
+					'event_espresso'
137
+				),
138
+				EEM_Question::QST_type_date          => esc_html__(
139
+					'A popup calendar that allows date selections',
140
+					'event_espresso'
141
+				),
142
+				EEM_Question::QST_type_decimal       => esc_html__(
143
+					'A text field that allows number values with decimals',
144
+					'event_espresso'
145
+				),
146
+				EEM_Question::QST_type_dropdown      => esc_html__(
147
+					'A dropdown that allows a single selection',
148
+					'event_espresso'
149
+				),
150
+				EEM_Question::QST_type_email         => esc_html__(
151
+					'A text field that must contain a valid Email address',
152
+					'event_espresso'
153
+				),
154
+				EEM_Question::QST_type_email_confirm => esc_html__(
155
+					'A text field that must contain a valid Email address and be equal to Email field',
156
+					'event_espresso'
157
+				),
158
+				EEM_Question::QST_type_html_textarea => esc_html__(
159
+					'A multi line text input field that allows HTML',
160
+					'event_espresso'
161
+				),
162
+				EEM_Question::QST_type_int           => esc_html__(
163
+					'A text field that only allows whole numbers (no decimals)',
164
+					'event_espresso'
165
+				),
166
+				EEM_Question::QST_type_multi_select  => esc_html__(
167
+					'A dropdown that allows multiple selections',
168
+					'event_espresso'
169
+				),
170
+				EEM_Question::QST_type_radio         => esc_html__(
171
+					'Allows a single preset option to be selected',
172
+					'event_espresso'
173
+				),
174
+				EEM_Question::QST_type_state         => esc_html__(
175
+					'A dropdown that lists states/provinces',
176
+					'event_espresso'
177
+				),
178
+				EEM_Question::QST_type_text          => esc_html__(
179
+					'A single line text input field',
180
+					'event_espresso'
181
+				),
182
+				EEM_Question::QST_type_textarea      => esc_html__(
183
+					'A multi line text input field',
184
+					'event_espresso'
185
+				),
186
+				EEM_Question::QST_type_url           => esc_html__(
187
+					'A text field that must contain a valid URL',
188
+					'event_espresso'
189
+				),
190
+				EEM_Question::QST_type_us_phone      => esc_html__(
191
+					'A text field that must contain a valid US phone number',
192
+					'event_espresso'
193
+				),
194
+				EEM_Question::QST_type_year          => esc_html__(
195
+					'A dropdown that lists the last 100 years',
196
+					'event_espresso'
197
+				),
198
+			]
199
+		);
200
+		$this->_question_type_categories      = (array) apply_filters(
201
+			'FHEE__EEM_Question__construct__question_type_categories',
202
+			[
203
+				'text'               => [
204
+					EEM_Question::QST_type_date,
205
+					EEM_Question::QST_type_decimal,
206
+					EEM_Question::QST_type_email,
207
+					EEM_Question::QST_type_email_confirm,
208
+					EEM_Question::QST_type_html_textarea,
209
+					EEM_Question::QST_type_int,
210
+					EEM_Question::QST_type_text,
211
+					EEM_Question::QST_type_textarea,
212
+					EEM_Question::QST_type_url,
213
+					EEM_Question::QST_type_us_phone,
214
+					EEM_Question::QST_type_year,
215
+				],
216
+				'single-answer-enum' => [
217
+					EEM_Question::QST_type_dropdown,
218
+					EEM_Question::QST_type_radio,
219
+				],
220
+				'multi-answer-enum'  => [
221
+					EEM_Question::QST_type_multi_select,
222
+					EEM_Question::QST_type_checkbox,
223
+				],
224
+			]
225
+		);
226
+		$this->question_types_with_max_length = (array) apply_filters(
227
+			'FHEE__EEM_Question___construct__question_types_with_max_length',
228
+			[
229
+				EEM_Question::QST_type_html_textarea,
230
+				EEM_Question::QST_type_text,
231
+				EEM_Question::QST_type_textarea,
232
+			]
233
+		);
234
+
235
+		$this->_tables          = [
236
+			'Question' => new EE_Primary_Table('esp_question', 'QST_ID'),
237
+		];
238
+		$this->_fields          = [
239
+			'Question' => [
240
+				'QST_ID'            => new EE_Primary_Key_Int_Field(
241
+					'QST_ID',
242
+					esc_html__('Question ID', 'event_espresso')
243
+				),
244
+				'QST_admin_label'   => new EE_Plain_Text_Field(
245
+					'QST_admin_label',
246
+					esc_html__('Question Label (admin-only)', 'event_espresso'),
247
+					true,
248
+					''
249
+				),
250
+				'QST_admin_only'    => new EE_Boolean_Field(
251
+					'QST_admin_only',
252
+					esc_html__('Admin-Only Question?', 'event_espresso'),
253
+					false,
254
+					false
255
+				),
256
+				'QST_deleted'       => new EE_Trashed_Flag_Field(
257
+					'QST_deleted',
258
+					esc_html__('Flag Indicating question was deleted', 'event_espresso'),
259
+					false,
260
+					false
261
+				),
262
+				'QST_display_text'  => new EE_Post_Content_Field(
263
+					'QST_display_text',
264
+					esc_html__('Question Text', 'event_espresso'),
265
+					true,
266
+					''
267
+				),
268
+				'QST_max'           => new EE_Infinite_Integer_Field(
269
+					'QST_max',
270
+					esc_html__('Max Size', 'event_espresso'),
271
+					false,
272
+					EE_INF
273
+				),
274
+				'QST_order'         => new EE_Integer_Field(
275
+					'QST_order',
276
+					esc_html__('Question Order', 'event_espresso'),
277
+					false,
278
+					0
279
+				),
280
+				'QST_required'      => new EE_Boolean_Field(
281
+					'QST_required',
282
+					esc_html__('Required Question?', 'event_espresso'),
283
+					false,
284
+					false
285
+				),
286
+				'QST_required_text' => new EE_Simple_HTML_Field(
287
+					'QST_required_text',
288
+					esc_html__('Text to Display if Not Provided', 'event_espresso'),
289
+					true,
290
+					''
291
+				),
292
+				'QST_system'        => new EE_Plain_Text_Field(
293
+					'QST_system',
294
+					esc_html__('Internal string ID for question', 'event_espresso'),
295
+					false,
296
+					''
297
+				),
298
+				'QST_type'          => new EE_Enum_Text_Field(
299
+					'QST_type',
300
+					esc_html__('Question Type', 'event_espresso'),
301
+					false,
302
+					'TEXT',
303
+					$this->_allowed_question_types
304
+				),
305
+				'QST_wp_user'       => new EE_WP_User_Field(
306
+					'QST_wp_user',
307
+					esc_html__('Question Creator ID', 'event_espresso'),
308
+					false
309
+				),
310
+			],
311
+		];
312
+		$this->_model_relations = [
313
+			'Answer'                  => new EE_Has_Many_Relation(),
314
+			'Question_Group'          => new EE_HABTM_Relation('Question_Group_Question'),
315
+			// for QST_order column
316
+			'Question_Group_Question' => new EE_Has_Many_Relation(),
317
+			'Question_Option'         => new EE_Has_Many_Relation(),
318
+			'WP_User'                 => new EE_Belongs_To_Relation(),
319
+		];
320
+		// this model is generally available for reading
321
+		$this->_cap_restriction_generators[ EEM_Base::caps_read ]       =
322
+			new EE_Restriction_Generator_Public();
323
+		$this->_cap_restriction_generators[ EEM_Base::caps_read_admin ] =
324
+			new EE_Restriction_Generator_Reg_Form('QST_system');
325
+		$this->_cap_restriction_generators[ EEM_Base::caps_edit ]       =
326
+			new EE_Restriction_Generator_Reg_Form('QST_system');
327
+		$this->_cap_restriction_generators[ EEM_Base::caps_delete ]     =
328
+			new EE_Restriction_Generator_Reg_Form('QST_system');
329
+
330
+		parent::__construct($timezone);
331
+	}
332
+
333
+
334
+	/**
335
+	 * Returns the list of allowed question types, which are normally:
336
+	 * 'TEXT','TEXTAREA','RADIO_BTN','DROPDOWN','CHECKBOX','DATE' but they can be extended
337
+	 *
338
+	 * @return string[]
339
+	 */
340
+	public function allowed_question_types(): array
341
+	{
342
+		return $this->_allowed_question_types;
343
+	}
344
+
345
+
346
+	/**
347
+	 * Gets all the question types in the same category
348
+	 *
349
+	 * @param string $question_type one of EEM_Question::allowed_question_types(
350
+	 * @return string[] like EEM_Question::allowed_question_types()
351
+	 */
352
+	public function question_types_in_same_category(string $question_type): array
353
+	{
354
+		$question_types = [$question_type];
355
+		foreach ($this->_question_type_categories as $category => $question_types_in_category) {
356
+			if (in_array($question_type, $question_types_in_category)) {
357
+				$question_types = $question_types_in_category;
358
+				break;
359
+			}
360
+		}
361
+
362
+		return array_intersect_key($this->allowed_question_types(), array_flip($question_types));
363
+	}
364
+
365
+
366
+	/**
367
+	 * Determines if the given question type is in the given question type category
368
+	 *
369
+	 * @param string $question_type one of EEM_Question::allowed_question_types()
370
+	 * @param string $category      one of the top-level keys of EEM_Question::question_type_categories()
371
+	 * @return boolean
372
+	 */
373
+	public function question_type_is_in_category(string $question_type, string $category): bool
374
+	{
375
+		if (! isset($this->_question_type_categories[ $category ])) {
376
+			return false;
377
+		}
378
+		return in_array($question_type, $this->_question_type_categories[ $category ]);
379
+	}
380
+
381
+
382
+	/**
383
+	 * Returns all the question types in the given category
384
+	 *
385
+	 * @param string $category
386
+	 * @return array|mixed
387
+	 */
388
+	public function question_types_in_category(string $category): array
389
+	{
390
+		if (isset($this->_question_type_categories[ $category ])) {
391
+			return $this->_question_type_categories[ $category ];
392
+		}
393
+		return [];
394
+	}
395
+
396
+
397
+	/**
398
+	 * Returns all the question types that should have question options
399
+	 *
400
+	 * @return array
401
+	 */
402
+	public function question_types_with_options(): array
403
+	{
404
+		return array_merge(
405
+			$this->question_types_in_category('single-answer-enum'),
406
+			$this->question_types_in_category('multi-answer-enum')
407
+		);
408
+	}
409
+
410
+
411
+	/**
412
+	 * Returns the question type categories 2d array
413
+	 *
414
+	 * @return array see EEM_Question::_question_type_categories
415
+	 */
416
+	public function question_type_categories(): array
417
+	{
418
+		return $this->_question_type_categories;
419
+	}
420
+
421
+
422
+	/**
423
+	 * Returns an array of all the QST_system values that can be allowed in the system question group
424
+	 * identified by $system_question_group_id
425
+	 *
426
+	 * @param string $system_question_group_id QSG_system
427
+	 * @return array of system question names (QST_system)
428
+	 */
429
+	public function allowed_system_questions_in_system_question_group(string $system_question_group_id): array
430
+	{
431
+		$question_system_ids = [];
432
+		switch ($system_question_group_id) {
433
+			case EEM_Question_Group::system_personal:
434
+				$question_system_ids = [
435
+					EEM_Attendee::system_question_fname,
436
+					EEM_Attendee::system_question_lname,
437
+					EEM_Attendee::system_question_email,
438
+					EEM_Attendee::system_question_email_confirm,
439
+					EEM_Attendee::system_question_phone,
440
+				];
441
+				break;
442
+			case EEM_Question_Group::system_address:
443
+				$question_system_ids = [
444
+					EEM_Attendee::system_question_address,
445
+					EEM_Attendee::system_question_address2,
446
+					EEM_Attendee::system_question_city,
447
+					EEM_Attendee::system_question_state,
448
+					EEM_Attendee::system_question_country,
449
+					EEM_Attendee::system_question_zip,
450
+					EEM_Attendee::system_question_phone,
451
+				];
452
+				break;
453
+		}
454
+		return apply_filters(
455
+			'FHEE__EEM_Question__system_questions_allowed_in_system_question_group__return',
456
+			$question_system_ids,
457
+			$system_question_group_id
458
+		);
459
+	}
460
+
461
+
462
+	/**
463
+	 * Returns an array of all the QST_system values that are required in the system question group
464
+	 * identified by $system_question_group_id
465
+	 *
466
+	 * @param string $system_question_group_id QSG_system
467
+	 * @return array of system question names (QST_system)
468
+	 */
469
+	public function required_system_questions_in_system_question_group(string $system_question_group_id): array
470
+	{
471
+		$question_system_ids = null;
472
+		switch ($system_question_group_id) {
473
+			case EEM_Question_Group::system_personal:
474
+				$question_system_ids = [
475
+					EEM_Attendee::system_question_fname,
476
+					EEM_Attendee::system_question_email,
477
+				];
478
+				break;
479
+			default:
480
+				$question_system_ids = [];
481
+		}
482
+		return apply_filters(
483
+			'FHEE__EEM_Question__system_questions_required_in_system_question_group',
484
+			$question_system_ids,
485
+			$system_question_group_id
486
+		);
487
+	}
488
+
489
+
490
+	/**
491
+	 * Gets an array for converting between QST_system and QST_IDs for system questions. Eg, if you want to know
492
+	 * which system question QST_ID corresponds to the QST_system 'city', use
493
+	 * EEM_Question::instance()->get_Question_ID_from_system_string('city');
494
+	 *
495
+	 * @param $QST_system
496
+	 * @return int of QST_ID for the question that corresponds to that QST_system
497
+	 * @throws EE_Error
498
+	 */
499
+	public function get_Question_ID_from_system_string($QST_system): int
500
+	{
501
+		return $this->get_var([['QST_system' => $QST_system]]);
502
+	}
503
+
504
+
505
+	/**
506
+	 * searches the db for the question with the latest question order and returns that value.
507
+	 *
508
+	 * @return int
509
+	 * @throws EE_Error
510
+	 */
511
+	public function get_latest_question_order(): int
512
+	{
513
+		$columns_to_select = [
514
+			'max_order' => ["MAX(QST_order)", "%d"],
515
+		];
516
+		$max               = $this->_get_all_wpdb_results([], ARRAY_A, $columns_to_select);
517
+		return isset($max[0], $max[0]['max_order']) ? $max[0]['max_order'] : 0;
518
+	}
519
+
520
+
521
+	/**
522
+	 * Returns an array where keys are system question QST_system values,
523
+	 * and values are the highest question max the admin can set on the question
524
+	 * (aka the "max max"; eg, a site admin can change the zip question to have a max
525
+	 * of 5, but no larger than 12)
526
+	 *
527
+	 * @return array
528
+	 */
529
+	public function system_question_maxes(): array
530
+	{
531
+		return [
532
+			'fname'         => 45,
533
+			'lname'         => 45,
534
+			'address'       => 255,
535
+			'address2'      => 255,
536
+			'city'          => 45,
537
+			'zip'           => 12,
538
+			'email'         => 255,
539
+			'email_confirm' => 255,
540
+			'phone'         => 45,
541
+		];
542
+	}
543
+
544
+
545
+	/**
546
+	 * Given a QST_system value, gets the question's largest allowable max input.
547
+	 *
548
+	 * @param string $system_question_value
549
+	 * @return int|float
550
+	 * @see Registration_Form_Admin_Page::system_question_maxes()
551
+	 */
552
+	public function absolute_max_for_system_question(string $system_question_value)
553
+	{
554
+		$maxes = $this->system_question_maxes();
555
+		return $maxes[ $system_question_value ] ?? EE_INF;
556
+	}
557
+
558
+
559
+	/**
560
+	 * @return array
561
+	 */
562
+	public function question_descriptions(): array
563
+	{
564
+		return $this->_question_descriptions;
565
+	}
566
+
567
+
568
+	/**
569
+	 * Returns all the question types that should have an admin-defined max input length
570
+	 *
571
+	 * @return array
572
+	 */
573
+	public function questionTypesWithMaxLength(): array
574
+	{
575
+		return (array) $this->question_types_with_max_length;
576
+	}
577 577
 }
Please login to merge, or discard this patch.
Spacing   +11 added lines, -11 removed lines patch added patch discarded remove patch
@@ -124,7 +124,7 @@  discard block
 block discarded – undo
124 124
                 EEM_Question::QST_type_year          => esc_html__('Year', 'event_espresso'),
125 125
             ]
126 126
         );
127
-        $this->_question_descriptions         = (array) apply_filters(
127
+        $this->_question_descriptions = (array) apply_filters(
128 128
             'FHEE__EEM_Question__construct__question_descriptions',
129 129
             [
130 130
                 EEM_Question::QST_type_checkbox      => esc_html__(
@@ -197,7 +197,7 @@  discard block
 block discarded – undo
197 197
                 ),
198 198
             ]
199 199
         );
200
-        $this->_question_type_categories      = (array) apply_filters(
200
+        $this->_question_type_categories = (array) apply_filters(
201 201
             'FHEE__EEM_Question__construct__question_type_categories',
202 202
             [
203 203
                 'text'               => [
@@ -318,13 +318,13 @@  discard block
 block discarded – undo
318 318
             'WP_User'                 => new EE_Belongs_To_Relation(),
319 319
         ];
320 320
         // this model is generally available for reading
321
-        $this->_cap_restriction_generators[ EEM_Base::caps_read ]       =
321
+        $this->_cap_restriction_generators[EEM_Base::caps_read]       =
322 322
             new EE_Restriction_Generator_Public();
323
-        $this->_cap_restriction_generators[ EEM_Base::caps_read_admin ] =
323
+        $this->_cap_restriction_generators[EEM_Base::caps_read_admin] =
324 324
             new EE_Restriction_Generator_Reg_Form('QST_system');
325
-        $this->_cap_restriction_generators[ EEM_Base::caps_edit ]       =
325
+        $this->_cap_restriction_generators[EEM_Base::caps_edit]       =
326 326
             new EE_Restriction_Generator_Reg_Form('QST_system');
327
-        $this->_cap_restriction_generators[ EEM_Base::caps_delete ]     =
327
+        $this->_cap_restriction_generators[EEM_Base::caps_delete]     =
328 328
             new EE_Restriction_Generator_Reg_Form('QST_system');
329 329
 
330 330
         parent::__construct($timezone);
@@ -372,10 +372,10 @@  discard block
 block discarded – undo
372 372
      */
373 373
     public function question_type_is_in_category(string $question_type, string $category): bool
374 374
     {
375
-        if (! isset($this->_question_type_categories[ $category ])) {
375
+        if ( ! isset($this->_question_type_categories[$category])) {
376 376
             return false;
377 377
         }
378
-        return in_array($question_type, $this->_question_type_categories[ $category ]);
378
+        return in_array($question_type, $this->_question_type_categories[$category]);
379 379
     }
380 380
 
381 381
 
@@ -387,8 +387,8 @@  discard block
 block discarded – undo
387 387
      */
388 388
     public function question_types_in_category(string $category): array
389 389
     {
390
-        if (isset($this->_question_type_categories[ $category ])) {
391
-            return $this->_question_type_categories[ $category ];
390
+        if (isset($this->_question_type_categories[$category])) {
391
+            return $this->_question_type_categories[$category];
392 392
         }
393 393
         return [];
394 394
     }
@@ -552,7 +552,7 @@  discard block
 block discarded – undo
552 552
     public function absolute_max_for_system_question(string $system_question_value)
553 553
     {
554 554
         $maxes = $this->system_question_maxes();
555
-        return $maxes[ $system_question_value ] ?? EE_INF;
555
+        return $maxes[$system_question_value] ?? EE_INF;
556 556
     }
557 557
 
558 558
 
Please login to merge, or discard this patch.
core/db_models/EEM_Event_Question_Group.model.php 1 patch
Indentation   +103 added lines, -103 removed lines patch added patch discarded remove patch
@@ -14,116 +14,116 @@
 block discarded – undo
14 14
  */
15 15
 class EEM_Event_Question_Group extends EEM_Base
16 16
 {
17
-    /**
18
-     * Name of the field indicating an event should use the question group for the primary attendee
19
-     */
20
-    const PRIMARY = 'EQG_primary';
17
+	/**
18
+	 * Name of the field indicating an event should use the question group for the primary attendee
19
+	 */
20
+	const PRIMARY = 'EQG_primary';
21 21
 
22
-    /**
23
-     * Name of hte field indicating an event should use the question group for additional attendees
24
-     */
25
-    const ADDITIONAL = 'EQG_additional';
22
+	/**
23
+	 * Name of hte field indicating an event should use the question group for additional attendees
24
+	 */
25
+	const ADDITIONAL = 'EQG_additional';
26 26
 
27 27
 
28
-    protected static ?EEM_Event_Question_Group $_instance = null;
28
+	protected static ?EEM_Event_Question_Group $_instance = null;
29 29
 
30 30
 
31
-    /**
32
-     * @param string|null $timezone
33
-     * @throws EE_Error
34
-     */
35
-    protected function __construct(?string $timezone = '')
36
-    {
37
-        $this->singular_item    = esc_html__('Event to Question Group Link', 'event_espresso');
38
-        $this->plural_item      = esc_html__('Event to Question Group Links', 'event_espresso');
39
-        $this->_tables          = [
40
-            'Event_Question_Group' => new EE_Primary_Table('esp_event_question_group', 'EQG_ID'),
41
-        ];
42
-        $this->_fields          = [
43
-            'Event_Question_Group' => [
44
-                'EQG_ID'         => new EE_Primary_Key_Int_Field(
45
-                    'EQG_ID',
46
-                    esc_html__('Event to Question Group Link ID', 'event_espresso')
47
-                ),
48
-                'EVT_ID'         => new EE_Foreign_Key_Int_Field(
49
-                    'EVT_ID',
50
-                    esc_html__('Event ID', 'event_espresso'),
51
-                    false,
52
-                    0,
53
-                    'Event'
54
-                ),
55
-                'QSG_ID'         => new EE_Foreign_Key_Int_Field(
56
-                    'QSG_ID',
57
-                    esc_html__('Question Group Id', 'event_espresso'),
58
-                    false,
59
-                    0,
60
-                    'Question_Group'
61
-                ),
62
-                'EQG_additional' => new EE_Boolean_Field(
63
-                    'EQG_additional',
64
-                    esc_html__(
65
-                        'Flag indicating question is only for additional attendees',
66
-                        'event_espresso'
67
-                    ),
68
-                    false,
69
-                    false
70
-                ),
71
-                'EQG_primary'    => new EE_Boolean_Field(
72
-                    'EQG_primary',
73
-                    esc_html__(
74
-                        'Flag indicating question is only for primary attendees',
75
-                        'event_espresso'
76
-                    ),
77
-                    false,
78
-                    false
79
-                ),
80
-            ],
81
-        ];
82
-        $this->_model_relations = [
83
-            'Event'          => new EE_Belongs_To_Relation(),
84
-            'Question_Group' => new EE_Belongs_To_Relation(),
85
-        ];
31
+	/**
32
+	 * @param string|null $timezone
33
+	 * @throws EE_Error
34
+	 */
35
+	protected function __construct(?string $timezone = '')
36
+	{
37
+		$this->singular_item    = esc_html__('Event to Question Group Link', 'event_espresso');
38
+		$this->plural_item      = esc_html__('Event to Question Group Links', 'event_espresso');
39
+		$this->_tables          = [
40
+			'Event_Question_Group' => new EE_Primary_Table('esp_event_question_group', 'EQG_ID'),
41
+		];
42
+		$this->_fields          = [
43
+			'Event_Question_Group' => [
44
+				'EQG_ID'         => new EE_Primary_Key_Int_Field(
45
+					'EQG_ID',
46
+					esc_html__('Event to Question Group Link ID', 'event_espresso')
47
+				),
48
+				'EVT_ID'         => new EE_Foreign_Key_Int_Field(
49
+					'EVT_ID',
50
+					esc_html__('Event ID', 'event_espresso'),
51
+					false,
52
+					0,
53
+					'Event'
54
+				),
55
+				'QSG_ID'         => new EE_Foreign_Key_Int_Field(
56
+					'QSG_ID',
57
+					esc_html__('Question Group Id', 'event_espresso'),
58
+					false,
59
+					0,
60
+					'Question_Group'
61
+				),
62
+				'EQG_additional' => new EE_Boolean_Field(
63
+					'EQG_additional',
64
+					esc_html__(
65
+						'Flag indicating question is only for additional attendees',
66
+						'event_espresso'
67
+					),
68
+					false,
69
+					false
70
+				),
71
+				'EQG_primary'    => new EE_Boolean_Field(
72
+					'EQG_primary',
73
+					esc_html__(
74
+						'Flag indicating question is only for primary attendees',
75
+						'event_espresso'
76
+					),
77
+					false,
78
+					false
79
+				),
80
+			],
81
+		];
82
+		$this->_model_relations = [
83
+			'Event'          => new EE_Belongs_To_Relation(),
84
+			'Question_Group' => new EE_Belongs_To_Relation(),
85
+		];
86 86
 
87
-        // this model is generally available for reading
88
-        $path_to_event = 'Event';
87
+		// this model is generally available for reading
88
+		$path_to_event = 'Event';
89 89
 
90
-        $caps[ EEM_Base::caps_read ]       = new EE_Restriction_Generator_Event_Related_Public($path_to_event);
91
-        $caps[ EEM_Base::caps_read_admin ] = new EE_Restriction_Generator_Event_Related_Protected($path_to_event);
92
-        $caps[ EEM_Base::caps_edit ]       = new EE_Restriction_Generator_Event_Related_Protected($path_to_event);
93
-        $caps[ EEM_Base::caps_delete ]     = new EE_Restriction_Generator_Event_Related_Protected(
94
-            $path_to_event,
95
-            EEM_Base::caps_edit
96
-        );
97
-        $this->_cap_restriction_generators = $caps;
98
-        parent::__construct($timezone);
99
-    }
90
+		$caps[ EEM_Base::caps_read ]       = new EE_Restriction_Generator_Event_Related_Public($path_to_event);
91
+		$caps[ EEM_Base::caps_read_admin ] = new EE_Restriction_Generator_Event_Related_Protected($path_to_event);
92
+		$caps[ EEM_Base::caps_edit ]       = new EE_Restriction_Generator_Event_Related_Protected($path_to_event);
93
+		$caps[ EEM_Base::caps_delete ]     = new EE_Restriction_Generator_Event_Related_Protected(
94
+			$path_to_event,
95
+			EEM_Base::caps_edit
96
+		);
97
+		$this->_cap_restriction_generators = $caps;
98
+		parent::__construct($timezone);
99
+	}
100 100
 
101 101
 
102
-    /**
103
-     * Decides whether to use the 'EQG_primary' or newer 'EQG_additional' for use in queries, based on whether
104
-     * this is concerning primary attendees or additional attendees.
105
-     * If 1, true, or "primary" is passed in, returns EQG_primary. If 0, false, or "additional" is passed in, returns
106
-     * EQG_additional.
107
-     *
108
-     * @param string|boolean|int $context
109
-     * @return string
110
-     * @since 4.10.0.p
111
-     */
112
-    public function fieldNameForContext($context): string
113
-    {
114
-        // Basically do a strict switch statement.
115
-        switch (true) {
116
-            case $context === 'additional':
117
-            case $context === false:
118
-            case $context === 0:
119
-                $field_name = EEM_Event_Question_Group::ADDITIONAL;
120
-                break;
121
-            case $context === 'primary':
122
-            case $context === true:
123
-            case $context === 1:
124
-            default:
125
-                $field_name = EEM_Event_Question_Group::PRIMARY;
126
-        }
127
-        return apply_filters('FHEE__EEM_Event_Question_Group__fieldNameForContext', $field_name, $context);
128
-    }
102
+	/**
103
+	 * Decides whether to use the 'EQG_primary' or newer 'EQG_additional' for use in queries, based on whether
104
+	 * this is concerning primary attendees or additional attendees.
105
+	 * If 1, true, or "primary" is passed in, returns EQG_primary. If 0, false, or "additional" is passed in, returns
106
+	 * EQG_additional.
107
+	 *
108
+	 * @param string|boolean|int $context
109
+	 * @return string
110
+	 * @since 4.10.0.p
111
+	 */
112
+	public function fieldNameForContext($context): string
113
+	{
114
+		// Basically do a strict switch statement.
115
+		switch (true) {
116
+			case $context === 'additional':
117
+			case $context === false:
118
+			case $context === 0:
119
+				$field_name = EEM_Event_Question_Group::ADDITIONAL;
120
+				break;
121
+			case $context === 'primary':
122
+			case $context === true:
123
+			case $context === 1:
124
+			default:
125
+				$field_name = EEM_Event_Question_Group::PRIMARY;
126
+		}
127
+		return apply_filters('FHEE__EEM_Event_Question_Group__fieldNameForContext', $field_name, $context);
128
+	}
129 129
 }
Please login to merge, or discard this patch.
core/db_classes/EE_Registration.class.php 1 patch
Indentation   +2546 added lines, -2546 removed lines patch added patch discarded remove patch
@@ -17,2550 +17,2550 @@
 block discarded – undo
17 17
  */
18 18
 class EE_Registration extends EE_Soft_Delete_Base_Class implements EEI_Registration, EEI_Admin_Links
19 19
 {
20
-    /**
21
-     * Used to reference when a registration has never been checked in.
22
-     *
23
-     * @deprecated use \EE_Checkin::status_checked_never instead
24
-     * @type int
25
-     */
26
-    const checkin_status_never = 2;
27
-
28
-    /**
29
-     * Used to reference when a registration has been checked in.
30
-     *
31
-     * @deprecated use \EE_Checkin::status_checked_in instead
32
-     * @type int
33
-     */
34
-    const checkin_status_in = 1;
35
-
36
-    /**
37
-     * Used to reference when a registration has been checked out.
38
-     *
39
-     * @deprecated use \EE_Checkin::status_checked_out instead
40
-     * @type int
41
-     */
42
-    const checkin_status_out = 0;
43
-
44
-    /**
45
-     * extra meta key for tracking reg status os trashed registrations
46
-     *
47
-     * @type string
48
-     */
49
-    const PRE_TRASH_REG_STATUS_KEY = 'pre_trash_registration_status';
50
-
51
-    /**
52
-     * extra meta key for tracking if registration has reserved ticket
53
-     *
54
-     * @type string
55
-     */
56
-    const HAS_RESERVED_TICKET_KEY = 'has_reserved_ticket';
57
-
58
-
59
-    /**
60
-     * @param array  $props_n_values          incoming values
61
-     * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
62
-     *                                        used.)
63
-     * @param array  $date_formats            incoming date_formats in an array where the first value is the
64
-     *                                        date_format and the second value is the time format
65
-     * @return EE_Registration
66
-     * @throws EE_Error
67
-     * @throws InvalidArgumentException
68
-     * @throws InvalidDataTypeException
69
-     * @throws InvalidInterfaceException
70
-     * @throws ReflectionException
71
-     */
72
-    public static function new_instance($props_n_values = [], $timezone = '', $date_formats = [])
73
-    {
74
-        $has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
75
-        return $has_object
76
-            ?: new self($props_n_values, false, $timezone, $date_formats);
77
-    }
78
-
79
-
80
-    /**
81
-     * @param array  $props_n_values  incoming values from the database
82
-     * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
83
-     *                                the website will be used.
84
-     * @return EE_Registration
85
-     * @throws EE_Error
86
-     * @throws InvalidArgumentException
87
-     * @throws InvalidDataTypeException
88
-     * @throws InvalidInterfaceException
89
-     * @throws ReflectionException
90
-     */
91
-    public static function new_instance_from_db($props_n_values = [], $timezone = '')
92
-    {
93
-        return new self($props_n_values, true, $timezone);
94
-    }
95
-
96
-
97
-    /**
98
-     *        Set Event ID
99
-     *
100
-     * @param int $EVT_ID Event ID
101
-     * @throws DomainException
102
-     * @throws EE_Error
103
-     * @throws EntityNotFoundException
104
-     * @throws InvalidArgumentException
105
-     * @throws InvalidDataTypeException
106
-     * @throws InvalidInterfaceException
107
-     * @throws ReflectionException
108
-     * @throws RuntimeException
109
-     * @throws UnexpectedEntityException
110
-     */
111
-    public function set_event($EVT_ID = 0)
112
-    {
113
-        $this->set('EVT_ID', $EVT_ID);
114
-    }
115
-
116
-
117
-    /**
118
-     * Overrides parent set() method so that all calls to set( 'REG_code', $REG_code ) OR set( 'STS_ID', $STS_ID ) can
119
-     * be routed to internal methods
120
-     *
121
-     * @param string $field_name
122
-     * @param mixed  $field_value
123
-     * @param bool   $use_default
124
-     * @throws DomainException
125
-     * @throws EE_Error
126
-     * @throws EntityNotFoundException
127
-     * @throws InvalidArgumentException
128
-     * @throws InvalidDataTypeException
129
-     * @throws InvalidInterfaceException
130
-     * @throws ReflectionException
131
-     * @throws RuntimeException
132
-     * @throws UnexpectedEntityException
133
-     */
134
-    public function set($field_name, $field_value, $use_default = false)
135
-    {
136
-        switch ($field_name) {
137
-            case 'REG_code':
138
-                if (! empty($field_value) && ! $this->reg_code()) {
139
-                    $this->set_reg_code($field_value, $use_default);
140
-                }
141
-                break;
142
-            case 'STS_ID':
143
-                $this->set_status($field_value, $use_default);
144
-                break;
145
-            default:
146
-                parent::set($field_name, $field_value, $use_default);
147
-        }
148
-    }
149
-
150
-
151
-    /**
152
-     * Set Status ID
153
-     * updates the registration status and ALSO...
154
-     * calls reserve_registration_space() if the reg status changes TO approved from any other reg status
155
-     * calls release_registration_space() if the reg status changes FROM approved to any other reg status
156
-     *
157
-     * @param string                $new_STS_ID
158
-     * @param boolean               $use_default
159
-     * @param ContextInterface|null $context
160
-     * @return bool
161
-     * @throws DomainException
162
-     * @throws EE_Error
163
-     * @throws EntityNotFoundException
164
-     * @throws InvalidArgumentException
165
-     * @throws InvalidDataTypeException
166
-     * @throws InvalidInterfaceException
167
-     * @throws ReflectionException
168
-     * @throws RuntimeException
169
-     * @throws UnexpectedEntityException
170
-     */
171
-    public function set_status($new_STS_ID = null, $use_default = false, ContextInterface $context = null)
172
-    {
173
-        // get current REG_Status
174
-        $old_STS_ID = $this->status_ID();
175
-        // if status has changed
176
-        if (
177
-            $old_STS_ID !== $new_STS_ID // and that status has actually changed
178
-            && ! empty($old_STS_ID) // and that old status is actually set
179
-            && ! empty($new_STS_ID) // as well as the new status
180
-            && $this->ID() // ensure registration is in the db
181
-        ) {
182
-            // update internal status first
183
-            parent::set('STS_ID', $new_STS_ID, $use_default);
184
-            // THEN handle other changes that occur when reg status changes
185
-            // TO approved
186
-            if ($new_STS_ID === EEM_Registration::status_id_approved) {
187
-                // reserve a space by incrementing ticket and datetime sold values
188
-                $this->reserveRegistrationSpace();
189
-                do_action('AHEE__EE_Registration__set_status__to_approved', $this, $old_STS_ID, $new_STS_ID, $context);
190
-                // OR FROM  approved
191
-            } elseif ($old_STS_ID === EEM_Registration::status_id_approved) {
192
-                // release a space by decrementing ticket and datetime sold values
193
-                $this->releaseRegistrationSpace();
194
-                do_action(
195
-                    'AHEE__EE_Registration__set_status__from_approved',
196
-                    $this,
197
-                    $old_STS_ID,
198
-                    $new_STS_ID,
199
-                    $context
200
-                );
201
-            }
202
-            // update status
203
-            parent::set('STS_ID', $new_STS_ID, $use_default);
204
-            $this->updateIfCanceledOrReinstated($new_STS_ID, $old_STS_ID, $context);
205
-            if ($this->statusChangeUpdatesTransaction($context)) {
206
-                $this->updateTransactionAfterStatusChange();
207
-            }
208
-            do_action('AHEE__EE_Registration__set_status__after_update', $this, $old_STS_ID, $new_STS_ID, $context);
209
-            return true;
210
-        }
211
-        // even though the old value matches the new value, it's still good to
212
-        // allow the parent set method to have a say
213
-        parent::set('STS_ID', $new_STS_ID, $use_default);
214
-        return true;
215
-    }
216
-
217
-
218
-    /**
219
-     * update REGs and TXN when cancelled or declined registrations involved
220
-     *
221
-     * @param string                $new_STS_ID
222
-     * @param string                $old_STS_ID
223
-     * @param ContextInterface|null $context
224
-     * @throws EE_Error
225
-     * @throws InvalidArgumentException
226
-     * @throws InvalidDataTypeException
227
-     * @throws InvalidInterfaceException
228
-     * @throws ReflectionException
229
-     * @throws RuntimeException
230
-     */
231
-    private function updateIfCanceledOrReinstated($new_STS_ID, $old_STS_ID, ContextInterface $context = null)
232
-    {
233
-        // these reg statuses should not be considered in any calculations involving monies owing
234
-        $closed_reg_statuses = EEM_Registration::closed_reg_statuses();
235
-        // true if registration has been cancelled or declined
236
-        $this->updateIfCanceled(
237
-            $closed_reg_statuses,
238
-            $new_STS_ID,
239
-            $old_STS_ID,
240
-            $context
241
-        );
242
-        $this->updateIfReinstated(
243
-            $closed_reg_statuses,
244
-            $new_STS_ID,
245
-            $old_STS_ID,
246
-            $context
247
-        );
248
-    }
249
-
250
-
251
-    /**
252
-     * update REGs and TXN when cancelled or declined registrations involved
253
-     *
254
-     * @param array                 $closed_reg_statuses
255
-     * @param string                $new_STS_ID
256
-     * @param string                $old_STS_ID
257
-     * @param ContextInterface|null $context
258
-     * @throws EE_Error
259
-     * @throws InvalidArgumentException
260
-     * @throws InvalidDataTypeException
261
-     * @throws InvalidInterfaceException
262
-     * @throws ReflectionException
263
-     * @throws RuntimeException
264
-     */
265
-    private function updateIfCanceled(
266
-        array $closed_reg_statuses,
267
-        $new_STS_ID,
268
-        $old_STS_ID,
269
-        ContextInterface $context = null
270
-    ) {
271
-        // true if registration has been cancelled or declined
272
-        if (
273
-            in_array($new_STS_ID, $closed_reg_statuses, true)
274
-            && ! in_array($old_STS_ID, $closed_reg_statuses, true)
275
-        ) {
276
-            /** @type EE_Registration_Processor $registration_processor */
277
-            $registration_processor = EE_Registry::instance()->load_class('Registration_Processor');
278
-            /** @type EE_Transaction_Processor $transaction_processor */
279
-            $transaction_processor = EE_Registry::instance()->load_class('Transaction_Processor');
280
-            // cancelled or declined registration
281
-            $registration_processor->update_registration_after_being_canceled_or_declined(
282
-                $this,
283
-                $closed_reg_statuses
284
-            );
285
-            $transaction_processor->update_transaction_after_canceled_or_declined_registration(
286
-                $this,
287
-                $closed_reg_statuses,
288
-                false
289
-            );
290
-            do_action(
291
-                'AHEE__EE_Registration__set_status__canceled_or_declined',
292
-                $this,
293
-                $old_STS_ID,
294
-                $new_STS_ID,
295
-                $context
296
-            );
297
-            return;
298
-        }
299
-    }
300
-
301
-
302
-    /**
303
-     * update REGs and TXN when cancelled or declined registrations involved
304
-     *
305
-     * @param array                 $closed_reg_statuses
306
-     * @param string                $new_STS_ID
307
-     * @param string                $old_STS_ID
308
-     * @param ContextInterface|null $context
309
-     * @throws EE_Error
310
-     * @throws InvalidArgumentException
311
-     * @throws InvalidDataTypeException
312
-     * @throws InvalidInterfaceException
313
-     * @throws ReflectionException
314
-     * @throws RuntimeException
315
-     */
316
-    private function updateIfReinstated(
317
-        array $closed_reg_statuses,
318
-        $new_STS_ID,
319
-        $old_STS_ID,
320
-        ContextInterface $context = null
321
-    ) {
322
-        // true if reinstating cancelled or declined registration
323
-        if (
324
-            in_array($old_STS_ID, $closed_reg_statuses, true)
325
-            && ! in_array($new_STS_ID, $closed_reg_statuses, true)
326
-        ) {
327
-            /** @type EE_Registration_Processor $registration_processor */
328
-            $registration_processor = EE_Registry::instance()->load_class('Registration_Processor');
329
-            /** @type EE_Transaction_Processor $transaction_processor */
330
-            $transaction_processor = EE_Registry::instance()->load_class('Transaction_Processor');
331
-            // reinstating cancelled or declined registration
332
-            $registration_processor->update_canceled_or_declined_registration_after_being_reinstated(
333
-                $this,
334
-                $closed_reg_statuses
335
-            );
336
-            $transaction_processor->update_transaction_after_reinstating_canceled_registration(
337
-                $this,
338
-                $closed_reg_statuses,
339
-                false
340
-            );
341
-            do_action(
342
-                'AHEE__EE_Registration__set_status__after_reinstated',
343
-                $this,
344
-                $old_STS_ID,
345
-                $new_STS_ID,
346
-                $context
347
-            );
348
-        }
349
-    }
350
-
351
-
352
-    /**
353
-     * @param ContextInterface|null $context
354
-     * @return bool
355
-     */
356
-    private function statusChangeUpdatesTransaction(ContextInterface $context = null)
357
-    {
358
-        $contexts_that_do_not_update_transaction = (array) apply_filters(
359
-            'AHEE__EE_Registration__statusChangeUpdatesTransaction__contexts_that_do_not_update_transaction',
360
-            ['spco_reg_step_attendee_information_process_registrations'],
361
-            $context,
362
-            $this
363
-        );
364
-        return ! (
365
-            $context instanceof ContextInterface
366
-            && in_array($context->slug(), $contexts_that_do_not_update_transaction, true)
367
-        );
368
-    }
369
-
370
-
371
-    /**
372
-     * @throws EE_Error
373
-     * @throws EntityNotFoundException
374
-     * @throws InvalidArgumentException
375
-     * @throws InvalidDataTypeException
376
-     * @throws InvalidInterfaceException
377
-     * @throws ReflectionException
378
-     * @throws RuntimeException
379
-     */
380
-    private function updateTransactionAfterStatusChange()
381
-    {
382
-        /** @type EE_Transaction_Payments $transaction_payments */
383
-        $transaction_payments = EE_Registry::instance()->load_class('Transaction_Payments');
384
-        $transaction_payments->recalculate_transaction_total($this->transaction(), false);
385
-        $this->transaction()->update_status_based_on_total_paid();
386
-    }
387
-
388
-
389
-    /**
390
-     * get Status ID
391
-     *
392
-     * @throws EE_Error
393
-     * @throws InvalidArgumentException
394
-     * @throws InvalidDataTypeException
395
-     * @throws InvalidInterfaceException
396
-     * @throws ReflectionException
397
-     */
398
-    public function status_ID()
399
-    {
400
-        return $this->get('STS_ID');
401
-    }
402
-
403
-
404
-    /**
405
-     * Gets the ticket this registration is for
406
-     *
407
-     * @param boolean $include_archived whether to include archived tickets or not.
408
-     * @return EE_Ticket|EE_Base_Class
409
-     * @throws EE_Error
410
-     * @throws InvalidArgumentException
411
-     * @throws InvalidDataTypeException
412
-     * @throws InvalidInterfaceException
413
-     * @throws ReflectionException
414
-     */
415
-    public function ticket($include_archived = true)
416
-    {
417
-        return EEM_Ticket::instance()->get_one_by_ID($this->ticket_ID());
418
-    }
419
-
420
-
421
-    /**
422
-     * Gets the event this registration is for
423
-     *
424
-     * @return EE_Event
425
-     * @throws EE_Error
426
-     * @throws EntityNotFoundException
427
-     * @throws InvalidArgumentException
428
-     * @throws InvalidDataTypeException
429
-     * @throws InvalidInterfaceException
430
-     * @throws ReflectionException
431
-     */
432
-    public function event(): EE_Event
433
-    {
434
-        $event = $this->event_obj();
435
-        if (! $event instanceof EE_Event) {
436
-            throw new EntityNotFoundException('Event ID', $this->event_ID());
437
-        }
438
-        return $event;
439
-    }
440
-
441
-
442
-    /**
443
-     * Gets the "author" of the registration.  Note that for the purposes of registrations, the author will correspond
444
-     * with the author of the event this registration is for.
445
-     *
446
-     * @return int
447
-     * @throws EE_Error
448
-     * @throws EntityNotFoundException
449
-     * @throws InvalidArgumentException
450
-     * @throws InvalidDataTypeException
451
-     * @throws InvalidInterfaceException
452
-     * @throws ReflectionException
453
-     * @since 4.5.0
454
-     */
455
-    public function wp_user(): int
456
-    {
457
-        return $this->event()->wp_user();
458
-    }
459
-
460
-
461
-    /**
462
-     * increments this registration's related ticket sold and corresponding datetime sold values
463
-     *
464
-     * @return void
465
-     * @throws DomainException
466
-     * @throws EE_Error
467
-     * @throws EntityNotFoundException
468
-     * @throws InvalidArgumentException
469
-     * @throws InvalidDataTypeException
470
-     * @throws InvalidInterfaceException
471
-     * @throws ReflectionException
472
-     * @throws UnexpectedEntityException
473
-     */
474
-    private function reserveRegistrationSpace()
475
-    {
476
-        // reserved ticket and datetime counts will be decremented as sold counts are incremented
477
-        // so stop tracking that this reg has a ticket reserved
478
-        $this->release_reserved_ticket(false, "REG: {$this->ID()} (ln:" . __LINE__ . ')');
479
-        $ticket = $this->ticket();
480
-        $ticket->increaseSold();
481
-        // possibly set event status to sold out
482
-        $this->event()->perform_sold_out_status_check();
483
-    }
484
-
485
-
486
-    /**
487
-     * decrements (subtracts) this registration's related ticket sold and corresponding datetime sold values
488
-     *
489
-     * @return void
490
-     * @throws DomainException
491
-     * @throws EE_Error
492
-     * @throws EntityNotFoundException
493
-     * @throws InvalidArgumentException
494
-     * @throws InvalidDataTypeException
495
-     * @throws InvalidInterfaceException
496
-     * @throws ReflectionException
497
-     * @throws UnexpectedEntityException
498
-     */
499
-    private function releaseRegistrationSpace()
500
-    {
501
-        $ticket = $this->ticket();
502
-        $ticket->decreaseSold();
503
-        // possibly change event status from sold out back to previous status
504
-        $this->event()->perform_sold_out_status_check();
505
-    }
506
-
507
-
508
-    /**
509
-     * tracks this registration's ticket reservation in extra meta
510
-     * and can increment related ticket reserved and corresponding datetime reserved values
511
-     *
512
-     * @param bool   $update_ticket if true, will increment ticket and datetime reserved count
513
-     * @param string $source
514
-     * @return void
515
-     * @throws EE_Error
516
-     * @throws InvalidArgumentException
517
-     * @throws InvalidDataTypeException
518
-     * @throws InvalidInterfaceException
519
-     * @throws ReflectionException
520
-     */
521
-    public function reserve_ticket($update_ticket = false, $source = 'unknown')
522
-    {
523
-        // only reserve ticket if space is not currently reserved
524
-        if ((bool) $this->get_extra_meta(EE_Registration::HAS_RESERVED_TICKET_KEY, true) !== true) {
525
-            $reserved = $this->update_extra_meta(EE_Registration::HAS_RESERVED_TICKET_KEY, true);
526
-            if ($reserved && $update_ticket) {
527
-                $ticket = $this->ticket();
528
-                $ticket->increaseReserved(1, "REG: {$this->ID()} (ln:" . __LINE__ . ')');
529
-                $this->update_extra_meta('reserve_ticket', "{$this->ticket_ID()} from {$source}");
530
-                $ticket->save();
531
-            }
532
-        }
533
-    }
534
-
535
-
536
-    /**
537
-     * stops tracking this registration's ticket reservation in extra meta
538
-     * decrements (subtracts) related ticket reserved and corresponding datetime reserved values
539
-     *
540
-     * @param bool   $update_ticket if true, will decrement ticket and datetime reserved count
541
-     * @param string $source
542
-     * @return void
543
-     * @throws EE_Error
544
-     * @throws InvalidArgumentException
545
-     * @throws InvalidDataTypeException
546
-     * @throws InvalidInterfaceException
547
-     * @throws ReflectionException
548
-     */
549
-    public function release_reserved_ticket($update_ticket = false, $source = 'unknown')
550
-    {
551
-        // only release ticket if space is currently reserved
552
-        if ((bool) $this->get_extra_meta(EE_Registration::HAS_RESERVED_TICKET_KEY, true) === true) {
553
-            $reserved = $this->update_extra_meta(EE_Registration::HAS_RESERVED_TICKET_KEY, false);
554
-            if ($reserved && $update_ticket) {
555
-                $ticket = $this->ticket();
556
-                $ticket->decreaseReserved(1, true, "REG: {$this->ID()} (ln:" . __LINE__ . ')');
557
-                $this->update_extra_meta('release_reserved_ticket', "{$this->ticket_ID()} from {$source}");
558
-            }
559
-        }
560
-    }
561
-
562
-
563
-    /**
564
-     * Set Attendee ID
565
-     *
566
-     * @param int $ATT_ID Attendee ID
567
-     * @throws DomainException
568
-     * @throws EE_Error
569
-     * @throws EntityNotFoundException
570
-     * @throws InvalidArgumentException
571
-     * @throws InvalidDataTypeException
572
-     * @throws InvalidInterfaceException
573
-     * @throws ReflectionException
574
-     * @throws RuntimeException
575
-     * @throws UnexpectedEntityException
576
-     */
577
-    public function set_attendee_id($ATT_ID = 0)
578
-    {
579
-        $this->set('ATT_ID', $ATT_ID);
580
-    }
581
-
582
-
583
-    /**
584
-     *        Set Transaction ID
585
-     *
586
-     * @param int $TXN_ID Transaction ID
587
-     * @throws DomainException
588
-     * @throws EE_Error
589
-     * @throws EntityNotFoundException
590
-     * @throws InvalidArgumentException
591
-     * @throws InvalidDataTypeException
592
-     * @throws InvalidInterfaceException
593
-     * @throws ReflectionException
594
-     * @throws RuntimeException
595
-     * @throws UnexpectedEntityException
596
-     */
597
-    public function set_transaction_id($TXN_ID = 0)
598
-    {
599
-        $this->set('TXN_ID', $TXN_ID);
600
-    }
601
-
602
-
603
-    /**
604
-     *        Set Session
605
-     *
606
-     * @param string $REG_session PHP Session ID
607
-     * @throws DomainException
608
-     * @throws EE_Error
609
-     * @throws EntityNotFoundException
610
-     * @throws InvalidArgumentException
611
-     * @throws InvalidDataTypeException
612
-     * @throws InvalidInterfaceException
613
-     * @throws ReflectionException
614
-     * @throws RuntimeException
615
-     * @throws UnexpectedEntityException
616
-     */
617
-    public function set_session($REG_session = '')
618
-    {
619
-        $this->set('REG_session', $REG_session);
620
-    }
621
-
622
-
623
-    /**
624
-     *        Set Registration URL Link
625
-     *
626
-     * @param string $REG_url_link Registration URL Link
627
-     * @throws DomainException
628
-     * @throws EE_Error
629
-     * @throws EntityNotFoundException
630
-     * @throws InvalidArgumentException
631
-     * @throws InvalidDataTypeException
632
-     * @throws InvalidInterfaceException
633
-     * @throws ReflectionException
634
-     * @throws RuntimeException
635
-     * @throws UnexpectedEntityException
636
-     */
637
-    public function set_reg_url_link($REG_url_link = '')
638
-    {
639
-        $this->set('REG_url_link', $REG_url_link);
640
-    }
641
-
642
-
643
-    /**
644
-     *        Set Attendee Counter
645
-     *
646
-     * @param int $REG_count Primary Attendee
647
-     * @throws DomainException
648
-     * @throws EE_Error
649
-     * @throws EntityNotFoundException
650
-     * @throws InvalidArgumentException
651
-     * @throws InvalidDataTypeException
652
-     * @throws InvalidInterfaceException
653
-     * @throws ReflectionException
654
-     * @throws RuntimeException
655
-     * @throws UnexpectedEntityException
656
-     */
657
-    public function set_count($REG_count = 1)
658
-    {
659
-        $this->set('REG_count', $REG_count);
660
-    }
661
-
662
-
663
-    /**
664
-     *        Set Group Size
665
-     *
666
-     * @param boolean $REG_group_size Group Registration
667
-     * @throws DomainException
668
-     * @throws EE_Error
669
-     * @throws EntityNotFoundException
670
-     * @throws InvalidArgumentException
671
-     * @throws InvalidDataTypeException
672
-     * @throws InvalidInterfaceException
673
-     * @throws ReflectionException
674
-     * @throws RuntimeException
675
-     * @throws UnexpectedEntityException
676
-     */
677
-    public function set_group_size($REG_group_size = false)
678
-    {
679
-        $this->set('REG_group_size', $REG_group_size);
680
-    }
681
-
682
-
683
-    /**
684
-     *    is_not_approved -  convenience method that returns TRUE if REG status ID ==
685
-     *    EEM_Registration::status_id_not_approved
686
-     *
687
-     * @return        boolean
688
-     * @throws EE_Error
689
-     * @throws InvalidArgumentException
690
-     * @throws InvalidDataTypeException
691
-     * @throws InvalidInterfaceException
692
-     * @throws ReflectionException
693
-     */
694
-    public function is_not_approved()
695
-    {
696
-        return $this->status_ID() === EEM_Registration::status_id_not_approved;
697
-    }
698
-
699
-
700
-    /**
701
-     *    is_pending_payment -  convenience method that returns TRUE if REG status ID ==
702
-     *    EEM_Registration::status_id_pending_payment
703
-     *
704
-     * @return        boolean
705
-     * @throws EE_Error
706
-     * @throws InvalidArgumentException
707
-     * @throws InvalidDataTypeException
708
-     * @throws InvalidInterfaceException
709
-     * @throws ReflectionException
710
-     */
711
-    public function is_pending_payment()
712
-    {
713
-        return $this->status_ID() === EEM_Registration::status_id_pending_payment;
714
-    }
715
-
716
-
717
-    /**
718
-     *    is_approved -  convenience method that returns TRUE if REG status ID == EEM_Registration::status_id_approved
719
-     *
720
-     * @return        boolean
721
-     * @throws EE_Error
722
-     * @throws InvalidArgumentException
723
-     * @throws InvalidDataTypeException
724
-     * @throws InvalidInterfaceException
725
-     * @throws ReflectionException
726
-     */
727
-    public function is_approved()
728
-    {
729
-        return $this->status_ID() === EEM_Registration::status_id_approved;
730
-    }
731
-
732
-
733
-    /**
734
-     *    is_cancelled -  convenience method that returns TRUE if REG status ID == EEM_Registration::status_id_cancelled
735
-     *
736
-     * @return        boolean
737
-     * @throws EE_Error
738
-     * @throws InvalidArgumentException
739
-     * @throws InvalidDataTypeException
740
-     * @throws InvalidInterfaceException
741
-     * @throws ReflectionException
742
-     */
743
-    public function is_cancelled()
744
-    {
745
-        return $this->status_ID() === EEM_Registration::status_id_cancelled;
746
-    }
747
-
748
-
749
-    /**
750
-     *    is_declined -  convenience method that returns TRUE if REG status ID == EEM_Registration::status_id_declined
751
-     *
752
-     * @return        boolean
753
-     * @throws EE_Error
754
-     * @throws InvalidArgumentException
755
-     * @throws InvalidDataTypeException
756
-     * @throws InvalidInterfaceException
757
-     * @throws ReflectionException
758
-     */
759
-    public function is_declined()
760
-    {
761
-        return $this->status_ID() === EEM_Registration::status_id_declined;
762
-    }
763
-
764
-
765
-    /**
766
-     *    is_incomplete -  convenience method that returns TRUE if REG status ID ==
767
-     *    EEM_Registration::status_id_incomplete
768
-     *
769
-     * @return        boolean
770
-     * @throws EE_Error
771
-     * @throws InvalidArgumentException
772
-     * @throws InvalidDataTypeException
773
-     * @throws InvalidInterfaceException
774
-     * @throws ReflectionException
775
-     */
776
-    public function is_incomplete()
777
-    {
778
-        return $this->status_ID() === EEM_Registration::status_id_incomplete;
779
-    }
780
-
781
-
782
-    /**
783
-     *        Set Registration Date
784
-     *
785
-     * @param mixed ( int or string ) $REG_date Registration Date - Unix timestamp or string representation of
786
-     *                                                 Date
787
-     * @throws DomainException
788
-     * @throws EE_Error
789
-     * @throws EntityNotFoundException
790
-     * @throws InvalidArgumentException
791
-     * @throws InvalidDataTypeException
792
-     * @throws InvalidInterfaceException
793
-     * @throws ReflectionException
794
-     * @throws RuntimeException
795
-     * @throws UnexpectedEntityException
796
-     */
797
-    public function set_reg_date($REG_date = false)
798
-    {
799
-        $this->set('REG_date', $REG_date);
800
-    }
801
-
802
-
803
-    /**
804
-     *    Set final price owing for this registration after all ticket/price modifications
805
-     *
806
-     * @param float $REG_final_price
807
-     * @throws DomainException
808
-     * @throws EE_Error
809
-     * @throws EntityNotFoundException
810
-     * @throws InvalidArgumentException
811
-     * @throws InvalidDataTypeException
812
-     * @throws InvalidInterfaceException
813
-     * @throws ReflectionException
814
-     * @throws RuntimeException
815
-     * @throws UnexpectedEntityException
816
-     */
817
-    public function set_final_price($REG_final_price = 0.00)
818
-    {
819
-        $this->set('REG_final_price', $REG_final_price);
820
-    }
821
-
822
-
823
-    /**
824
-     *    Set amount paid towards this registration's final price
825
-     *
826
-     * @param float|int|string $REG_paid
827
-     * @throws DomainException
828
-     * @throws EE_Error
829
-     * @throws EntityNotFoundException
830
-     * @throws InvalidArgumentException
831
-     * @throws InvalidDataTypeException
832
-     * @throws InvalidInterfaceException
833
-     * @throws ReflectionException
834
-     * @throws RuntimeException
835
-     * @throws UnexpectedEntityException
836
-     */
837
-    public function set_paid($REG_paid = 0.00)
838
-    {
839
-        $this->set('REG_paid', (float) $REG_paid);
840
-    }
841
-
842
-
843
-    /**
844
-     *        Attendee Is Going
845
-     *
846
-     * @param boolean $REG_att_is_going Attendee Is Going
847
-     * @throws DomainException
848
-     * @throws EE_Error
849
-     * @throws EntityNotFoundException
850
-     * @throws InvalidArgumentException
851
-     * @throws InvalidDataTypeException
852
-     * @throws InvalidInterfaceException
853
-     * @throws ReflectionException
854
-     * @throws RuntimeException
855
-     * @throws UnexpectedEntityException
856
-     */
857
-    public function set_att_is_going($REG_att_is_going = false)
858
-    {
859
-        $this->set('REG_att_is_going', $REG_att_is_going);
860
-    }
861
-
862
-
863
-    /**
864
-     * Gets the related attendee
865
-     *
866
-     * @return EE_Attendee|EE_Base_Class
867
-     * @throws EE_Error
868
-     * @throws InvalidArgumentException
869
-     * @throws InvalidDataTypeException
870
-     * @throws InvalidInterfaceException
871
-     * @throws ReflectionException
872
-     */
873
-    public function attendee()
874
-    {
875
-        return EEM_Attendee::instance()->get_one_by_ID($this->attendee_ID());
876
-    }
877
-
878
-
879
-    /**
880
-     * Gets the name of the attendee.
881
-     *
882
-     * @param bool $apply_html_entities set to true if you want to use HTML entities.
883
-     * @return string
884
-     * @throws EE_Error
885
-     * @throws InvalidArgumentException
886
-     * @throws InvalidDataTypeException
887
-     * @throws InvalidInterfaceException
888
-     * @throws ReflectionException
889
-     * @since 4.10.12.p
890
-     */
891
-    public function attendeeName($apply_html_entities = false)
892
-    {
893
-        $attendee = $this->attendee();
894
-        if ($attendee instanceof EE_Attendee) {
895
-            $attendee_name = $attendee->full_name($apply_html_entities);
896
-        } else {
897
-            $attendee_name = esc_html__('Unknown', 'event_espresso');
898
-        }
899
-        return $attendee_name;
900
-    }
901
-
902
-
903
-    /**
904
-     *        get Event ID
905
-     */
906
-    public function event_ID()
907
-    {
908
-        return $this->get('EVT_ID');
909
-    }
910
-
911
-
912
-    /**
913
-     *        get Event ID
914
-     */
915
-    public function event_name()
916
-    {
917
-        $event = $this->event_obj();
918
-        if ($event) {
919
-            return $event->name();
920
-        } else {
921
-            return null;
922
-        }
923
-    }
924
-
925
-
926
-    /**
927
-     * Fetches the event this registration is for
928
-     *
929
-     * @return EE_Base_Class|EE_Event
930
-     * @throws EE_Error
931
-     * @throws InvalidArgumentException
932
-     * @throws InvalidDataTypeException
933
-     * @throws InvalidInterfaceException
934
-     * @throws ReflectionException
935
-     */
936
-    public function event_obj()
937
-    {
938
-        return EEM_Event::instance()->get_one_by_ID($this->event_ID());
939
-    }
940
-
941
-
942
-    /**
943
-     *        get Attendee ID
944
-     */
945
-    public function attendee_ID()
946
-    {
947
-        return $this->get('ATT_ID');
948
-    }
949
-
950
-
951
-    /**
952
-     *        get PHP Session ID
953
-     */
954
-    public function session_ID()
955
-    {
956
-        return $this->get('REG_session');
957
-    }
958
-
959
-
960
-    /**
961
-     * Gets the string which represents the URL trigger for the receipt template in the message template system.
962
-     *
963
-     * @param string $messenger 'pdf' or 'html'.  Default 'html'.
964
-     * @return string
965
-     * @throws DomainException
966
-     * @throws InvalidArgumentException
967
-     * @throws InvalidDataTypeException
968
-     * @throws InvalidInterfaceException
969
-     */
970
-    public function receipt_url($messenger = 'html')
971
-    {
972
-        return apply_filters('FHEE__EE_Registration__receipt_url__receipt_url', '', $this, $messenger, 'receipt');
973
-    }
974
-
975
-
976
-    /**
977
-     * Gets the string which represents the URL trigger for the invoice template in the message template system.
978
-     *
979
-     * @param string $messenger 'pdf' or 'html'.  Default 'html'.
980
-     * @return string
981
-     * @throws DomainException
982
-     * @throws InvalidArgumentException
983
-     * @throws InvalidDataTypeException
984
-     * @throws InvalidInterfaceException
985
-     */
986
-    public function invoice_url($messenger = 'html')
987
-    {
988
-        return apply_filters('FHEE__EE_Registration__invoice_url__invoice_url', '', $this, $messenger, 'invoice');
989
-    }
990
-
991
-
992
-    /**
993
-     * get Registration URL Link
994
-     *
995
-     * @return string
996
-     * @throws EE_Error
997
-     * @throws InvalidArgumentException
998
-     * @throws InvalidDataTypeException
999
-     * @throws InvalidInterfaceException
1000
-     * @throws ReflectionException
1001
-     */
1002
-    public function reg_url_link()
1003
-    {
1004
-        return (string) $this->get('REG_url_link');
1005
-    }
1006
-
1007
-
1008
-    /**
1009
-     * Echoes out invoice_url()
1010
-     *
1011
-     * @param string $type 'download','launch', or 'html' (default is 'launch')
1012
-     * @return void
1013
-     * @throws DomainException
1014
-     * @throws EE_Error
1015
-     * @throws InvalidArgumentException
1016
-     * @throws InvalidDataTypeException
1017
-     * @throws InvalidInterfaceException
1018
-     * @throws ReflectionException
1019
-     */
1020
-    public function e_invoice_url($type = 'launch')
1021
-    {
1022
-        echo esc_url_raw($this->invoice_url($type));
1023
-    }
1024
-
1025
-
1026
-    /**
1027
-     * Echoes out payment_overview_url
1028
-     */
1029
-    public function e_payment_overview_url()
1030
-    {
1031
-        echo esc_url_raw($this->payment_overview_url());
1032
-    }
1033
-
1034
-
1035
-    /**
1036
-     * Gets the URL for the checkout payment options reg step
1037
-     * with this registration's REG_url_link added as a query parameter
1038
-     *
1039
-     * @param bool $clear_session Set to true when you want to clear the session on revisiting the
1040
-     *                            payment overview url.
1041
-     * @return string
1042
-     * @throws EE_Error
1043
-     * @throws InvalidArgumentException
1044
-     * @throws InvalidDataTypeException
1045
-     * @throws InvalidInterfaceException
1046
-     * @throws ReflectionException
1047
-     */
1048
-    public function payment_overview_url($clear_session = false)
1049
-    {
1050
-        return add_query_arg(
1051
-            (array) apply_filters(
1052
-                'FHEE__EE_Registration__payment_overview_url__query_args',
1053
-                [
1054
-                    'e_reg_url_link' => $this->reg_url_link(),
1055
-                    'step'           => 'payment_options',
1056
-                    'revisit'        => true,
1057
-                    'clear_session'  => (bool) $clear_session,
1058
-                ],
1059
-                $this
1060
-            ),
1061
-            EE_Registry::instance()->CFG->core->reg_page_url()
1062
-        );
1063
-    }
1064
-
1065
-
1066
-    /**
1067
-     * Gets the URL for the checkout attendee information reg step
1068
-     * with this registration's REG_url_link added as a query parameter
1069
-     *
1070
-     * @return string
1071
-     * @throws EE_Error
1072
-     * @throws InvalidArgumentException
1073
-     * @throws InvalidDataTypeException
1074
-     * @throws InvalidInterfaceException
1075
-     * @throws ReflectionException
1076
-     */
1077
-    public function edit_attendee_information_url()
1078
-    {
1079
-        return add_query_arg(
1080
-            (array) apply_filters(
1081
-                'FHEE__EE_Registration__edit_attendee_information_url__query_args',
1082
-                [
1083
-                    'e_reg_url_link' => $this->reg_url_link(),
1084
-                    'step'           => 'attendee_information',
1085
-                    'revisit'        => true,
1086
-                ],
1087
-                $this
1088
-            ),
1089
-            EE_Registry::instance()->CFG->core->reg_page_url()
1090
-        );
1091
-    }
1092
-
1093
-
1094
-    /**
1095
-     * Simply generates and returns the appropriate admin_url link to edit this registration
1096
-     *
1097
-     * @return string
1098
-     * @throws EE_Error
1099
-     * @throws InvalidArgumentException
1100
-     * @throws InvalidDataTypeException
1101
-     * @throws InvalidInterfaceException
1102
-     * @throws ReflectionException
1103
-     */
1104
-    public function get_admin_edit_url()
1105
-    {
1106
-        return EEH_URL::add_query_args_and_nonce(
1107
-            [
1108
-                'page'    => 'espresso_registrations',
1109
-                'action'  => 'view_registration',
1110
-                '_REG_ID' => $this->ID(),
1111
-            ],
1112
-            admin_url('admin.php')
1113
-        );
1114
-    }
1115
-
1116
-
1117
-    /**
1118
-     * is_primary_registrant?
1119
-     *
1120
-     * @throws EE_Error
1121
-     * @throws InvalidArgumentException
1122
-     * @throws InvalidDataTypeException
1123
-     * @throws InvalidInterfaceException
1124
-     * @throws ReflectionException
1125
-     */
1126
-    public function is_primary_registrant()
1127
-    {
1128
-        return (int) $this->get('REG_count') === 1;
1129
-    }
1130
-
1131
-
1132
-    /**
1133
-     * This returns the primary registration object for this registration group (which may be this object).
1134
-     *
1135
-     * @return EE_Registration
1136
-     * @throws EE_Error
1137
-     * @throws InvalidArgumentException
1138
-     * @throws InvalidDataTypeException
1139
-     * @throws InvalidInterfaceException
1140
-     * @throws ReflectionException
1141
-     */
1142
-    public function get_primary_registration()
1143
-    {
1144
-        if ($this->is_primary_registrant()) {
1145
-            return $this;
1146
-        }
1147
-
1148
-        // k reg_count !== 1 so let's get the EE_Registration object matching this txn_id and reg_count == 1
1149
-        /** @var EE_Registration $primary_registrant */
1150
-        $primary_registrant = EEM_Registration::instance()->get_one(
1151
-            [
1152
-                [
1153
-                    'TXN_ID'    => $this->transaction_ID(),
1154
-                    'REG_count' => 1,
1155
-                ],
1156
-            ]
1157
-        );
1158
-        return $primary_registrant;
1159
-    }
1160
-
1161
-
1162
-    /**
1163
-     * get  Attendee Number
1164
-     *
1165
-     * @throws EE_Error
1166
-     * @throws InvalidArgumentException
1167
-     * @throws InvalidDataTypeException
1168
-     * @throws InvalidInterfaceException
1169
-     * @throws ReflectionException
1170
-     */
1171
-    public function count()
1172
-    {
1173
-        return $this->get('REG_count');
1174
-    }
1175
-
1176
-
1177
-    /**
1178
-     * get Group Size
1179
-     *
1180
-     * @throws EE_Error
1181
-     * @throws InvalidArgumentException
1182
-     * @throws InvalidDataTypeException
1183
-     * @throws InvalidInterfaceException
1184
-     * @throws ReflectionException
1185
-     */
1186
-    public function group_size()
1187
-    {
1188
-        return $this->get('REG_group_size');
1189
-    }
1190
-
1191
-
1192
-    /**
1193
-     * get Registration Date
1194
-     *
1195
-     * @throws EE_Error
1196
-     * @throws InvalidArgumentException
1197
-     * @throws InvalidDataTypeException
1198
-     * @throws InvalidInterfaceException
1199
-     * @throws ReflectionException
1200
-     */
1201
-    public function date()
1202
-    {
1203
-        return $this->get('REG_date');
1204
-    }
1205
-
1206
-
1207
-    /**
1208
-     * gets a pretty date
1209
-     *
1210
-     * @param string $date_format
1211
-     * @param string $time_format
1212
-     * @return string
1213
-     * @throws EE_Error
1214
-     * @throws InvalidArgumentException
1215
-     * @throws InvalidDataTypeException
1216
-     * @throws InvalidInterfaceException
1217
-     * @throws ReflectionException
1218
-     */
1219
-    public function pretty_date($date_format = null, $time_format = null)
1220
-    {
1221
-        return $this->get_datetime('REG_date', $date_format, $time_format);
1222
-    }
1223
-
1224
-
1225
-    /**
1226
-     * final_price
1227
-     * the registration's share of the transaction total, so that the
1228
-     * sum of all the transaction's REG_final_prices equal the transaction's total
1229
-     *
1230
-     * @return float
1231
-     * @throws EE_Error
1232
-     * @throws InvalidArgumentException
1233
-     * @throws InvalidDataTypeException
1234
-     * @throws InvalidInterfaceException
1235
-     * @throws ReflectionException
1236
-     */
1237
-    public function final_price(): float
1238
-    {
1239
-        return (float) $this->get('REG_final_price');
1240
-    }
1241
-
1242
-
1243
-    /**
1244
-     * pretty_final_price
1245
-     *  final price as formatted string, with correct decimal places and currency symbol
1246
-     *
1247
-     * @param string|null $schema
1248
-     *      Schemas:
1249
-     *      'localized_float': "3,023.00"
1250
-     *      'no_currency_code': "$3,023.00"
1251
-     *      null: "$3,023.00<span>USD</span>"
1252
-     * @return string
1253
-     * @throws EE_Error
1254
-     * @throws InvalidArgumentException
1255
-     * @throws InvalidDataTypeException
1256
-     * @throws InvalidInterfaceException
1257
-     * @throws ReflectionException
1258
-     */
1259
-    public function pretty_final_price(?string $schema = null)
1260
-    {
1261
-        return $this->get_pretty('REG_final_price', $schema);
1262
-    }
1263
-
1264
-
1265
-    /**
1266
-     * get paid (yeah)
1267
-     *
1268
-     * @return float
1269
-     * @throws EE_Error
1270
-     * @throws InvalidArgumentException
1271
-     * @throws InvalidDataTypeException
1272
-     * @throws InvalidInterfaceException
1273
-     * @throws ReflectionException
1274
-     */
1275
-    public function paid(): float
1276
-    {
1277
-        return (float) $this->get('REG_paid');
1278
-    }
1279
-
1280
-
1281
-    /**
1282
-     * pretty_paid
1283
-     *
1284
-     * @param string|null $schema
1285
-     *      Schemas:
1286
-     *      'localized_float': "3,023.00"
1287
-     *      'no_currency_code': "$3,023.00"
1288
-     *      null: "$3,023.00<span>USD</span>"
1289
-     * @return float
1290
-     * @throws EE_Error
1291
-     * @throws InvalidArgumentException
1292
-     * @throws InvalidDataTypeException
1293
-     * @throws InvalidInterfaceException
1294
-     * @throws ReflectionException
1295
-     */
1296
-    public function pretty_paid(?string $schema = null)
1297
-    {
1298
-        return $this->get_pretty('REG_paid', $schema);
1299
-    }
1300
-
1301
-
1302
-    /**
1303
-     * owes_monies_and_can_pay
1304
-     * whether or not this registration has monies owing and it's' status allows payment
1305
-     *
1306
-     * @param array $requires_payment list of registration statuses that allow a registrant to make a payment
1307
-     * @return bool
1308
-     * @throws EE_Error
1309
-     * @throws InvalidArgumentException
1310
-     * @throws InvalidDataTypeException
1311
-     * @throws InvalidInterfaceException
1312
-     * @throws ReflectionException
1313
-     */
1314
-    public function owes_monies_and_can_pay(array $requires_payment = []): bool
1315
-    {
1316
-        // these reg statuses require payment (if event is not free)
1317
-        $requires_payment = ! empty($requires_payment)
1318
-            ? $requires_payment
1319
-            : EEM_Registration::reg_statuses_that_allow_payment();
1320
-        if (
1321
-            $this->final_price() !== 0.0 &&
1322
-            $this->final_price() !== $this->paid() &&
1323
-            in_array($this->status_ID(), $requires_payment)
1324
-        ) {
1325
-            return true;
1326
-        }
1327
-        return false;
1328
-    }
1329
-
1330
-
1331
-    /**
1332
-     * Prints out the return value of $this->pretty_status()
1333
-     *
1334
-     * @param bool $show_icons
1335
-     * @return void
1336
-     * @throws EE_Error
1337
-     * @throws InvalidArgumentException
1338
-     * @throws InvalidDataTypeException
1339
-     * @throws InvalidInterfaceException
1340
-     * @throws ReflectionException
1341
-     */
1342
-    public function e_pretty_status($show_icons = false)
1343
-    {
1344
-        echo wp_kses($this->pretty_status($show_icons), AllowedTags::getAllowedTags());
1345
-    }
1346
-
1347
-
1348
-    /**
1349
-     * Returns a nice version of the status for displaying to customers
1350
-     *
1351
-     * @param bool $show_icons
1352
-     * @return string
1353
-     * @throws EE_Error
1354
-     * @throws InvalidArgumentException
1355
-     * @throws InvalidDataTypeException
1356
-     * @throws InvalidInterfaceException
1357
-     * @throws ReflectionException
1358
-     */
1359
-    public function pretty_status($show_icons = false)
1360
-    {
1361
-        $status = EEM_Status::instance()->localized_status(
1362
-            [$this->status_ID() => esc_html__('unknown', 'event_espresso')],
1363
-            false,
1364
-            'sentence'
1365
-        );
1366
-        $icon   = '';
1367
-        switch ($this->status_ID()) {
1368
-            case EEM_Registration::status_id_approved:
1369
-                $icon = $show_icons
1370
-                    ? '<span class="dashicons dashicons-star-filled ee-icon-size-16 green-text"></span>'
1371
-                    : '';
1372
-                break;
1373
-            case EEM_Registration::status_id_pending_payment:
1374
-                $icon = $show_icons
1375
-                    ? '<span class="dashicons dashicons-star-half ee-icon-size-16 orange-text"></span>'
1376
-                    : '';
1377
-                break;
1378
-            case EEM_Registration::status_id_not_approved:
1379
-                $icon = $show_icons
1380
-                    ? '<span class="dashicons dashicons-marker ee-icon-size-16 orange-text"></span>'
1381
-                    : '';
1382
-                break;
1383
-            case EEM_Registration::status_id_cancelled:
1384
-                $icon = $show_icons
1385
-                    ? '<span class="dashicons dashicons-no ee-icon-size-16 lt-grey-text"></span>'
1386
-                    : '';
1387
-                break;
1388
-            case EEM_Registration::status_id_incomplete:
1389
-                $icon = $show_icons
1390
-                    ? '<span class="dashicons dashicons-no ee-icon-size-16 lt-orange-text"></span>'
1391
-                    : '';
1392
-                break;
1393
-            case EEM_Registration::status_id_declined:
1394
-                $icon = $show_icons
1395
-                    ? '<span class="dashicons dashicons-no ee-icon-size-16 red-text"></span>'
1396
-                    : '';
1397
-                break;
1398
-            case EEM_Registration::status_id_wait_list:
1399
-                $icon = $show_icons
1400
-                    ? '<span class="dashicons dashicons-clipboard ee-icon-size-16 purple-text"></span>'
1401
-                    : '';
1402
-                break;
1403
-        }
1404
-        return $icon . $status[ $this->status_ID() ];
1405
-    }
1406
-
1407
-
1408
-    /**
1409
-     *        get Attendee Is Going
1410
-     */
1411
-    public function att_is_going()
1412
-    {
1413
-        return $this->get('REG_att_is_going');
1414
-    }
1415
-
1416
-
1417
-    /**
1418
-     * Gets related answers
1419
-     *
1420
-     * @param array $query_params @see
1421
-     *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1422
-     * @return EE_Answer[]|EE_Base_Class[]
1423
-     * @throws EE_Error
1424
-     * @throws InvalidArgumentException
1425
-     * @throws InvalidDataTypeException
1426
-     * @throws InvalidInterfaceException
1427
-     * @throws ReflectionException
1428
-     */
1429
-    public function answers($query_params = [])
1430
-    {
1431
-        return $this->get_many_related('Answer', $query_params);
1432
-    }
1433
-
1434
-
1435
-    /**
1436
-     * Gets the registration's answer value to the specified question
1437
-     * (either the question's ID or a question object)
1438
-     *
1439
-     * @param EE_Question|int $question
1440
-     * @param bool            $pretty_value
1441
-     * @return array|string if pretty_value= true, the result will always be a string
1442
-     * (because the answer might be an array of answer values, so passing pretty_value=true
1443
-     * will convert it into some kind of string)
1444
-     * @throws EE_Error
1445
-     * @throws InvalidArgumentException
1446
-     * @throws InvalidDataTypeException
1447
-     * @throws InvalidInterfaceException
1448
-     */
1449
-    public function answer_value_to_question($question, $pretty_value = true)
1450
-    {
1451
-        $question_id = EEM_Question::instance()->ensure_is_ID($question);
1452
-        return EEM_Answer::instance()->get_answer_value_to_question($this, $question_id, $pretty_value);
1453
-    }
1454
-
1455
-
1456
-    /**
1457
-     * question_groups
1458
-     * returns an array of EE_Question_Group objects for this registration
1459
-     *
1460
-     * @return EE_Question_Group[]
1461
-     * @throws EE_Error
1462
-     * @throws InvalidArgumentException
1463
-     * @throws InvalidDataTypeException
1464
-     * @throws InvalidInterfaceException
1465
-     * @throws ReflectionException
1466
-     */
1467
-    public function question_groups()
1468
-    {
1469
-        return EEM_Event::instance()->get_question_groups_for_event($this->event_ID(), $this);
1470
-    }
1471
-
1472
-
1473
-    /**
1474
-     * count_question_groups
1475
-     * returns a count of the number of EE_Question_Group objects for this registration
1476
-     *
1477
-     * @return int
1478
-     * @throws EE_Error
1479
-     * @throws EntityNotFoundException
1480
-     * @throws InvalidArgumentException
1481
-     * @throws InvalidDataTypeException
1482
-     * @throws InvalidInterfaceException
1483
-     * @throws ReflectionException
1484
-     */
1485
-    public function count_question_groups()
1486
-    {
1487
-        return EEM_Event::instance()->count_related(
1488
-            $this->event_ID(),
1489
-            'Question_Group',
1490
-            [
1491
-                [
1492
-                    'Event_Question_Group.'
1493
-                    . EEM_Event_Question_Group::instance()->fieldNameForContext($this->is_primary_registrant()) => true,
1494
-                ],
1495
-            ]
1496
-        );
1497
-    }
1498
-
1499
-
1500
-    /**
1501
-     * Returns the registration date in the 'standard' string format
1502
-     * (function may be improved in the future to allow for different formats and timezones)
1503
-     *
1504
-     * @return string
1505
-     * @throws EE_Error
1506
-     * @throws InvalidArgumentException
1507
-     * @throws InvalidDataTypeException
1508
-     * @throws InvalidInterfaceException
1509
-     * @throws ReflectionException
1510
-     */
1511
-    public function reg_date()
1512
-    {
1513
-        return $this->get_datetime('REG_date');
1514
-    }
1515
-
1516
-
1517
-    /**
1518
-     * Gets the datetime-ticket for this registration (ie, it can be used to isolate
1519
-     * the ticket this registration purchased, or the datetime they have registered
1520
-     * to attend)
1521
-     *
1522
-     * @return EE_Base_Class|EE_Datetime_Ticket
1523
-     * @throws EE_Error
1524
-     * @throws InvalidArgumentException
1525
-     * @throws InvalidDataTypeException
1526
-     * @throws InvalidInterfaceException
1527
-     * @throws ReflectionException
1528
-     */
1529
-    public function datetime_ticket()
1530
-    {
1531
-        return $this->get_first_related('Datetime_Ticket');
1532
-    }
1533
-
1534
-
1535
-    /**
1536
-     * Sets the registration's datetime_ticket.
1537
-     *
1538
-     * @param EE_Datetime_Ticket $datetime_ticket
1539
-     * @return EE_Base_Class|EE_Datetime_Ticket
1540
-     * @throws EE_Error
1541
-     * @throws InvalidArgumentException
1542
-     * @throws InvalidDataTypeException
1543
-     * @throws InvalidInterfaceException
1544
-     * @throws ReflectionException
1545
-     */
1546
-    public function set_datetime_ticket($datetime_ticket)
1547
-    {
1548
-        return $this->_add_relation_to($datetime_ticket, 'Datetime_Ticket');
1549
-    }
1550
-
1551
-
1552
-    /**
1553
-     * Gets deleted
1554
-     *
1555
-     * @return bool
1556
-     * @throws EE_Error
1557
-     * @throws InvalidArgumentException
1558
-     * @throws InvalidDataTypeException
1559
-     * @throws InvalidInterfaceException
1560
-     * @throws ReflectionException
1561
-     */
1562
-    public function deleted()
1563
-    {
1564
-        return $this->get('REG_deleted');
1565
-    }
1566
-
1567
-
1568
-    /**
1569
-     * Sets deleted
1570
-     *
1571
-     * @param boolean $deleted
1572
-     * @return void
1573
-     * @throws DomainException
1574
-     * @throws EE_Error
1575
-     * @throws EntityNotFoundException
1576
-     * @throws InvalidArgumentException
1577
-     * @throws InvalidDataTypeException
1578
-     * @throws InvalidInterfaceException
1579
-     * @throws ReflectionException
1580
-     * @throws RuntimeException
1581
-     * @throws UnexpectedEntityException
1582
-     */
1583
-    public function set_deleted($deleted)
1584
-    {
1585
-        if ($deleted) {
1586
-            $this->delete();
1587
-        } else {
1588
-            $this->restore();
1589
-        }
1590
-    }
1591
-
1592
-
1593
-    /**
1594
-     * Get the status object of this object
1595
-     *
1596
-     * @return EE_Base_Class|EE_Status
1597
-     * @throws EE_Error
1598
-     * @throws InvalidArgumentException
1599
-     * @throws InvalidDataTypeException
1600
-     * @throws InvalidInterfaceException
1601
-     * @throws ReflectionException
1602
-     */
1603
-    public function status_obj()
1604
-    {
1605
-        return $this->get_first_related('Status');
1606
-    }
1607
-
1608
-
1609
-    /**
1610
-     * Returns the number of times this registration has checked into any of the datetimes
1611
-     * its available for
1612
-     *
1613
-     * @return int
1614
-     * @throws EE_Error
1615
-     * @throws InvalidArgumentException
1616
-     * @throws InvalidDataTypeException
1617
-     * @throws InvalidInterfaceException
1618
-     * @throws ReflectionException
1619
-     */
1620
-    public function count_checkins()
1621
-    {
1622
-        return $this->get_model()->count_related($this, 'Checkin');
1623
-    }
1624
-
1625
-
1626
-    /**
1627
-     * Returns the number of current Check-ins this registration is checked into for any of the datetimes the
1628
-     * registration is for.  Note, this is ONLY checked in (does not include checkedout)
1629
-     *
1630
-     * @return int
1631
-     * @throws EE_Error
1632
-     * @throws InvalidArgumentException
1633
-     * @throws InvalidDataTypeException
1634
-     * @throws InvalidInterfaceException
1635
-     * @throws ReflectionException
1636
-     */
1637
-    public function count_checkins_not_checkedout()
1638
-    {
1639
-        return $this->get_model()->count_related($this, 'Checkin', [['CHK_in' => 1]]);
1640
-    }
1641
-
1642
-
1643
-    /**
1644
-     * The purpose of this method is simply to check whether this registration can checkin to the given datetime.
1645
-     *
1646
-     * @param int | EE_Datetime $DTT_OR_ID      The datetime the registration is being checked against
1647
-     * @param bool              $check_approved This is used to indicate whether the caller wants can_checkin to also
1648
-     *                                          consider registration status as well as datetime access.
1649
-     * @return bool
1650
-     * @throws EE_Error
1651
-     * @throws InvalidArgumentException
1652
-     * @throws InvalidDataTypeException
1653
-     * @throws InvalidInterfaceException
1654
-     * @throws ReflectionException
1655
-     */
1656
-    public function can_checkin($DTT_OR_ID, $check_approved = true)
1657
-    {
1658
-        $DTT_ID = EEM_Datetime::instance()->ensure_is_ID($DTT_OR_ID);
1659
-        // first check registration status
1660
-        if (! $DTT_ID || ($check_approved && ! $this->is_approved())) {
1661
-            return false;
1662
-        }
1663
-        // is there a datetime ticket that matches this dtt_ID?
1664
-        if (
1665
-            ! (EEM_Datetime_Ticket::instance()->exists(
1666
-                [
1667
-                    [
1668
-                        'TKT_ID' => $this->get('TKT_ID'),
1669
-                        'DTT_ID' => $DTT_ID,
1670
-                    ],
1671
-                ]
1672
-            ))
1673
-        ) {
1674
-            return false;
1675
-        }
1676
-
1677
-        // final check is against TKT_uses
1678
-        return $this->verify_can_checkin_against_TKT_uses($DTT_ID);
1679
-    }
1680
-
1681
-
1682
-    /**
1683
-     * This method verifies whether the user can checkin for the given datetime considering the max uses value set on
1684
-     * the ticket. To do this,  a query is done to get the count of the datetime records already checked into.  If the
1685
-     * datetime given does not have a check-in record and checking in for that datetime will exceed the allowed uses,
1686
-     * then return false.  Otherwise return true.
1687
-     *
1688
-     * @param int | EE_Datetime $DTT_OR_ID The datetime the registration is being checked against
1689
-     * @return bool true means can checkin.  false means cannot checkin.
1690
-     * @throws EE_Error
1691
-     * @throws InvalidArgumentException
1692
-     * @throws InvalidDataTypeException
1693
-     * @throws InvalidInterfaceException
1694
-     * @throws ReflectionException
1695
-     */
1696
-    public function verify_can_checkin_against_TKT_uses($DTT_OR_ID)
1697
-    {
1698
-        $DTT_ID = EEM_Datetime::instance()->ensure_is_ID($DTT_OR_ID);
1699
-
1700
-        if (! $DTT_ID) {
1701
-            return false;
1702
-        }
1703
-
1704
-        $max_uses = $this->ticket() instanceof EE_Ticket
1705
-            ? $this->ticket()->uses()
1706
-            : EE_INF;
1707
-
1708
-        // if max uses is not set or equals infinity then return true cause its not a factor for whether user can
1709
-        // check-in or not.
1710
-        if (! $max_uses || $max_uses === EE_INF) {
1711
-            return true;
1712
-        }
1713
-
1714
-        // does this datetime have a checkin record?  If so, then the dtt count has already been verified so we can just
1715
-        // go ahead and toggle.
1716
-        if (EEM_Checkin::instance()->exists([['REG_ID' => $this->ID(), 'DTT_ID' => $DTT_ID]])) {
1717
-            return true;
1718
-        }
1719
-
1720
-        // made it here so the last check is whether the number of checkins per unique datetime on this registration
1721
-        // disallows further check-ins.
1722
-        $count_unique_dtt_checkins = EEM_Checkin::instance()->count(
1723
-            [
1724
-                [
1725
-                    'REG_ID' => $this->ID(),
1726
-                    'CHK_in' => true,
1727
-                ],
1728
-            ],
1729
-            'DTT_ID',
1730
-            true
1731
-        );
1732
-        // checkins have already reached their max number of uses
1733
-        // so registrant can NOT checkin
1734
-        if ($count_unique_dtt_checkins >= $max_uses) {
1735
-            EE_Error::add_error(
1736
-                esc_html__(
1737
-                    'Check-in denied because number of datetime uses for the ticket has been reached or exceeded.',
1738
-                    'event_espresso'
1739
-                ),
1740
-                __FILE__,
1741
-                __FUNCTION__,
1742
-                __LINE__
1743
-            );
1744
-            return false;
1745
-        }
1746
-        return true;
1747
-    }
1748
-
1749
-
1750
-    /**
1751
-     * toggle Check-in status for this registration
1752
-     * Check-ins are toggled in the following order:
1753
-     * never checked in -> checked in
1754
-     * checked in -> checked out
1755
-     * checked out -> checked in
1756
-     *
1757
-     * @param int  $DTT_ID  include specific datetime to toggle Check-in for.
1758
-     *                      If not included or null, then it is assumed latest datetime is being toggled.
1759
-     * @param bool $verify  If true then can_checkin() is used to verify whether the person
1760
-     *                      can be checked in or not.  Otherwise this forces change in checkin status.
1761
-     * @return bool|int     the chk_in status toggled to OR false if nothing got changed.
1762
-     * @throws EE_Error
1763
-     * @throws InvalidArgumentException
1764
-     * @throws InvalidDataTypeException
1765
-     * @throws InvalidInterfaceException
1766
-     * @throws ReflectionException
1767
-     */
1768
-    public function toggle_checkin_status($DTT_ID = null, $verify = false)
1769
-    {
1770
-        if (empty($DTT_ID)) {
1771
-            $datetime = $this->get_latest_related_datetime();
1772
-            $DTT_ID   = $datetime instanceof EE_Datetime ? $datetime->ID() : 0;
1773
-            // verify the registration can checkin for the given DTT_ID
1774
-        } elseif (! $this->can_checkin($DTT_ID, $verify)) {
1775
-            EE_Error::add_error(
1776
-                sprintf(
1777
-                    esc_html__(
1778
-                        'The given registration (ID:%1$d) can not be checked in to the given DTT_ID (%2$d), because the registration does not have access',
1779
-                        'event_espresso'
1780
-                    ),
1781
-                    $this->ID(),
1782
-                    $DTT_ID
1783
-                ),
1784
-                __FILE__,
1785
-                __FUNCTION__,
1786
-                __LINE__
1787
-            );
1788
-            return false;
1789
-        }
1790
-        $status_paths = [
1791
-            EE_Checkin::status_checked_never => EE_Checkin::status_checked_in,
1792
-            EE_Checkin::status_checked_in    => EE_Checkin::status_checked_out,
1793
-            EE_Checkin::status_checked_out   => EE_Checkin::status_checked_in,
1794
-        ];
1795
-        // start by getting the current status so we know what status we'll be changing to.
1796
-        $cur_status = $this->check_in_status_for_datetime($DTT_ID);
1797
-        $status_to  = $status_paths[ $cur_status ];
1798
-        // database only records true for checked IN or false for checked OUT
1799
-        // no record ( null ) means checked in NEVER, but we obviously don't save that
1800
-        $new_status = $status_to === EE_Checkin::status_checked_in;
1801
-        // add relation - note Check-ins are always creating new rows
1802
-        // because we are keeping track of Check-ins over time.
1803
-        // Eventually we'll probably want to show a list table
1804
-        // for the individual Check-ins so that they can be managed.
1805
-        $checkin = EE_Checkin::new_instance(
1806
-            [
1807
-                'REG_ID' => $this->ID(),
1808
-                'DTT_ID' => $DTT_ID,
1809
-                'CHK_in' => $new_status,
1810
-            ]
1811
-        );
1812
-        // if the record could not be saved then return false
1813
-        if ($checkin->save() === 0) {
1814
-            if (WP_DEBUG) {
1815
-                global $wpdb;
1816
-                $error = sprintf(
1817
-                    esc_html__(
1818
-                        'Registration check in update failed because of the following database error: %1$s%2$s',
1819
-                        'event_espresso'
1820
-                    ),
1821
-                    '<br />',
1822
-                    $wpdb->last_error
1823
-                );
1824
-            } else {
1825
-                $error = esc_html__(
1826
-                    'Registration check in update failed because of an unknown database error',
1827
-                    'event_espresso'
1828
-                );
1829
-            }
1830
-            EE_Error::add_error($error, __FILE__, __FUNCTION__, __LINE__);
1831
-            return false;
1832
-        }
1833
-        // Fire a checked_in and checkout_out action.
1834
-        $checked_status = $status_to === EE_Checkin::status_checked_in
1835
-            ? 'checked_in'
1836
-            : 'checked_out';
1837
-        do_action("AHEE__EE_Registration__toggle_checkin_status__{$checked_status}", $this, $DTT_ID);
1838
-        return $status_to;
1839
-    }
1840
-
1841
-
1842
-    /**
1843
-     * Returns the latest datetime related to this registration (via the ticket attached to the registration).
1844
-     * "Latest" is defined by the `DTT_EVT_start` column.
1845
-     *
1846
-     * @return EE_Datetime|null
1847
-     * @throws EE_Error
1848
-     * @throws InvalidArgumentException
1849
-     * @throws InvalidDataTypeException
1850
-     * @throws InvalidInterfaceException
1851
-     * @throws ReflectionException
1852
-     */
1853
-    public function get_latest_related_datetime(): ?EE_Datetime
1854
-    {
1855
-        return EEM_Datetime::instance()->get_one(
1856
-            [
1857
-                [
1858
-                    'Ticket.Registration.REG_ID' => $this->ID(),
1859
-                ],
1860
-                'order_by' => ['DTT_EVT_start' => 'DESC'],
1861
-            ]
1862
-        );
1863
-    }
1864
-
1865
-
1866
-    /**
1867
-     * Returns the earliest datetime related to this registration (via the ticket attached to the registration).
1868
-     * "Earliest" is defined by the `DTT_EVT_start` column.
1869
-     *
1870
-     * @return EE_Base_Class|EE_Soft_Delete_Base_Class|NULL
1871
-     * @throws EE_Error
1872
-     * @throws InvalidArgumentException
1873
-     * @throws InvalidDataTypeException
1874
-     * @throws InvalidInterfaceException
1875
-     * @throws ReflectionException
1876
-     */
1877
-    public function get_earliest_related_datetime()
1878
-    {
1879
-        return EEM_Datetime::instance()->get_one(
1880
-            [
1881
-                [
1882
-                    'Ticket.Registration.REG_ID' => $this->ID(),
1883
-                ],
1884
-                'order_by' => ['DTT_EVT_start' => 'ASC'],
1885
-            ]
1886
-        );
1887
-    }
1888
-
1889
-
1890
-    /**
1891
-     * This method simply returns the check-in status for this registration and the given datetime.
1892
-     * If neither the datetime nor the checkin values are provided as arguments,
1893
-     * then this will return the LATEST check-in status for the registration across all datetimes it belongs to.
1894
-     *
1895
-     * @param int|null        $DTT_ID  The ID of the datetime we're checking against
1896
-     *                                 (if empty we'll get the primary datetime for
1897
-     *                                 this registration (via event) and use it's ID);
1898
-     * @param EE_Checkin|null $checkin If present, we use the given checkin object rather than the dtt_id.
1899
-     * @return int                     Integer representing Check-in status.
1900
-     * @throws EE_Error
1901
-     * @throws ReflectionException
1902
-     */
1903
-    public function check_in_status_for_datetime(?int $DTT_ID = 0, ?EE_Checkin $checkin = null): int
1904
-    {
1905
-        if ($checkin instanceof EE_Checkin) {
1906
-            return $checkin->status();
1907
-        }
1908
-
1909
-        if (! $DTT_ID) {
1910
-            return EE_Checkin::status_invalid;
1911
-        }
1912
-
1913
-        $checkin_query_params = [
1914
-            0          => ['DTT_ID' => $DTT_ID],
1915
-            'order_by' => ['CHK_timestamp' => 'DESC'],
1916
-        ];
1917
-
1918
-        $checkin = $this->get_first_related(
1919
-            'Checkin',
1920
-            $checkin_query_params
1921
-        );
1922
-        return $checkin instanceof EE_Checkin ? $checkin->status() : EE_Checkin::status_checked_never;
1923
-    }
1924
-
1925
-
1926
-    /**
1927
-     * This method returns a localized message for the toggled Check-in message.
1928
-     *
1929
-     * @param int|null $DTT_ID include specific datetime to get the correct Check-in message.  If not included or null,
1930
-     *                         then it is assumed Check-in for primary datetime was toggled.
1931
-     * @param bool     $error  This just flags that you want an error message returned. This is put in so that the error
1932
-     *                         message can be customized with the attendee name.
1933
-     * @return string internationalized message
1934
-     * @throws EE_Error
1935
-     * @throws ReflectionException
1936
-     */
1937
-    public function get_checkin_msg(?int $DTT_ID, bool $error = false): string
1938
-    {
1939
-        // let's get the attendee first so we can include the name of the attendee
1940
-        $attendee = $this->attendee();
1941
-        if ($attendee instanceof EE_Attendee) {
1942
-            if ($error) {
1943
-                return sprintf(
1944
-                    esc_html__("%s's check-in status was not changed.", "event_espresso"),
1945
-                    $attendee->full_name()
1946
-                );
1947
-            }
1948
-            $cur_status = $this->check_in_status_for_datetime($DTT_ID);
1949
-            // what is the status message going to be?
1950
-            switch ($cur_status) {
1951
-                case EE_Checkin::status_checked_never:
1952
-                    return sprintf(
1953
-                        esc_html__('%s has been removed from Check-in records', 'event_espresso'),
1954
-                        $attendee->full_name()
1955
-                    );
1956
-                case EE_Checkin::status_checked_in:
1957
-                    return sprintf(esc_html__('%s has been checked in', 'event_espresso'), $attendee->full_name());
1958
-                case EE_Checkin::status_checked_out:
1959
-                    return sprintf(esc_html__('%s has been checked out', 'event_espresso'), $attendee->full_name());
1960
-            }
1961
-        }
1962
-        return esc_html__('The check-in status could not be determined.', 'event_espresso');
1963
-    }
1964
-
1965
-
1966
-    /**
1967
-     * Returns the related EE_Transaction to this registration
1968
-     *
1969
-     * @return EE_Transaction
1970
-     * @throws EE_Error
1971
-     * @throws EntityNotFoundException
1972
-     * @throws ReflectionException
1973
-     */
1974
-    public function transaction(): EE_Transaction
1975
-    {
1976
-        $transaction = EEM_Transaction::instance()->get_one_by_ID($this->transaction_ID());
1977
-        if (! $transaction instanceof \EE_Transaction) {
1978
-            throw new EntityNotFoundException('Transaction ID', $this->transaction_ID());
1979
-        }
1980
-        return $transaction;
1981
-    }
1982
-
1983
-
1984
-    /**
1985
-     * get Registration Code
1986
-     *
1987
-     * @return string
1988
-     * @throws EE_Error
1989
-     * @throws InvalidArgumentException
1990
-     * @throws InvalidDataTypeException
1991
-     * @throws InvalidInterfaceException
1992
-     * @throws ReflectionException
1993
-     */
1994
-    public function reg_code(): string
1995
-    {
1996
-        return $this->get('REG_code')
1997
-            ?: '';
1998
-    }
1999
-
2000
-
2001
-    /**
2002
-     * @return mixed
2003
-     * @throws EE_Error
2004
-     * @throws InvalidArgumentException
2005
-     * @throws InvalidDataTypeException
2006
-     * @throws InvalidInterfaceException
2007
-     * @throws ReflectionException
2008
-     */
2009
-    public function transaction_ID()
2010
-    {
2011
-        return $this->get('TXN_ID');
2012
-    }
2013
-
2014
-
2015
-    /**
2016
-     * @return int
2017
-     * @throws EE_Error
2018
-     * @throws InvalidArgumentException
2019
-     * @throws InvalidDataTypeException
2020
-     * @throws InvalidInterfaceException
2021
-     * @throws ReflectionException
2022
-     */
2023
-    public function ticket_ID()
2024
-    {
2025
-        return $this->get('TKT_ID');
2026
-    }
2027
-
2028
-
2029
-    /**
2030
-     * Set Registration Code
2031
-     *
2032
-     * @param RegCode|string $REG_code Registration Code
2033
-     * @param boolean        $use_default
2034
-     * @throws EE_Error
2035
-     * @throws InvalidArgumentException
2036
-     * @throws InvalidDataTypeException
2037
-     * @throws InvalidInterfaceException
2038
-     * @throws ReflectionException
2039
-     */
2040
-    public function set_reg_code($REG_code, bool $use_default = false)
2041
-    {
2042
-        if (empty($REG_code)) {
2043
-            EE_Error::add_error(
2044
-                esc_html__('REG_code can not be empty.', 'event_espresso'),
2045
-                __FILE__,
2046
-                __FUNCTION__,
2047
-                __LINE__
2048
-            );
2049
-            return;
2050
-        }
2051
-        if (! $this->reg_code()) {
2052
-            parent::set('REG_code', $REG_code, $use_default);
2053
-        } else {
2054
-            EE_Error::doing_it_wrong(
2055
-                __CLASS__ . '::' . __FUNCTION__,
2056
-                esc_html__('Can not change a registration REG_code once it has been set.', 'event_espresso'),
2057
-                '4.6.0'
2058
-            );
2059
-        }
2060
-    }
2061
-
2062
-
2063
-    /**
2064
-     * Returns all other registrations in the same group as this registrant who have the same ticket option.
2065
-     * Note, if you want to just get all registrations in the same transaction (group), use:
2066
-     *    $registration->transaction()->registrations();
2067
-     *
2068
-     * @return EE_Registration[] or empty array if this isn't a group registration.
2069
-     * @throws EE_Error
2070
-     * @throws InvalidArgumentException
2071
-     * @throws InvalidDataTypeException
2072
-     * @throws InvalidInterfaceException
2073
-     * @throws ReflectionException
2074
-     * @since 4.5.0
2075
-     */
2076
-    public function get_all_other_registrations_in_group()
2077
-    {
2078
-        if ($this->group_size() < 2) {
2079
-            return [];
2080
-        }
2081
-
2082
-        $query[0] = [
2083
-            'TXN_ID' => $this->transaction_ID(),
2084
-            'REG_ID' => ['!=', $this->ID()],
2085
-            'TKT_ID' => $this->ticket_ID(),
2086
-        ];
2087
-        /** @var EE_Registration[] $registrations */
2088
-        $registrations = $this->get_model()->get_all($query);
2089
-        return $registrations;
2090
-    }
2091
-
2092
-
2093
-    /**
2094
-     * Return the link to the admin details for the object.
2095
-     *
2096
-     * @return string
2097
-     * @throws EE_Error
2098
-     * @throws InvalidArgumentException
2099
-     * @throws InvalidDataTypeException
2100
-     * @throws InvalidInterfaceException
2101
-     * @throws ReflectionException
2102
-     */
2103
-    public function get_admin_details_link()
2104
-    {
2105
-        EE_Registry::instance()->load_helper('URL');
2106
-        return EEH_URL::add_query_args_and_nonce(
2107
-            [
2108
-                'page'    => 'espresso_registrations',
2109
-                'action'  => 'view_registration',
2110
-                '_REG_ID' => $this->ID(),
2111
-            ],
2112
-            admin_url('admin.php')
2113
-        );
2114
-    }
2115
-
2116
-
2117
-    /**
2118
-     * Returns the link to the editor for the object.  Sometimes this is the same as the details.
2119
-     *
2120
-     * @return string
2121
-     * @throws EE_Error
2122
-     * @throws InvalidArgumentException
2123
-     * @throws InvalidDataTypeException
2124
-     * @throws InvalidInterfaceException
2125
-     * @throws ReflectionException
2126
-     */
2127
-    public function get_admin_edit_link()
2128
-    {
2129
-        return $this->get_admin_details_link();
2130
-    }
2131
-
2132
-
2133
-    /**
2134
-     * Returns the link to a settings page for the object.
2135
-     *
2136
-     * @return string
2137
-     * @throws EE_Error
2138
-     * @throws InvalidArgumentException
2139
-     * @throws InvalidDataTypeException
2140
-     * @throws InvalidInterfaceException
2141
-     * @throws ReflectionException
2142
-     */
2143
-    public function get_admin_settings_link()
2144
-    {
2145
-        return $this->get_admin_details_link();
2146
-    }
2147
-
2148
-
2149
-    /**
2150
-     * Returns the link to the "overview" for the object (typically the "list table" view).
2151
-     *
2152
-     * @return string
2153
-     * @throws EE_Error
2154
-     * @throws InvalidArgumentException
2155
-     * @throws InvalidDataTypeException
2156
-     * @throws InvalidInterfaceException
2157
-     * @throws ReflectionException
2158
-     */
2159
-    public function get_admin_overview_link()
2160
-    {
2161
-        EE_Registry::instance()->load_helper('URL');
2162
-        return EEH_URL::add_query_args_and_nonce(
2163
-            [
2164
-                'page' => 'espresso_registrations',
2165
-            ],
2166
-            admin_url('admin.php')
2167
-        );
2168
-    }
2169
-
2170
-
2171
-    /**
2172
-     * @param array $query_params
2173
-     * @return EE_Base_Class[]|EE_Registration[]
2174
-     * @throws EE_Error
2175
-     * @throws InvalidArgumentException
2176
-     * @throws InvalidDataTypeException
2177
-     * @throws InvalidInterfaceException
2178
-     * @throws ReflectionException
2179
-     */
2180
-    public function payments($query_params = [])
2181
-    {
2182
-        return $this->get_many_related('Payment', $query_params);
2183
-    }
2184
-
2185
-
2186
-    /**
2187
-     * @param array $query_params
2188
-     * @return EE_Base_Class[]|EE_Registration_Payment[]
2189
-     * @throws EE_Error
2190
-     * @throws InvalidArgumentException
2191
-     * @throws InvalidDataTypeException
2192
-     * @throws InvalidInterfaceException
2193
-     * @throws ReflectionException
2194
-     */
2195
-    public function registration_payments($query_params = [])
2196
-    {
2197
-        return $this->get_many_related('Registration_Payment', $query_params);
2198
-    }
2199
-
2200
-
2201
-    /**
2202
-     * This grabs the payment method corresponding to the last payment made for the amount owing on the registration.
2203
-     * Note: if there are no payments on the registration there will be no payment method returned.
2204
-     *
2205
-     * @return EE_Payment|EE_Payment_Method|null
2206
-     * @throws EE_Error
2207
-     * @throws InvalidArgumentException
2208
-     * @throws InvalidDataTypeException
2209
-     * @throws InvalidInterfaceException
2210
-     */
2211
-    public function payment_method()
2212
-    {
2213
-        return EEM_Payment_Method::instance()->get_last_used_for_registration($this);
2214
-    }
2215
-
2216
-
2217
-    /**
2218
-     * @return \EE_Line_Item
2219
-     * @throws EE_Error
2220
-     * @throws EntityNotFoundException
2221
-     * @throws InvalidArgumentException
2222
-     * @throws InvalidDataTypeException
2223
-     * @throws InvalidInterfaceException
2224
-     * @throws ReflectionException
2225
-     */
2226
-    public function ticket_line_item()
2227
-    {
2228
-        $ticket            = $this->ticket();
2229
-        $transaction       = $this->transaction();
2230
-        $line_item         = null;
2231
-        $ticket_line_items = \EEH_Line_Item::get_line_items_by_object_type_and_IDs(
2232
-            $transaction->total_line_item(),
2233
-            'Ticket',
2234
-            [$ticket->ID()]
2235
-        );
2236
-        foreach ($ticket_line_items as $ticket_line_item) {
2237
-            if (
2238
-                $ticket_line_item instanceof \EE_Line_Item
2239
-                && $ticket_line_item->OBJ_type() === 'Ticket'
2240
-                && $ticket_line_item->OBJ_ID() === $ticket->ID()
2241
-            ) {
2242
-                $line_item = $ticket_line_item;
2243
-                break;
2244
-            }
2245
-        }
2246
-        if (! ($line_item instanceof \EE_Line_Item && $line_item->OBJ_type() === 'Ticket')) {
2247
-            throw new EntityNotFoundException('Line Item Ticket ID', $ticket->ID());
2248
-        }
2249
-        return $line_item;
2250
-    }
2251
-
2252
-
2253
-    /**
2254
-     * Soft Deletes this model object.
2255
-     *
2256
-     * @param string $source function name that called this method
2257
-     * @return boolean | int
2258
-     * @throws DomainException
2259
-     * @throws EE_Error
2260
-     * @throws EntityNotFoundException
2261
-     * @throws InvalidArgumentException
2262
-     * @throws InvalidDataTypeException
2263
-     * @throws InvalidInterfaceException
2264
-     * @throws ReflectionException
2265
-     * @throws RuntimeException
2266
-     * @throws UnexpectedEntityException
2267
-     */
2268
-    public function delete()
2269
-    {
2270
-        if ($this->update_extra_meta(EE_Registration::PRE_TRASH_REG_STATUS_KEY, $this->status_ID()) === true) {
2271
-            $this->set_status(EEM_Registration::status_id_cancelled);
2272
-        }
2273
-        return parent::delete();
2274
-    }
2275
-
2276
-
2277
-    /**
2278
-     * Restores whatever the previous status was on a registration before it was trashed (if possible)
2279
-     *
2280
-     * @param string $source function name that called this method
2281
-     * @return bool|int
2282
-     * @throws DomainException
2283
-     * @throws EE_Error
2284
-     * @throws EntityNotFoundException
2285
-     * @throws InvalidArgumentException
2286
-     * @throws InvalidDataTypeException
2287
-     * @throws InvalidInterfaceException
2288
-     * @throws ReflectionException
2289
-     * @throws RuntimeException
2290
-     * @throws UnexpectedEntityException
2291
-     */
2292
-    public function restore()
2293
-    {
2294
-        $previous_status = $this->get_extra_meta(
2295
-            EE_Registration::PRE_TRASH_REG_STATUS_KEY,
2296
-            true,
2297
-            EEM_Registration::status_id_cancelled
2298
-        );
2299
-        if ($previous_status) {
2300
-            $this->delete_extra_meta(EE_Registration::PRE_TRASH_REG_STATUS_KEY);
2301
-            $this->set_status($previous_status);
2302
-        }
2303
-        return parent::restore();
2304
-    }
2305
-
2306
-
2307
-    /**
2308
-     * possibly toggle Registration status based on comparison of REG_paid vs REG_final_price
2309
-     *
2310
-     * @param boolean $trigger_set_status_logic  EE_Registration::set_status() can trigger additional logic
2311
-     *                                           depending on whether the reg status changes to or from "Approved"
2312
-     * @return boolean whether the Registration status was updated
2313
-     * @throws DomainException
2314
-     * @throws EE_Error
2315
-     * @throws EntityNotFoundException
2316
-     * @throws InvalidArgumentException
2317
-     * @throws InvalidDataTypeException
2318
-     * @throws InvalidInterfaceException
2319
-     * @throws ReflectionException
2320
-     * @throws RuntimeException
2321
-     * @throws UnexpectedEntityException
2322
-     */
2323
-    public function updateStatusBasedOnTotalPaid($trigger_set_status_logic = true)
2324
-    {
2325
-        $paid  = $this->paid();
2326
-        $price = $this->final_price();
2327
-        switch (true) {
2328
-            // overpaid or paid
2329
-            case EEH_Money::compare_floats($paid, $price, '>'):
2330
-            case EEH_Money::compare_floats($paid, $price):
2331
-                $new_status = EEM_Registration::status_id_approved;
2332
-                break;
2333
-            //  underpaid
2334
-            case EEH_Money::compare_floats($paid, $price, '<'):
2335
-                $new_status = EEM_Registration::status_id_pending_payment;
2336
-                break;
2337
-            // uhhh Houston...
2338
-            default:
2339
-                throw new RuntimeException(
2340
-                    esc_html__('The total paid calculation for this registration is inaccurate.', 'event_espresso')
2341
-                );
2342
-        }
2343
-        if ($new_status !== $this->status_ID()) {
2344
-            if ($trigger_set_status_logic) {
2345
-                return $this->set_status($new_status);
2346
-            }
2347
-            parent::set('STS_ID', $new_status);
2348
-            return true;
2349
-        }
2350
-        return false;
2351
-    }
2352
-
2353
-
2354
-    /*************************** DEPRECATED ***************************/
2355
-
2356
-
2357
-    /**
2358
-     * @deprecated
2359
-     * @since     4.7.0
2360
-     */
2361
-    public function price_paid()
2362
-    {
2363
-        EE_Error::doing_it_wrong(
2364
-            'EE_Registration::price_paid()',
2365
-            esc_html__(
2366
-                'This method is deprecated, please use EE_Registration::final_price() instead.',
2367
-                'event_espresso'
2368
-            ),
2369
-            '4.7.0'
2370
-        );
2371
-        return $this->final_price();
2372
-    }
2373
-
2374
-
2375
-    /**
2376
-     * @param float $REG_final_price
2377
-     * @throws EE_Error
2378
-     * @throws EntityNotFoundException
2379
-     * @throws InvalidArgumentException
2380
-     * @throws InvalidDataTypeException
2381
-     * @throws InvalidInterfaceException
2382
-     * @throws ReflectionException
2383
-     * @throws RuntimeException
2384
-     * @throws DomainException
2385
-     * @deprecated
2386
-     * @since     4.7.0
2387
-     */
2388
-    public function set_price_paid($REG_final_price = 0.00)
2389
-    {
2390
-        EE_Error::doing_it_wrong(
2391
-            'EE_Registration::set_price_paid()',
2392
-            esc_html__(
2393
-                'This method is deprecated, please use EE_Registration::set_final_price() instead.',
2394
-                'event_espresso'
2395
-            ),
2396
-            '4.7.0'
2397
-        );
2398
-        $this->set_final_price($REG_final_price);
2399
-    }
2400
-
2401
-
2402
-    /**
2403
-     * @return string
2404
-     * @throws EE_Error
2405
-     * @throws InvalidArgumentException
2406
-     * @throws InvalidDataTypeException
2407
-     * @throws InvalidInterfaceException
2408
-     * @throws ReflectionException
2409
-     * @deprecated
2410
-     * @since 4.7.0
2411
-     */
2412
-    public function pretty_price_paid()
2413
-    {
2414
-        EE_Error::doing_it_wrong(
2415
-            'EE_Registration::pretty_price_paid()',
2416
-            esc_html__(
2417
-                'This method is deprecated, please use EE_Registration::pretty_final_price() instead.',
2418
-                'event_espresso'
2419
-            ),
2420
-            '4.7.0'
2421
-        );
2422
-        return $this->pretty_final_price();
2423
-    }
2424
-
2425
-
2426
-    /**
2427
-     * Gets the primary datetime related to this registration via the related Event to this registration
2428
-     *
2429
-     * @return EE_Datetime
2430
-     * @throws EE_Error
2431
-     * @throws EntityNotFoundException
2432
-     * @throws InvalidArgumentException
2433
-     * @throws InvalidDataTypeException
2434
-     * @throws InvalidInterfaceException
2435
-     * @throws ReflectionException
2436
-     * @deprecated 4.9.17
2437
-     */
2438
-    public function get_related_primary_datetime()
2439
-    {
2440
-        EE_Error::doing_it_wrong(
2441
-            __METHOD__,
2442
-            esc_html__(
2443
-                'Use EE_Registration::get_latest_related_datetime() or EE_Registration::get_earliest_related_datetime()',
2444
-                'event_espresso'
2445
-            ),
2446
-            '4.9.17',
2447
-            '5.0.0'
2448
-        );
2449
-        return $this->event()->primary_datetime();
2450
-    }
2451
-
2452
-
2453
-    /**
2454
-     * Returns the contact's name (or "Unknown" if there is no contact.)
2455
-     *
2456
-     * @return string
2457
-     * @throws EE_Error
2458
-     * @throws InvalidArgumentException
2459
-     * @throws InvalidDataTypeException
2460
-     * @throws InvalidInterfaceException
2461
-     * @throws ReflectionException
2462
-     * @since 4.10.12.p
2463
-     */
2464
-    public function name()
2465
-    {
2466
-        return $this->attendeeName();
2467
-    }
2468
-
2469
-
2470
-    /**
2471
-     * @return bool
2472
-     * @throws EE_Error
2473
-     * @throws ReflectionException
2474
-     */
2475
-    public function wasMoved(): bool
2476
-    {
2477
-        // only need to check 'registration-moved-to' because
2478
-        // the existence of a new REG ID means the registration was moved
2479
-        $reg_moved = $this->get_extra_meta('registration-moved-to', true, []);
2480
-        return isset($reg_moved['NEW_REG_ID']) && $reg_moved['NEW_REG_ID'];
2481
-    }
2482
-
2483
-
2484
-    /**
2485
-     * @param EE_Payment $payment
2486
-     * @param float|null $amount
2487
-     * @return float
2488
-     * @throws EE_Error
2489
-     * @throws ReflectionException
2490
-     * @since 5.0.8.p
2491
-     */
2492
-    public function applyPayment(EE_Payment $payment, ?float $amount = null): float
2493
-    {
2494
-        $payment_amount = $amount ?? $payment->amount();
2495
-        // ensure $payment_amount is NOT negative
2496
-        $payment_amount = (float) abs($payment_amount);
2497
-        $payment_amount = $payment->is_a_refund()
2498
-            ? $this->processRefund($payment_amount)
2499
-            : $this->processPayment($payment_amount);
2500
-        if ($payment_amount) {
2501
-            $reg_payment = EEM_Registration_Payment::instance()->get_one(
2502
-                [['REG_ID' => $this->ID(), 'PAY_ID' => $payment->ID()]]
2503
-            );
2504
-            // if existing registration payment exists
2505
-            if ($reg_payment instanceof EE_Registration_Payment) {
2506
-                // then update that record
2507
-                $reg_payment->set_amount($payment_amount);
2508
-            } else {
2509
-                // or add new relation between registration and payment and set amount
2510
-                $reg_payment = EE_Registration_Payment::new_instance(
2511
-                    [
2512
-                        'REG_ID'     => $this->ID(),
2513
-                        'PAY_ID'     => $payment->ID(),
2514
-                        'RPY_amount' => $payment_amount,
2515
-                    ]
2516
-                );
2517
-            }
2518
-            $reg_payment->save();
2519
-        }
2520
-        return $payment_amount;
2521
-    }
2522
-
2523
-
2524
-    /**
2525
-     * @throws EE_Error
2526
-     * @throws ReflectionException
2527
-     */
2528
-    private function processPayment(float $payment_amount): float
2529
-    {
2530
-        $paid  = $this->paid();
2531
-        $owing = $this->final_price() - $paid;
2532
-        if ($owing <= 0) {
2533
-            return 0.0;
2534
-        }
2535
-        // don't allow payment amount to exceed the incoming amount, OR the amount owing
2536
-        $payment_amount = min($payment_amount, $owing);
2537
-        $paid           = $paid + $payment_amount;
2538
-        // calculate and set new REG_paid
2539
-        $this->set_paid($paid);
2540
-        // make it stick
2541
-        $this->save();
2542
-        return (float) $payment_amount;
2543
-    }
2544
-
2545
-
2546
-    /**
2547
-     * @throws ReflectionException
2548
-     * @throws EE_Error
2549
-     */
2550
-    private function processRefund(float $payment_amount): float
2551
-    {
2552
-        $paid = $this->paid();
2553
-        if ($paid <= 0) {
2554
-            return 0.0;
2555
-        }
2556
-        // don't allow refund amount to exceed the incoming amount, OR the amount paid
2557
-        $payment_amount = min($payment_amount, $paid);
2558
-        // calculate and set new REG_paid
2559
-        $paid = $paid - $payment_amount;
2560
-        $this->set_paid($paid);
2561
-        // make it stick
2562
-        $this->save();
2563
-        // convert payment amount back to a negative value for storage in the db
2564
-        return (float) $payment_amount;
2565
-    }
20
+	/**
21
+	 * Used to reference when a registration has never been checked in.
22
+	 *
23
+	 * @deprecated use \EE_Checkin::status_checked_never instead
24
+	 * @type int
25
+	 */
26
+	const checkin_status_never = 2;
27
+
28
+	/**
29
+	 * Used to reference when a registration has been checked in.
30
+	 *
31
+	 * @deprecated use \EE_Checkin::status_checked_in instead
32
+	 * @type int
33
+	 */
34
+	const checkin_status_in = 1;
35
+
36
+	/**
37
+	 * Used to reference when a registration has been checked out.
38
+	 *
39
+	 * @deprecated use \EE_Checkin::status_checked_out instead
40
+	 * @type int
41
+	 */
42
+	const checkin_status_out = 0;
43
+
44
+	/**
45
+	 * extra meta key for tracking reg status os trashed registrations
46
+	 *
47
+	 * @type string
48
+	 */
49
+	const PRE_TRASH_REG_STATUS_KEY = 'pre_trash_registration_status';
50
+
51
+	/**
52
+	 * extra meta key for tracking if registration has reserved ticket
53
+	 *
54
+	 * @type string
55
+	 */
56
+	const HAS_RESERVED_TICKET_KEY = 'has_reserved_ticket';
57
+
58
+
59
+	/**
60
+	 * @param array  $props_n_values          incoming values
61
+	 * @param string $timezone                incoming timezone (if not set the timezone set for the website will be
62
+	 *                                        used.)
63
+	 * @param array  $date_formats            incoming date_formats in an array where the first value is the
64
+	 *                                        date_format and the second value is the time format
65
+	 * @return EE_Registration
66
+	 * @throws EE_Error
67
+	 * @throws InvalidArgumentException
68
+	 * @throws InvalidDataTypeException
69
+	 * @throws InvalidInterfaceException
70
+	 * @throws ReflectionException
71
+	 */
72
+	public static function new_instance($props_n_values = [], $timezone = '', $date_formats = [])
73
+	{
74
+		$has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
75
+		return $has_object
76
+			?: new self($props_n_values, false, $timezone, $date_formats);
77
+	}
78
+
79
+
80
+	/**
81
+	 * @param array  $props_n_values  incoming values from the database
82
+	 * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
83
+	 *                                the website will be used.
84
+	 * @return EE_Registration
85
+	 * @throws EE_Error
86
+	 * @throws InvalidArgumentException
87
+	 * @throws InvalidDataTypeException
88
+	 * @throws InvalidInterfaceException
89
+	 * @throws ReflectionException
90
+	 */
91
+	public static function new_instance_from_db($props_n_values = [], $timezone = '')
92
+	{
93
+		return new self($props_n_values, true, $timezone);
94
+	}
95
+
96
+
97
+	/**
98
+	 *        Set Event ID
99
+	 *
100
+	 * @param int $EVT_ID Event ID
101
+	 * @throws DomainException
102
+	 * @throws EE_Error
103
+	 * @throws EntityNotFoundException
104
+	 * @throws InvalidArgumentException
105
+	 * @throws InvalidDataTypeException
106
+	 * @throws InvalidInterfaceException
107
+	 * @throws ReflectionException
108
+	 * @throws RuntimeException
109
+	 * @throws UnexpectedEntityException
110
+	 */
111
+	public function set_event($EVT_ID = 0)
112
+	{
113
+		$this->set('EVT_ID', $EVT_ID);
114
+	}
115
+
116
+
117
+	/**
118
+	 * Overrides parent set() method so that all calls to set( 'REG_code', $REG_code ) OR set( 'STS_ID', $STS_ID ) can
119
+	 * be routed to internal methods
120
+	 *
121
+	 * @param string $field_name
122
+	 * @param mixed  $field_value
123
+	 * @param bool   $use_default
124
+	 * @throws DomainException
125
+	 * @throws EE_Error
126
+	 * @throws EntityNotFoundException
127
+	 * @throws InvalidArgumentException
128
+	 * @throws InvalidDataTypeException
129
+	 * @throws InvalidInterfaceException
130
+	 * @throws ReflectionException
131
+	 * @throws RuntimeException
132
+	 * @throws UnexpectedEntityException
133
+	 */
134
+	public function set($field_name, $field_value, $use_default = false)
135
+	{
136
+		switch ($field_name) {
137
+			case 'REG_code':
138
+				if (! empty($field_value) && ! $this->reg_code()) {
139
+					$this->set_reg_code($field_value, $use_default);
140
+				}
141
+				break;
142
+			case 'STS_ID':
143
+				$this->set_status($field_value, $use_default);
144
+				break;
145
+			default:
146
+				parent::set($field_name, $field_value, $use_default);
147
+		}
148
+	}
149
+
150
+
151
+	/**
152
+	 * Set Status ID
153
+	 * updates the registration status and ALSO...
154
+	 * calls reserve_registration_space() if the reg status changes TO approved from any other reg status
155
+	 * calls release_registration_space() if the reg status changes FROM approved to any other reg status
156
+	 *
157
+	 * @param string                $new_STS_ID
158
+	 * @param boolean               $use_default
159
+	 * @param ContextInterface|null $context
160
+	 * @return bool
161
+	 * @throws DomainException
162
+	 * @throws EE_Error
163
+	 * @throws EntityNotFoundException
164
+	 * @throws InvalidArgumentException
165
+	 * @throws InvalidDataTypeException
166
+	 * @throws InvalidInterfaceException
167
+	 * @throws ReflectionException
168
+	 * @throws RuntimeException
169
+	 * @throws UnexpectedEntityException
170
+	 */
171
+	public function set_status($new_STS_ID = null, $use_default = false, ContextInterface $context = null)
172
+	{
173
+		// get current REG_Status
174
+		$old_STS_ID = $this->status_ID();
175
+		// if status has changed
176
+		if (
177
+			$old_STS_ID !== $new_STS_ID // and that status has actually changed
178
+			&& ! empty($old_STS_ID) // and that old status is actually set
179
+			&& ! empty($new_STS_ID) // as well as the new status
180
+			&& $this->ID() // ensure registration is in the db
181
+		) {
182
+			// update internal status first
183
+			parent::set('STS_ID', $new_STS_ID, $use_default);
184
+			// THEN handle other changes that occur when reg status changes
185
+			// TO approved
186
+			if ($new_STS_ID === EEM_Registration::status_id_approved) {
187
+				// reserve a space by incrementing ticket and datetime sold values
188
+				$this->reserveRegistrationSpace();
189
+				do_action('AHEE__EE_Registration__set_status__to_approved', $this, $old_STS_ID, $new_STS_ID, $context);
190
+				// OR FROM  approved
191
+			} elseif ($old_STS_ID === EEM_Registration::status_id_approved) {
192
+				// release a space by decrementing ticket and datetime sold values
193
+				$this->releaseRegistrationSpace();
194
+				do_action(
195
+					'AHEE__EE_Registration__set_status__from_approved',
196
+					$this,
197
+					$old_STS_ID,
198
+					$new_STS_ID,
199
+					$context
200
+				);
201
+			}
202
+			// update status
203
+			parent::set('STS_ID', $new_STS_ID, $use_default);
204
+			$this->updateIfCanceledOrReinstated($new_STS_ID, $old_STS_ID, $context);
205
+			if ($this->statusChangeUpdatesTransaction($context)) {
206
+				$this->updateTransactionAfterStatusChange();
207
+			}
208
+			do_action('AHEE__EE_Registration__set_status__after_update', $this, $old_STS_ID, $new_STS_ID, $context);
209
+			return true;
210
+		}
211
+		// even though the old value matches the new value, it's still good to
212
+		// allow the parent set method to have a say
213
+		parent::set('STS_ID', $new_STS_ID, $use_default);
214
+		return true;
215
+	}
216
+
217
+
218
+	/**
219
+	 * update REGs and TXN when cancelled or declined registrations involved
220
+	 *
221
+	 * @param string                $new_STS_ID
222
+	 * @param string                $old_STS_ID
223
+	 * @param ContextInterface|null $context
224
+	 * @throws EE_Error
225
+	 * @throws InvalidArgumentException
226
+	 * @throws InvalidDataTypeException
227
+	 * @throws InvalidInterfaceException
228
+	 * @throws ReflectionException
229
+	 * @throws RuntimeException
230
+	 */
231
+	private function updateIfCanceledOrReinstated($new_STS_ID, $old_STS_ID, ContextInterface $context = null)
232
+	{
233
+		// these reg statuses should not be considered in any calculations involving monies owing
234
+		$closed_reg_statuses = EEM_Registration::closed_reg_statuses();
235
+		// true if registration has been cancelled or declined
236
+		$this->updateIfCanceled(
237
+			$closed_reg_statuses,
238
+			$new_STS_ID,
239
+			$old_STS_ID,
240
+			$context
241
+		);
242
+		$this->updateIfReinstated(
243
+			$closed_reg_statuses,
244
+			$new_STS_ID,
245
+			$old_STS_ID,
246
+			$context
247
+		);
248
+	}
249
+
250
+
251
+	/**
252
+	 * update REGs and TXN when cancelled or declined registrations involved
253
+	 *
254
+	 * @param array                 $closed_reg_statuses
255
+	 * @param string                $new_STS_ID
256
+	 * @param string                $old_STS_ID
257
+	 * @param ContextInterface|null $context
258
+	 * @throws EE_Error
259
+	 * @throws InvalidArgumentException
260
+	 * @throws InvalidDataTypeException
261
+	 * @throws InvalidInterfaceException
262
+	 * @throws ReflectionException
263
+	 * @throws RuntimeException
264
+	 */
265
+	private function updateIfCanceled(
266
+		array $closed_reg_statuses,
267
+		$new_STS_ID,
268
+		$old_STS_ID,
269
+		ContextInterface $context = null
270
+	) {
271
+		// true if registration has been cancelled or declined
272
+		if (
273
+			in_array($new_STS_ID, $closed_reg_statuses, true)
274
+			&& ! in_array($old_STS_ID, $closed_reg_statuses, true)
275
+		) {
276
+			/** @type EE_Registration_Processor $registration_processor */
277
+			$registration_processor = EE_Registry::instance()->load_class('Registration_Processor');
278
+			/** @type EE_Transaction_Processor $transaction_processor */
279
+			$transaction_processor = EE_Registry::instance()->load_class('Transaction_Processor');
280
+			// cancelled or declined registration
281
+			$registration_processor->update_registration_after_being_canceled_or_declined(
282
+				$this,
283
+				$closed_reg_statuses
284
+			);
285
+			$transaction_processor->update_transaction_after_canceled_or_declined_registration(
286
+				$this,
287
+				$closed_reg_statuses,
288
+				false
289
+			);
290
+			do_action(
291
+				'AHEE__EE_Registration__set_status__canceled_or_declined',
292
+				$this,
293
+				$old_STS_ID,
294
+				$new_STS_ID,
295
+				$context
296
+			);
297
+			return;
298
+		}
299
+	}
300
+
301
+
302
+	/**
303
+	 * update REGs and TXN when cancelled or declined registrations involved
304
+	 *
305
+	 * @param array                 $closed_reg_statuses
306
+	 * @param string                $new_STS_ID
307
+	 * @param string                $old_STS_ID
308
+	 * @param ContextInterface|null $context
309
+	 * @throws EE_Error
310
+	 * @throws InvalidArgumentException
311
+	 * @throws InvalidDataTypeException
312
+	 * @throws InvalidInterfaceException
313
+	 * @throws ReflectionException
314
+	 * @throws RuntimeException
315
+	 */
316
+	private function updateIfReinstated(
317
+		array $closed_reg_statuses,
318
+		$new_STS_ID,
319
+		$old_STS_ID,
320
+		ContextInterface $context = null
321
+	) {
322
+		// true if reinstating cancelled or declined registration
323
+		if (
324
+			in_array($old_STS_ID, $closed_reg_statuses, true)
325
+			&& ! in_array($new_STS_ID, $closed_reg_statuses, true)
326
+		) {
327
+			/** @type EE_Registration_Processor $registration_processor */
328
+			$registration_processor = EE_Registry::instance()->load_class('Registration_Processor');
329
+			/** @type EE_Transaction_Processor $transaction_processor */
330
+			$transaction_processor = EE_Registry::instance()->load_class('Transaction_Processor');
331
+			// reinstating cancelled or declined registration
332
+			$registration_processor->update_canceled_or_declined_registration_after_being_reinstated(
333
+				$this,
334
+				$closed_reg_statuses
335
+			);
336
+			$transaction_processor->update_transaction_after_reinstating_canceled_registration(
337
+				$this,
338
+				$closed_reg_statuses,
339
+				false
340
+			);
341
+			do_action(
342
+				'AHEE__EE_Registration__set_status__after_reinstated',
343
+				$this,
344
+				$old_STS_ID,
345
+				$new_STS_ID,
346
+				$context
347
+			);
348
+		}
349
+	}
350
+
351
+
352
+	/**
353
+	 * @param ContextInterface|null $context
354
+	 * @return bool
355
+	 */
356
+	private function statusChangeUpdatesTransaction(ContextInterface $context = null)
357
+	{
358
+		$contexts_that_do_not_update_transaction = (array) apply_filters(
359
+			'AHEE__EE_Registration__statusChangeUpdatesTransaction__contexts_that_do_not_update_transaction',
360
+			['spco_reg_step_attendee_information_process_registrations'],
361
+			$context,
362
+			$this
363
+		);
364
+		return ! (
365
+			$context instanceof ContextInterface
366
+			&& in_array($context->slug(), $contexts_that_do_not_update_transaction, true)
367
+		);
368
+	}
369
+
370
+
371
+	/**
372
+	 * @throws EE_Error
373
+	 * @throws EntityNotFoundException
374
+	 * @throws InvalidArgumentException
375
+	 * @throws InvalidDataTypeException
376
+	 * @throws InvalidInterfaceException
377
+	 * @throws ReflectionException
378
+	 * @throws RuntimeException
379
+	 */
380
+	private function updateTransactionAfterStatusChange()
381
+	{
382
+		/** @type EE_Transaction_Payments $transaction_payments */
383
+		$transaction_payments = EE_Registry::instance()->load_class('Transaction_Payments');
384
+		$transaction_payments->recalculate_transaction_total($this->transaction(), false);
385
+		$this->transaction()->update_status_based_on_total_paid();
386
+	}
387
+
388
+
389
+	/**
390
+	 * get Status ID
391
+	 *
392
+	 * @throws EE_Error
393
+	 * @throws InvalidArgumentException
394
+	 * @throws InvalidDataTypeException
395
+	 * @throws InvalidInterfaceException
396
+	 * @throws ReflectionException
397
+	 */
398
+	public function status_ID()
399
+	{
400
+		return $this->get('STS_ID');
401
+	}
402
+
403
+
404
+	/**
405
+	 * Gets the ticket this registration is for
406
+	 *
407
+	 * @param boolean $include_archived whether to include archived tickets or not.
408
+	 * @return EE_Ticket|EE_Base_Class
409
+	 * @throws EE_Error
410
+	 * @throws InvalidArgumentException
411
+	 * @throws InvalidDataTypeException
412
+	 * @throws InvalidInterfaceException
413
+	 * @throws ReflectionException
414
+	 */
415
+	public function ticket($include_archived = true)
416
+	{
417
+		return EEM_Ticket::instance()->get_one_by_ID($this->ticket_ID());
418
+	}
419
+
420
+
421
+	/**
422
+	 * Gets the event this registration is for
423
+	 *
424
+	 * @return EE_Event
425
+	 * @throws EE_Error
426
+	 * @throws EntityNotFoundException
427
+	 * @throws InvalidArgumentException
428
+	 * @throws InvalidDataTypeException
429
+	 * @throws InvalidInterfaceException
430
+	 * @throws ReflectionException
431
+	 */
432
+	public function event(): EE_Event
433
+	{
434
+		$event = $this->event_obj();
435
+		if (! $event instanceof EE_Event) {
436
+			throw new EntityNotFoundException('Event ID', $this->event_ID());
437
+		}
438
+		return $event;
439
+	}
440
+
441
+
442
+	/**
443
+	 * Gets the "author" of the registration.  Note that for the purposes of registrations, the author will correspond
444
+	 * with the author of the event this registration is for.
445
+	 *
446
+	 * @return int
447
+	 * @throws EE_Error
448
+	 * @throws EntityNotFoundException
449
+	 * @throws InvalidArgumentException
450
+	 * @throws InvalidDataTypeException
451
+	 * @throws InvalidInterfaceException
452
+	 * @throws ReflectionException
453
+	 * @since 4.5.0
454
+	 */
455
+	public function wp_user(): int
456
+	{
457
+		return $this->event()->wp_user();
458
+	}
459
+
460
+
461
+	/**
462
+	 * increments this registration's related ticket sold and corresponding datetime sold values
463
+	 *
464
+	 * @return void
465
+	 * @throws DomainException
466
+	 * @throws EE_Error
467
+	 * @throws EntityNotFoundException
468
+	 * @throws InvalidArgumentException
469
+	 * @throws InvalidDataTypeException
470
+	 * @throws InvalidInterfaceException
471
+	 * @throws ReflectionException
472
+	 * @throws UnexpectedEntityException
473
+	 */
474
+	private function reserveRegistrationSpace()
475
+	{
476
+		// reserved ticket and datetime counts will be decremented as sold counts are incremented
477
+		// so stop tracking that this reg has a ticket reserved
478
+		$this->release_reserved_ticket(false, "REG: {$this->ID()} (ln:" . __LINE__ . ')');
479
+		$ticket = $this->ticket();
480
+		$ticket->increaseSold();
481
+		// possibly set event status to sold out
482
+		$this->event()->perform_sold_out_status_check();
483
+	}
484
+
485
+
486
+	/**
487
+	 * decrements (subtracts) this registration's related ticket sold and corresponding datetime sold values
488
+	 *
489
+	 * @return void
490
+	 * @throws DomainException
491
+	 * @throws EE_Error
492
+	 * @throws EntityNotFoundException
493
+	 * @throws InvalidArgumentException
494
+	 * @throws InvalidDataTypeException
495
+	 * @throws InvalidInterfaceException
496
+	 * @throws ReflectionException
497
+	 * @throws UnexpectedEntityException
498
+	 */
499
+	private function releaseRegistrationSpace()
500
+	{
501
+		$ticket = $this->ticket();
502
+		$ticket->decreaseSold();
503
+		// possibly change event status from sold out back to previous status
504
+		$this->event()->perform_sold_out_status_check();
505
+	}
506
+
507
+
508
+	/**
509
+	 * tracks this registration's ticket reservation in extra meta
510
+	 * and can increment related ticket reserved and corresponding datetime reserved values
511
+	 *
512
+	 * @param bool   $update_ticket if true, will increment ticket and datetime reserved count
513
+	 * @param string $source
514
+	 * @return void
515
+	 * @throws EE_Error
516
+	 * @throws InvalidArgumentException
517
+	 * @throws InvalidDataTypeException
518
+	 * @throws InvalidInterfaceException
519
+	 * @throws ReflectionException
520
+	 */
521
+	public function reserve_ticket($update_ticket = false, $source = 'unknown')
522
+	{
523
+		// only reserve ticket if space is not currently reserved
524
+		if ((bool) $this->get_extra_meta(EE_Registration::HAS_RESERVED_TICKET_KEY, true) !== true) {
525
+			$reserved = $this->update_extra_meta(EE_Registration::HAS_RESERVED_TICKET_KEY, true);
526
+			if ($reserved && $update_ticket) {
527
+				$ticket = $this->ticket();
528
+				$ticket->increaseReserved(1, "REG: {$this->ID()} (ln:" . __LINE__ . ')');
529
+				$this->update_extra_meta('reserve_ticket', "{$this->ticket_ID()} from {$source}");
530
+				$ticket->save();
531
+			}
532
+		}
533
+	}
534
+
535
+
536
+	/**
537
+	 * stops tracking this registration's ticket reservation in extra meta
538
+	 * decrements (subtracts) related ticket reserved and corresponding datetime reserved values
539
+	 *
540
+	 * @param bool   $update_ticket if true, will decrement ticket and datetime reserved count
541
+	 * @param string $source
542
+	 * @return void
543
+	 * @throws EE_Error
544
+	 * @throws InvalidArgumentException
545
+	 * @throws InvalidDataTypeException
546
+	 * @throws InvalidInterfaceException
547
+	 * @throws ReflectionException
548
+	 */
549
+	public function release_reserved_ticket($update_ticket = false, $source = 'unknown')
550
+	{
551
+		// only release ticket if space is currently reserved
552
+		if ((bool) $this->get_extra_meta(EE_Registration::HAS_RESERVED_TICKET_KEY, true) === true) {
553
+			$reserved = $this->update_extra_meta(EE_Registration::HAS_RESERVED_TICKET_KEY, false);
554
+			if ($reserved && $update_ticket) {
555
+				$ticket = $this->ticket();
556
+				$ticket->decreaseReserved(1, true, "REG: {$this->ID()} (ln:" . __LINE__ . ')');
557
+				$this->update_extra_meta('release_reserved_ticket', "{$this->ticket_ID()} from {$source}");
558
+			}
559
+		}
560
+	}
561
+
562
+
563
+	/**
564
+	 * Set Attendee ID
565
+	 *
566
+	 * @param int $ATT_ID Attendee ID
567
+	 * @throws DomainException
568
+	 * @throws EE_Error
569
+	 * @throws EntityNotFoundException
570
+	 * @throws InvalidArgumentException
571
+	 * @throws InvalidDataTypeException
572
+	 * @throws InvalidInterfaceException
573
+	 * @throws ReflectionException
574
+	 * @throws RuntimeException
575
+	 * @throws UnexpectedEntityException
576
+	 */
577
+	public function set_attendee_id($ATT_ID = 0)
578
+	{
579
+		$this->set('ATT_ID', $ATT_ID);
580
+	}
581
+
582
+
583
+	/**
584
+	 *        Set Transaction ID
585
+	 *
586
+	 * @param int $TXN_ID Transaction ID
587
+	 * @throws DomainException
588
+	 * @throws EE_Error
589
+	 * @throws EntityNotFoundException
590
+	 * @throws InvalidArgumentException
591
+	 * @throws InvalidDataTypeException
592
+	 * @throws InvalidInterfaceException
593
+	 * @throws ReflectionException
594
+	 * @throws RuntimeException
595
+	 * @throws UnexpectedEntityException
596
+	 */
597
+	public function set_transaction_id($TXN_ID = 0)
598
+	{
599
+		$this->set('TXN_ID', $TXN_ID);
600
+	}
601
+
602
+
603
+	/**
604
+	 *        Set Session
605
+	 *
606
+	 * @param string $REG_session PHP Session ID
607
+	 * @throws DomainException
608
+	 * @throws EE_Error
609
+	 * @throws EntityNotFoundException
610
+	 * @throws InvalidArgumentException
611
+	 * @throws InvalidDataTypeException
612
+	 * @throws InvalidInterfaceException
613
+	 * @throws ReflectionException
614
+	 * @throws RuntimeException
615
+	 * @throws UnexpectedEntityException
616
+	 */
617
+	public function set_session($REG_session = '')
618
+	{
619
+		$this->set('REG_session', $REG_session);
620
+	}
621
+
622
+
623
+	/**
624
+	 *        Set Registration URL Link
625
+	 *
626
+	 * @param string $REG_url_link Registration URL Link
627
+	 * @throws DomainException
628
+	 * @throws EE_Error
629
+	 * @throws EntityNotFoundException
630
+	 * @throws InvalidArgumentException
631
+	 * @throws InvalidDataTypeException
632
+	 * @throws InvalidInterfaceException
633
+	 * @throws ReflectionException
634
+	 * @throws RuntimeException
635
+	 * @throws UnexpectedEntityException
636
+	 */
637
+	public function set_reg_url_link($REG_url_link = '')
638
+	{
639
+		$this->set('REG_url_link', $REG_url_link);
640
+	}
641
+
642
+
643
+	/**
644
+	 *        Set Attendee Counter
645
+	 *
646
+	 * @param int $REG_count Primary Attendee
647
+	 * @throws DomainException
648
+	 * @throws EE_Error
649
+	 * @throws EntityNotFoundException
650
+	 * @throws InvalidArgumentException
651
+	 * @throws InvalidDataTypeException
652
+	 * @throws InvalidInterfaceException
653
+	 * @throws ReflectionException
654
+	 * @throws RuntimeException
655
+	 * @throws UnexpectedEntityException
656
+	 */
657
+	public function set_count($REG_count = 1)
658
+	{
659
+		$this->set('REG_count', $REG_count);
660
+	}
661
+
662
+
663
+	/**
664
+	 *        Set Group Size
665
+	 *
666
+	 * @param boolean $REG_group_size Group Registration
667
+	 * @throws DomainException
668
+	 * @throws EE_Error
669
+	 * @throws EntityNotFoundException
670
+	 * @throws InvalidArgumentException
671
+	 * @throws InvalidDataTypeException
672
+	 * @throws InvalidInterfaceException
673
+	 * @throws ReflectionException
674
+	 * @throws RuntimeException
675
+	 * @throws UnexpectedEntityException
676
+	 */
677
+	public function set_group_size($REG_group_size = false)
678
+	{
679
+		$this->set('REG_group_size', $REG_group_size);
680
+	}
681
+
682
+
683
+	/**
684
+	 *    is_not_approved -  convenience method that returns TRUE if REG status ID ==
685
+	 *    EEM_Registration::status_id_not_approved
686
+	 *
687
+	 * @return        boolean
688
+	 * @throws EE_Error
689
+	 * @throws InvalidArgumentException
690
+	 * @throws InvalidDataTypeException
691
+	 * @throws InvalidInterfaceException
692
+	 * @throws ReflectionException
693
+	 */
694
+	public function is_not_approved()
695
+	{
696
+		return $this->status_ID() === EEM_Registration::status_id_not_approved;
697
+	}
698
+
699
+
700
+	/**
701
+	 *    is_pending_payment -  convenience method that returns TRUE if REG status ID ==
702
+	 *    EEM_Registration::status_id_pending_payment
703
+	 *
704
+	 * @return        boolean
705
+	 * @throws EE_Error
706
+	 * @throws InvalidArgumentException
707
+	 * @throws InvalidDataTypeException
708
+	 * @throws InvalidInterfaceException
709
+	 * @throws ReflectionException
710
+	 */
711
+	public function is_pending_payment()
712
+	{
713
+		return $this->status_ID() === EEM_Registration::status_id_pending_payment;
714
+	}
715
+
716
+
717
+	/**
718
+	 *    is_approved -  convenience method that returns TRUE if REG status ID == EEM_Registration::status_id_approved
719
+	 *
720
+	 * @return        boolean
721
+	 * @throws EE_Error
722
+	 * @throws InvalidArgumentException
723
+	 * @throws InvalidDataTypeException
724
+	 * @throws InvalidInterfaceException
725
+	 * @throws ReflectionException
726
+	 */
727
+	public function is_approved()
728
+	{
729
+		return $this->status_ID() === EEM_Registration::status_id_approved;
730
+	}
731
+
732
+
733
+	/**
734
+	 *    is_cancelled -  convenience method that returns TRUE if REG status ID == EEM_Registration::status_id_cancelled
735
+	 *
736
+	 * @return        boolean
737
+	 * @throws EE_Error
738
+	 * @throws InvalidArgumentException
739
+	 * @throws InvalidDataTypeException
740
+	 * @throws InvalidInterfaceException
741
+	 * @throws ReflectionException
742
+	 */
743
+	public function is_cancelled()
744
+	{
745
+		return $this->status_ID() === EEM_Registration::status_id_cancelled;
746
+	}
747
+
748
+
749
+	/**
750
+	 *    is_declined -  convenience method that returns TRUE if REG status ID == EEM_Registration::status_id_declined
751
+	 *
752
+	 * @return        boolean
753
+	 * @throws EE_Error
754
+	 * @throws InvalidArgumentException
755
+	 * @throws InvalidDataTypeException
756
+	 * @throws InvalidInterfaceException
757
+	 * @throws ReflectionException
758
+	 */
759
+	public function is_declined()
760
+	{
761
+		return $this->status_ID() === EEM_Registration::status_id_declined;
762
+	}
763
+
764
+
765
+	/**
766
+	 *    is_incomplete -  convenience method that returns TRUE if REG status ID ==
767
+	 *    EEM_Registration::status_id_incomplete
768
+	 *
769
+	 * @return        boolean
770
+	 * @throws EE_Error
771
+	 * @throws InvalidArgumentException
772
+	 * @throws InvalidDataTypeException
773
+	 * @throws InvalidInterfaceException
774
+	 * @throws ReflectionException
775
+	 */
776
+	public function is_incomplete()
777
+	{
778
+		return $this->status_ID() === EEM_Registration::status_id_incomplete;
779
+	}
780
+
781
+
782
+	/**
783
+	 *        Set Registration Date
784
+	 *
785
+	 * @param mixed ( int or string ) $REG_date Registration Date - Unix timestamp or string representation of
786
+	 *                                                 Date
787
+	 * @throws DomainException
788
+	 * @throws EE_Error
789
+	 * @throws EntityNotFoundException
790
+	 * @throws InvalidArgumentException
791
+	 * @throws InvalidDataTypeException
792
+	 * @throws InvalidInterfaceException
793
+	 * @throws ReflectionException
794
+	 * @throws RuntimeException
795
+	 * @throws UnexpectedEntityException
796
+	 */
797
+	public function set_reg_date($REG_date = false)
798
+	{
799
+		$this->set('REG_date', $REG_date);
800
+	}
801
+
802
+
803
+	/**
804
+	 *    Set final price owing for this registration after all ticket/price modifications
805
+	 *
806
+	 * @param float $REG_final_price
807
+	 * @throws DomainException
808
+	 * @throws EE_Error
809
+	 * @throws EntityNotFoundException
810
+	 * @throws InvalidArgumentException
811
+	 * @throws InvalidDataTypeException
812
+	 * @throws InvalidInterfaceException
813
+	 * @throws ReflectionException
814
+	 * @throws RuntimeException
815
+	 * @throws UnexpectedEntityException
816
+	 */
817
+	public function set_final_price($REG_final_price = 0.00)
818
+	{
819
+		$this->set('REG_final_price', $REG_final_price);
820
+	}
821
+
822
+
823
+	/**
824
+	 *    Set amount paid towards this registration's final price
825
+	 *
826
+	 * @param float|int|string $REG_paid
827
+	 * @throws DomainException
828
+	 * @throws EE_Error
829
+	 * @throws EntityNotFoundException
830
+	 * @throws InvalidArgumentException
831
+	 * @throws InvalidDataTypeException
832
+	 * @throws InvalidInterfaceException
833
+	 * @throws ReflectionException
834
+	 * @throws RuntimeException
835
+	 * @throws UnexpectedEntityException
836
+	 */
837
+	public function set_paid($REG_paid = 0.00)
838
+	{
839
+		$this->set('REG_paid', (float) $REG_paid);
840
+	}
841
+
842
+
843
+	/**
844
+	 *        Attendee Is Going
845
+	 *
846
+	 * @param boolean $REG_att_is_going Attendee Is Going
847
+	 * @throws DomainException
848
+	 * @throws EE_Error
849
+	 * @throws EntityNotFoundException
850
+	 * @throws InvalidArgumentException
851
+	 * @throws InvalidDataTypeException
852
+	 * @throws InvalidInterfaceException
853
+	 * @throws ReflectionException
854
+	 * @throws RuntimeException
855
+	 * @throws UnexpectedEntityException
856
+	 */
857
+	public function set_att_is_going($REG_att_is_going = false)
858
+	{
859
+		$this->set('REG_att_is_going', $REG_att_is_going);
860
+	}
861
+
862
+
863
+	/**
864
+	 * Gets the related attendee
865
+	 *
866
+	 * @return EE_Attendee|EE_Base_Class
867
+	 * @throws EE_Error
868
+	 * @throws InvalidArgumentException
869
+	 * @throws InvalidDataTypeException
870
+	 * @throws InvalidInterfaceException
871
+	 * @throws ReflectionException
872
+	 */
873
+	public function attendee()
874
+	{
875
+		return EEM_Attendee::instance()->get_one_by_ID($this->attendee_ID());
876
+	}
877
+
878
+
879
+	/**
880
+	 * Gets the name of the attendee.
881
+	 *
882
+	 * @param bool $apply_html_entities set to true if you want to use HTML entities.
883
+	 * @return string
884
+	 * @throws EE_Error
885
+	 * @throws InvalidArgumentException
886
+	 * @throws InvalidDataTypeException
887
+	 * @throws InvalidInterfaceException
888
+	 * @throws ReflectionException
889
+	 * @since 4.10.12.p
890
+	 */
891
+	public function attendeeName($apply_html_entities = false)
892
+	{
893
+		$attendee = $this->attendee();
894
+		if ($attendee instanceof EE_Attendee) {
895
+			$attendee_name = $attendee->full_name($apply_html_entities);
896
+		} else {
897
+			$attendee_name = esc_html__('Unknown', 'event_espresso');
898
+		}
899
+		return $attendee_name;
900
+	}
901
+
902
+
903
+	/**
904
+	 *        get Event ID
905
+	 */
906
+	public function event_ID()
907
+	{
908
+		return $this->get('EVT_ID');
909
+	}
910
+
911
+
912
+	/**
913
+	 *        get Event ID
914
+	 */
915
+	public function event_name()
916
+	{
917
+		$event = $this->event_obj();
918
+		if ($event) {
919
+			return $event->name();
920
+		} else {
921
+			return null;
922
+		}
923
+	}
924
+
925
+
926
+	/**
927
+	 * Fetches the event this registration is for
928
+	 *
929
+	 * @return EE_Base_Class|EE_Event
930
+	 * @throws EE_Error
931
+	 * @throws InvalidArgumentException
932
+	 * @throws InvalidDataTypeException
933
+	 * @throws InvalidInterfaceException
934
+	 * @throws ReflectionException
935
+	 */
936
+	public function event_obj()
937
+	{
938
+		return EEM_Event::instance()->get_one_by_ID($this->event_ID());
939
+	}
940
+
941
+
942
+	/**
943
+	 *        get Attendee ID
944
+	 */
945
+	public function attendee_ID()
946
+	{
947
+		return $this->get('ATT_ID');
948
+	}
949
+
950
+
951
+	/**
952
+	 *        get PHP Session ID
953
+	 */
954
+	public function session_ID()
955
+	{
956
+		return $this->get('REG_session');
957
+	}
958
+
959
+
960
+	/**
961
+	 * Gets the string which represents the URL trigger for the receipt template in the message template system.
962
+	 *
963
+	 * @param string $messenger 'pdf' or 'html'.  Default 'html'.
964
+	 * @return string
965
+	 * @throws DomainException
966
+	 * @throws InvalidArgumentException
967
+	 * @throws InvalidDataTypeException
968
+	 * @throws InvalidInterfaceException
969
+	 */
970
+	public function receipt_url($messenger = 'html')
971
+	{
972
+		return apply_filters('FHEE__EE_Registration__receipt_url__receipt_url', '', $this, $messenger, 'receipt');
973
+	}
974
+
975
+
976
+	/**
977
+	 * Gets the string which represents the URL trigger for the invoice template in the message template system.
978
+	 *
979
+	 * @param string $messenger 'pdf' or 'html'.  Default 'html'.
980
+	 * @return string
981
+	 * @throws DomainException
982
+	 * @throws InvalidArgumentException
983
+	 * @throws InvalidDataTypeException
984
+	 * @throws InvalidInterfaceException
985
+	 */
986
+	public function invoice_url($messenger = 'html')
987
+	{
988
+		return apply_filters('FHEE__EE_Registration__invoice_url__invoice_url', '', $this, $messenger, 'invoice');
989
+	}
990
+
991
+
992
+	/**
993
+	 * get Registration URL Link
994
+	 *
995
+	 * @return string
996
+	 * @throws EE_Error
997
+	 * @throws InvalidArgumentException
998
+	 * @throws InvalidDataTypeException
999
+	 * @throws InvalidInterfaceException
1000
+	 * @throws ReflectionException
1001
+	 */
1002
+	public function reg_url_link()
1003
+	{
1004
+		return (string) $this->get('REG_url_link');
1005
+	}
1006
+
1007
+
1008
+	/**
1009
+	 * Echoes out invoice_url()
1010
+	 *
1011
+	 * @param string $type 'download','launch', or 'html' (default is 'launch')
1012
+	 * @return void
1013
+	 * @throws DomainException
1014
+	 * @throws EE_Error
1015
+	 * @throws InvalidArgumentException
1016
+	 * @throws InvalidDataTypeException
1017
+	 * @throws InvalidInterfaceException
1018
+	 * @throws ReflectionException
1019
+	 */
1020
+	public function e_invoice_url($type = 'launch')
1021
+	{
1022
+		echo esc_url_raw($this->invoice_url($type));
1023
+	}
1024
+
1025
+
1026
+	/**
1027
+	 * Echoes out payment_overview_url
1028
+	 */
1029
+	public function e_payment_overview_url()
1030
+	{
1031
+		echo esc_url_raw($this->payment_overview_url());
1032
+	}
1033
+
1034
+
1035
+	/**
1036
+	 * Gets the URL for the checkout payment options reg step
1037
+	 * with this registration's REG_url_link added as a query parameter
1038
+	 *
1039
+	 * @param bool $clear_session Set to true when you want to clear the session on revisiting the
1040
+	 *                            payment overview url.
1041
+	 * @return string
1042
+	 * @throws EE_Error
1043
+	 * @throws InvalidArgumentException
1044
+	 * @throws InvalidDataTypeException
1045
+	 * @throws InvalidInterfaceException
1046
+	 * @throws ReflectionException
1047
+	 */
1048
+	public function payment_overview_url($clear_session = false)
1049
+	{
1050
+		return add_query_arg(
1051
+			(array) apply_filters(
1052
+				'FHEE__EE_Registration__payment_overview_url__query_args',
1053
+				[
1054
+					'e_reg_url_link' => $this->reg_url_link(),
1055
+					'step'           => 'payment_options',
1056
+					'revisit'        => true,
1057
+					'clear_session'  => (bool) $clear_session,
1058
+				],
1059
+				$this
1060
+			),
1061
+			EE_Registry::instance()->CFG->core->reg_page_url()
1062
+		);
1063
+	}
1064
+
1065
+
1066
+	/**
1067
+	 * Gets the URL for the checkout attendee information reg step
1068
+	 * with this registration's REG_url_link added as a query parameter
1069
+	 *
1070
+	 * @return string
1071
+	 * @throws EE_Error
1072
+	 * @throws InvalidArgumentException
1073
+	 * @throws InvalidDataTypeException
1074
+	 * @throws InvalidInterfaceException
1075
+	 * @throws ReflectionException
1076
+	 */
1077
+	public function edit_attendee_information_url()
1078
+	{
1079
+		return add_query_arg(
1080
+			(array) apply_filters(
1081
+				'FHEE__EE_Registration__edit_attendee_information_url__query_args',
1082
+				[
1083
+					'e_reg_url_link' => $this->reg_url_link(),
1084
+					'step'           => 'attendee_information',
1085
+					'revisit'        => true,
1086
+				],
1087
+				$this
1088
+			),
1089
+			EE_Registry::instance()->CFG->core->reg_page_url()
1090
+		);
1091
+	}
1092
+
1093
+
1094
+	/**
1095
+	 * Simply generates and returns the appropriate admin_url link to edit this registration
1096
+	 *
1097
+	 * @return string
1098
+	 * @throws EE_Error
1099
+	 * @throws InvalidArgumentException
1100
+	 * @throws InvalidDataTypeException
1101
+	 * @throws InvalidInterfaceException
1102
+	 * @throws ReflectionException
1103
+	 */
1104
+	public function get_admin_edit_url()
1105
+	{
1106
+		return EEH_URL::add_query_args_and_nonce(
1107
+			[
1108
+				'page'    => 'espresso_registrations',
1109
+				'action'  => 'view_registration',
1110
+				'_REG_ID' => $this->ID(),
1111
+			],
1112
+			admin_url('admin.php')
1113
+		);
1114
+	}
1115
+
1116
+
1117
+	/**
1118
+	 * is_primary_registrant?
1119
+	 *
1120
+	 * @throws EE_Error
1121
+	 * @throws InvalidArgumentException
1122
+	 * @throws InvalidDataTypeException
1123
+	 * @throws InvalidInterfaceException
1124
+	 * @throws ReflectionException
1125
+	 */
1126
+	public function is_primary_registrant()
1127
+	{
1128
+		return (int) $this->get('REG_count') === 1;
1129
+	}
1130
+
1131
+
1132
+	/**
1133
+	 * This returns the primary registration object for this registration group (which may be this object).
1134
+	 *
1135
+	 * @return EE_Registration
1136
+	 * @throws EE_Error
1137
+	 * @throws InvalidArgumentException
1138
+	 * @throws InvalidDataTypeException
1139
+	 * @throws InvalidInterfaceException
1140
+	 * @throws ReflectionException
1141
+	 */
1142
+	public function get_primary_registration()
1143
+	{
1144
+		if ($this->is_primary_registrant()) {
1145
+			return $this;
1146
+		}
1147
+
1148
+		// k reg_count !== 1 so let's get the EE_Registration object matching this txn_id and reg_count == 1
1149
+		/** @var EE_Registration $primary_registrant */
1150
+		$primary_registrant = EEM_Registration::instance()->get_one(
1151
+			[
1152
+				[
1153
+					'TXN_ID'    => $this->transaction_ID(),
1154
+					'REG_count' => 1,
1155
+				],
1156
+			]
1157
+		);
1158
+		return $primary_registrant;
1159
+	}
1160
+
1161
+
1162
+	/**
1163
+	 * get  Attendee Number
1164
+	 *
1165
+	 * @throws EE_Error
1166
+	 * @throws InvalidArgumentException
1167
+	 * @throws InvalidDataTypeException
1168
+	 * @throws InvalidInterfaceException
1169
+	 * @throws ReflectionException
1170
+	 */
1171
+	public function count()
1172
+	{
1173
+		return $this->get('REG_count');
1174
+	}
1175
+
1176
+
1177
+	/**
1178
+	 * get Group Size
1179
+	 *
1180
+	 * @throws EE_Error
1181
+	 * @throws InvalidArgumentException
1182
+	 * @throws InvalidDataTypeException
1183
+	 * @throws InvalidInterfaceException
1184
+	 * @throws ReflectionException
1185
+	 */
1186
+	public function group_size()
1187
+	{
1188
+		return $this->get('REG_group_size');
1189
+	}
1190
+
1191
+
1192
+	/**
1193
+	 * get Registration Date
1194
+	 *
1195
+	 * @throws EE_Error
1196
+	 * @throws InvalidArgumentException
1197
+	 * @throws InvalidDataTypeException
1198
+	 * @throws InvalidInterfaceException
1199
+	 * @throws ReflectionException
1200
+	 */
1201
+	public function date()
1202
+	{
1203
+		return $this->get('REG_date');
1204
+	}
1205
+
1206
+
1207
+	/**
1208
+	 * gets a pretty date
1209
+	 *
1210
+	 * @param string $date_format
1211
+	 * @param string $time_format
1212
+	 * @return string
1213
+	 * @throws EE_Error
1214
+	 * @throws InvalidArgumentException
1215
+	 * @throws InvalidDataTypeException
1216
+	 * @throws InvalidInterfaceException
1217
+	 * @throws ReflectionException
1218
+	 */
1219
+	public function pretty_date($date_format = null, $time_format = null)
1220
+	{
1221
+		return $this->get_datetime('REG_date', $date_format, $time_format);
1222
+	}
1223
+
1224
+
1225
+	/**
1226
+	 * final_price
1227
+	 * the registration's share of the transaction total, so that the
1228
+	 * sum of all the transaction's REG_final_prices equal the transaction's total
1229
+	 *
1230
+	 * @return float
1231
+	 * @throws EE_Error
1232
+	 * @throws InvalidArgumentException
1233
+	 * @throws InvalidDataTypeException
1234
+	 * @throws InvalidInterfaceException
1235
+	 * @throws ReflectionException
1236
+	 */
1237
+	public function final_price(): float
1238
+	{
1239
+		return (float) $this->get('REG_final_price');
1240
+	}
1241
+
1242
+
1243
+	/**
1244
+	 * pretty_final_price
1245
+	 *  final price as formatted string, with correct decimal places and currency symbol
1246
+	 *
1247
+	 * @param string|null $schema
1248
+	 *      Schemas:
1249
+	 *      'localized_float': "3,023.00"
1250
+	 *      'no_currency_code': "$3,023.00"
1251
+	 *      null: "$3,023.00<span>USD</span>"
1252
+	 * @return string
1253
+	 * @throws EE_Error
1254
+	 * @throws InvalidArgumentException
1255
+	 * @throws InvalidDataTypeException
1256
+	 * @throws InvalidInterfaceException
1257
+	 * @throws ReflectionException
1258
+	 */
1259
+	public function pretty_final_price(?string $schema = null)
1260
+	{
1261
+		return $this->get_pretty('REG_final_price', $schema);
1262
+	}
1263
+
1264
+
1265
+	/**
1266
+	 * get paid (yeah)
1267
+	 *
1268
+	 * @return float
1269
+	 * @throws EE_Error
1270
+	 * @throws InvalidArgumentException
1271
+	 * @throws InvalidDataTypeException
1272
+	 * @throws InvalidInterfaceException
1273
+	 * @throws ReflectionException
1274
+	 */
1275
+	public function paid(): float
1276
+	{
1277
+		return (float) $this->get('REG_paid');
1278
+	}
1279
+
1280
+
1281
+	/**
1282
+	 * pretty_paid
1283
+	 *
1284
+	 * @param string|null $schema
1285
+	 *      Schemas:
1286
+	 *      'localized_float': "3,023.00"
1287
+	 *      'no_currency_code': "$3,023.00"
1288
+	 *      null: "$3,023.00<span>USD</span>"
1289
+	 * @return float
1290
+	 * @throws EE_Error
1291
+	 * @throws InvalidArgumentException
1292
+	 * @throws InvalidDataTypeException
1293
+	 * @throws InvalidInterfaceException
1294
+	 * @throws ReflectionException
1295
+	 */
1296
+	public function pretty_paid(?string $schema = null)
1297
+	{
1298
+		return $this->get_pretty('REG_paid', $schema);
1299
+	}
1300
+
1301
+
1302
+	/**
1303
+	 * owes_monies_and_can_pay
1304
+	 * whether or not this registration has monies owing and it's' status allows payment
1305
+	 *
1306
+	 * @param array $requires_payment list of registration statuses that allow a registrant to make a payment
1307
+	 * @return bool
1308
+	 * @throws EE_Error
1309
+	 * @throws InvalidArgumentException
1310
+	 * @throws InvalidDataTypeException
1311
+	 * @throws InvalidInterfaceException
1312
+	 * @throws ReflectionException
1313
+	 */
1314
+	public function owes_monies_and_can_pay(array $requires_payment = []): bool
1315
+	{
1316
+		// these reg statuses require payment (if event is not free)
1317
+		$requires_payment = ! empty($requires_payment)
1318
+			? $requires_payment
1319
+			: EEM_Registration::reg_statuses_that_allow_payment();
1320
+		if (
1321
+			$this->final_price() !== 0.0 &&
1322
+			$this->final_price() !== $this->paid() &&
1323
+			in_array($this->status_ID(), $requires_payment)
1324
+		) {
1325
+			return true;
1326
+		}
1327
+		return false;
1328
+	}
1329
+
1330
+
1331
+	/**
1332
+	 * Prints out the return value of $this->pretty_status()
1333
+	 *
1334
+	 * @param bool $show_icons
1335
+	 * @return void
1336
+	 * @throws EE_Error
1337
+	 * @throws InvalidArgumentException
1338
+	 * @throws InvalidDataTypeException
1339
+	 * @throws InvalidInterfaceException
1340
+	 * @throws ReflectionException
1341
+	 */
1342
+	public function e_pretty_status($show_icons = false)
1343
+	{
1344
+		echo wp_kses($this->pretty_status($show_icons), AllowedTags::getAllowedTags());
1345
+	}
1346
+
1347
+
1348
+	/**
1349
+	 * Returns a nice version of the status for displaying to customers
1350
+	 *
1351
+	 * @param bool $show_icons
1352
+	 * @return string
1353
+	 * @throws EE_Error
1354
+	 * @throws InvalidArgumentException
1355
+	 * @throws InvalidDataTypeException
1356
+	 * @throws InvalidInterfaceException
1357
+	 * @throws ReflectionException
1358
+	 */
1359
+	public function pretty_status($show_icons = false)
1360
+	{
1361
+		$status = EEM_Status::instance()->localized_status(
1362
+			[$this->status_ID() => esc_html__('unknown', 'event_espresso')],
1363
+			false,
1364
+			'sentence'
1365
+		);
1366
+		$icon   = '';
1367
+		switch ($this->status_ID()) {
1368
+			case EEM_Registration::status_id_approved:
1369
+				$icon = $show_icons
1370
+					? '<span class="dashicons dashicons-star-filled ee-icon-size-16 green-text"></span>'
1371
+					: '';
1372
+				break;
1373
+			case EEM_Registration::status_id_pending_payment:
1374
+				$icon = $show_icons
1375
+					? '<span class="dashicons dashicons-star-half ee-icon-size-16 orange-text"></span>'
1376
+					: '';
1377
+				break;
1378
+			case EEM_Registration::status_id_not_approved:
1379
+				$icon = $show_icons
1380
+					? '<span class="dashicons dashicons-marker ee-icon-size-16 orange-text"></span>'
1381
+					: '';
1382
+				break;
1383
+			case EEM_Registration::status_id_cancelled:
1384
+				$icon = $show_icons
1385
+					? '<span class="dashicons dashicons-no ee-icon-size-16 lt-grey-text"></span>'
1386
+					: '';
1387
+				break;
1388
+			case EEM_Registration::status_id_incomplete:
1389
+				$icon = $show_icons
1390
+					? '<span class="dashicons dashicons-no ee-icon-size-16 lt-orange-text"></span>'
1391
+					: '';
1392
+				break;
1393
+			case EEM_Registration::status_id_declined:
1394
+				$icon = $show_icons
1395
+					? '<span class="dashicons dashicons-no ee-icon-size-16 red-text"></span>'
1396
+					: '';
1397
+				break;
1398
+			case EEM_Registration::status_id_wait_list:
1399
+				$icon = $show_icons
1400
+					? '<span class="dashicons dashicons-clipboard ee-icon-size-16 purple-text"></span>'
1401
+					: '';
1402
+				break;
1403
+		}
1404
+		return $icon . $status[ $this->status_ID() ];
1405
+	}
1406
+
1407
+
1408
+	/**
1409
+	 *        get Attendee Is Going
1410
+	 */
1411
+	public function att_is_going()
1412
+	{
1413
+		return $this->get('REG_att_is_going');
1414
+	}
1415
+
1416
+
1417
+	/**
1418
+	 * Gets related answers
1419
+	 *
1420
+	 * @param array $query_params @see
1421
+	 *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1422
+	 * @return EE_Answer[]|EE_Base_Class[]
1423
+	 * @throws EE_Error
1424
+	 * @throws InvalidArgumentException
1425
+	 * @throws InvalidDataTypeException
1426
+	 * @throws InvalidInterfaceException
1427
+	 * @throws ReflectionException
1428
+	 */
1429
+	public function answers($query_params = [])
1430
+	{
1431
+		return $this->get_many_related('Answer', $query_params);
1432
+	}
1433
+
1434
+
1435
+	/**
1436
+	 * Gets the registration's answer value to the specified question
1437
+	 * (either the question's ID or a question object)
1438
+	 *
1439
+	 * @param EE_Question|int $question
1440
+	 * @param bool            $pretty_value
1441
+	 * @return array|string if pretty_value= true, the result will always be a string
1442
+	 * (because the answer might be an array of answer values, so passing pretty_value=true
1443
+	 * will convert it into some kind of string)
1444
+	 * @throws EE_Error
1445
+	 * @throws InvalidArgumentException
1446
+	 * @throws InvalidDataTypeException
1447
+	 * @throws InvalidInterfaceException
1448
+	 */
1449
+	public function answer_value_to_question($question, $pretty_value = true)
1450
+	{
1451
+		$question_id = EEM_Question::instance()->ensure_is_ID($question);
1452
+		return EEM_Answer::instance()->get_answer_value_to_question($this, $question_id, $pretty_value);
1453
+	}
1454
+
1455
+
1456
+	/**
1457
+	 * question_groups
1458
+	 * returns an array of EE_Question_Group objects for this registration
1459
+	 *
1460
+	 * @return EE_Question_Group[]
1461
+	 * @throws EE_Error
1462
+	 * @throws InvalidArgumentException
1463
+	 * @throws InvalidDataTypeException
1464
+	 * @throws InvalidInterfaceException
1465
+	 * @throws ReflectionException
1466
+	 */
1467
+	public function question_groups()
1468
+	{
1469
+		return EEM_Event::instance()->get_question_groups_for_event($this->event_ID(), $this);
1470
+	}
1471
+
1472
+
1473
+	/**
1474
+	 * count_question_groups
1475
+	 * returns a count of the number of EE_Question_Group objects for this registration
1476
+	 *
1477
+	 * @return int
1478
+	 * @throws EE_Error
1479
+	 * @throws EntityNotFoundException
1480
+	 * @throws InvalidArgumentException
1481
+	 * @throws InvalidDataTypeException
1482
+	 * @throws InvalidInterfaceException
1483
+	 * @throws ReflectionException
1484
+	 */
1485
+	public function count_question_groups()
1486
+	{
1487
+		return EEM_Event::instance()->count_related(
1488
+			$this->event_ID(),
1489
+			'Question_Group',
1490
+			[
1491
+				[
1492
+					'Event_Question_Group.'
1493
+					. EEM_Event_Question_Group::instance()->fieldNameForContext($this->is_primary_registrant()) => true,
1494
+				],
1495
+			]
1496
+		);
1497
+	}
1498
+
1499
+
1500
+	/**
1501
+	 * Returns the registration date in the 'standard' string format
1502
+	 * (function may be improved in the future to allow for different formats and timezones)
1503
+	 *
1504
+	 * @return string
1505
+	 * @throws EE_Error
1506
+	 * @throws InvalidArgumentException
1507
+	 * @throws InvalidDataTypeException
1508
+	 * @throws InvalidInterfaceException
1509
+	 * @throws ReflectionException
1510
+	 */
1511
+	public function reg_date()
1512
+	{
1513
+		return $this->get_datetime('REG_date');
1514
+	}
1515
+
1516
+
1517
+	/**
1518
+	 * Gets the datetime-ticket for this registration (ie, it can be used to isolate
1519
+	 * the ticket this registration purchased, or the datetime they have registered
1520
+	 * to attend)
1521
+	 *
1522
+	 * @return EE_Base_Class|EE_Datetime_Ticket
1523
+	 * @throws EE_Error
1524
+	 * @throws InvalidArgumentException
1525
+	 * @throws InvalidDataTypeException
1526
+	 * @throws InvalidInterfaceException
1527
+	 * @throws ReflectionException
1528
+	 */
1529
+	public function datetime_ticket()
1530
+	{
1531
+		return $this->get_first_related('Datetime_Ticket');
1532
+	}
1533
+
1534
+
1535
+	/**
1536
+	 * Sets the registration's datetime_ticket.
1537
+	 *
1538
+	 * @param EE_Datetime_Ticket $datetime_ticket
1539
+	 * @return EE_Base_Class|EE_Datetime_Ticket
1540
+	 * @throws EE_Error
1541
+	 * @throws InvalidArgumentException
1542
+	 * @throws InvalidDataTypeException
1543
+	 * @throws InvalidInterfaceException
1544
+	 * @throws ReflectionException
1545
+	 */
1546
+	public function set_datetime_ticket($datetime_ticket)
1547
+	{
1548
+		return $this->_add_relation_to($datetime_ticket, 'Datetime_Ticket');
1549
+	}
1550
+
1551
+
1552
+	/**
1553
+	 * Gets deleted
1554
+	 *
1555
+	 * @return bool
1556
+	 * @throws EE_Error
1557
+	 * @throws InvalidArgumentException
1558
+	 * @throws InvalidDataTypeException
1559
+	 * @throws InvalidInterfaceException
1560
+	 * @throws ReflectionException
1561
+	 */
1562
+	public function deleted()
1563
+	{
1564
+		return $this->get('REG_deleted');
1565
+	}
1566
+
1567
+
1568
+	/**
1569
+	 * Sets deleted
1570
+	 *
1571
+	 * @param boolean $deleted
1572
+	 * @return void
1573
+	 * @throws DomainException
1574
+	 * @throws EE_Error
1575
+	 * @throws EntityNotFoundException
1576
+	 * @throws InvalidArgumentException
1577
+	 * @throws InvalidDataTypeException
1578
+	 * @throws InvalidInterfaceException
1579
+	 * @throws ReflectionException
1580
+	 * @throws RuntimeException
1581
+	 * @throws UnexpectedEntityException
1582
+	 */
1583
+	public function set_deleted($deleted)
1584
+	{
1585
+		if ($deleted) {
1586
+			$this->delete();
1587
+		} else {
1588
+			$this->restore();
1589
+		}
1590
+	}
1591
+
1592
+
1593
+	/**
1594
+	 * Get the status object of this object
1595
+	 *
1596
+	 * @return EE_Base_Class|EE_Status
1597
+	 * @throws EE_Error
1598
+	 * @throws InvalidArgumentException
1599
+	 * @throws InvalidDataTypeException
1600
+	 * @throws InvalidInterfaceException
1601
+	 * @throws ReflectionException
1602
+	 */
1603
+	public function status_obj()
1604
+	{
1605
+		return $this->get_first_related('Status');
1606
+	}
1607
+
1608
+
1609
+	/**
1610
+	 * Returns the number of times this registration has checked into any of the datetimes
1611
+	 * its available for
1612
+	 *
1613
+	 * @return int
1614
+	 * @throws EE_Error
1615
+	 * @throws InvalidArgumentException
1616
+	 * @throws InvalidDataTypeException
1617
+	 * @throws InvalidInterfaceException
1618
+	 * @throws ReflectionException
1619
+	 */
1620
+	public function count_checkins()
1621
+	{
1622
+		return $this->get_model()->count_related($this, 'Checkin');
1623
+	}
1624
+
1625
+
1626
+	/**
1627
+	 * Returns the number of current Check-ins this registration is checked into for any of the datetimes the
1628
+	 * registration is for.  Note, this is ONLY checked in (does not include checkedout)
1629
+	 *
1630
+	 * @return int
1631
+	 * @throws EE_Error
1632
+	 * @throws InvalidArgumentException
1633
+	 * @throws InvalidDataTypeException
1634
+	 * @throws InvalidInterfaceException
1635
+	 * @throws ReflectionException
1636
+	 */
1637
+	public function count_checkins_not_checkedout()
1638
+	{
1639
+		return $this->get_model()->count_related($this, 'Checkin', [['CHK_in' => 1]]);
1640
+	}
1641
+
1642
+
1643
+	/**
1644
+	 * The purpose of this method is simply to check whether this registration can checkin to the given datetime.
1645
+	 *
1646
+	 * @param int | EE_Datetime $DTT_OR_ID      The datetime the registration is being checked against
1647
+	 * @param bool              $check_approved This is used to indicate whether the caller wants can_checkin to also
1648
+	 *                                          consider registration status as well as datetime access.
1649
+	 * @return bool
1650
+	 * @throws EE_Error
1651
+	 * @throws InvalidArgumentException
1652
+	 * @throws InvalidDataTypeException
1653
+	 * @throws InvalidInterfaceException
1654
+	 * @throws ReflectionException
1655
+	 */
1656
+	public function can_checkin($DTT_OR_ID, $check_approved = true)
1657
+	{
1658
+		$DTT_ID = EEM_Datetime::instance()->ensure_is_ID($DTT_OR_ID);
1659
+		// first check registration status
1660
+		if (! $DTT_ID || ($check_approved && ! $this->is_approved())) {
1661
+			return false;
1662
+		}
1663
+		// is there a datetime ticket that matches this dtt_ID?
1664
+		if (
1665
+			! (EEM_Datetime_Ticket::instance()->exists(
1666
+				[
1667
+					[
1668
+						'TKT_ID' => $this->get('TKT_ID'),
1669
+						'DTT_ID' => $DTT_ID,
1670
+					],
1671
+				]
1672
+			))
1673
+		) {
1674
+			return false;
1675
+		}
1676
+
1677
+		// final check is against TKT_uses
1678
+		return $this->verify_can_checkin_against_TKT_uses($DTT_ID);
1679
+	}
1680
+
1681
+
1682
+	/**
1683
+	 * This method verifies whether the user can checkin for the given datetime considering the max uses value set on
1684
+	 * the ticket. To do this,  a query is done to get the count of the datetime records already checked into.  If the
1685
+	 * datetime given does not have a check-in record and checking in for that datetime will exceed the allowed uses,
1686
+	 * then return false.  Otherwise return true.
1687
+	 *
1688
+	 * @param int | EE_Datetime $DTT_OR_ID The datetime the registration is being checked against
1689
+	 * @return bool true means can checkin.  false means cannot checkin.
1690
+	 * @throws EE_Error
1691
+	 * @throws InvalidArgumentException
1692
+	 * @throws InvalidDataTypeException
1693
+	 * @throws InvalidInterfaceException
1694
+	 * @throws ReflectionException
1695
+	 */
1696
+	public function verify_can_checkin_against_TKT_uses($DTT_OR_ID)
1697
+	{
1698
+		$DTT_ID = EEM_Datetime::instance()->ensure_is_ID($DTT_OR_ID);
1699
+
1700
+		if (! $DTT_ID) {
1701
+			return false;
1702
+		}
1703
+
1704
+		$max_uses = $this->ticket() instanceof EE_Ticket
1705
+			? $this->ticket()->uses()
1706
+			: EE_INF;
1707
+
1708
+		// if max uses is not set or equals infinity then return true cause its not a factor for whether user can
1709
+		// check-in or not.
1710
+		if (! $max_uses || $max_uses === EE_INF) {
1711
+			return true;
1712
+		}
1713
+
1714
+		// does this datetime have a checkin record?  If so, then the dtt count has already been verified so we can just
1715
+		// go ahead and toggle.
1716
+		if (EEM_Checkin::instance()->exists([['REG_ID' => $this->ID(), 'DTT_ID' => $DTT_ID]])) {
1717
+			return true;
1718
+		}
1719
+
1720
+		// made it here so the last check is whether the number of checkins per unique datetime on this registration
1721
+		// disallows further check-ins.
1722
+		$count_unique_dtt_checkins = EEM_Checkin::instance()->count(
1723
+			[
1724
+				[
1725
+					'REG_ID' => $this->ID(),
1726
+					'CHK_in' => true,
1727
+				],
1728
+			],
1729
+			'DTT_ID',
1730
+			true
1731
+		);
1732
+		// checkins have already reached their max number of uses
1733
+		// so registrant can NOT checkin
1734
+		if ($count_unique_dtt_checkins >= $max_uses) {
1735
+			EE_Error::add_error(
1736
+				esc_html__(
1737
+					'Check-in denied because number of datetime uses for the ticket has been reached or exceeded.',
1738
+					'event_espresso'
1739
+				),
1740
+				__FILE__,
1741
+				__FUNCTION__,
1742
+				__LINE__
1743
+			);
1744
+			return false;
1745
+		}
1746
+		return true;
1747
+	}
1748
+
1749
+
1750
+	/**
1751
+	 * toggle Check-in status for this registration
1752
+	 * Check-ins are toggled in the following order:
1753
+	 * never checked in -> checked in
1754
+	 * checked in -> checked out
1755
+	 * checked out -> checked in
1756
+	 *
1757
+	 * @param int  $DTT_ID  include specific datetime to toggle Check-in for.
1758
+	 *                      If not included or null, then it is assumed latest datetime is being toggled.
1759
+	 * @param bool $verify  If true then can_checkin() is used to verify whether the person
1760
+	 *                      can be checked in or not.  Otherwise this forces change in checkin status.
1761
+	 * @return bool|int     the chk_in status toggled to OR false if nothing got changed.
1762
+	 * @throws EE_Error
1763
+	 * @throws InvalidArgumentException
1764
+	 * @throws InvalidDataTypeException
1765
+	 * @throws InvalidInterfaceException
1766
+	 * @throws ReflectionException
1767
+	 */
1768
+	public function toggle_checkin_status($DTT_ID = null, $verify = false)
1769
+	{
1770
+		if (empty($DTT_ID)) {
1771
+			$datetime = $this->get_latest_related_datetime();
1772
+			$DTT_ID   = $datetime instanceof EE_Datetime ? $datetime->ID() : 0;
1773
+			// verify the registration can checkin for the given DTT_ID
1774
+		} elseif (! $this->can_checkin($DTT_ID, $verify)) {
1775
+			EE_Error::add_error(
1776
+				sprintf(
1777
+					esc_html__(
1778
+						'The given registration (ID:%1$d) can not be checked in to the given DTT_ID (%2$d), because the registration does not have access',
1779
+						'event_espresso'
1780
+					),
1781
+					$this->ID(),
1782
+					$DTT_ID
1783
+				),
1784
+				__FILE__,
1785
+				__FUNCTION__,
1786
+				__LINE__
1787
+			);
1788
+			return false;
1789
+		}
1790
+		$status_paths = [
1791
+			EE_Checkin::status_checked_never => EE_Checkin::status_checked_in,
1792
+			EE_Checkin::status_checked_in    => EE_Checkin::status_checked_out,
1793
+			EE_Checkin::status_checked_out   => EE_Checkin::status_checked_in,
1794
+		];
1795
+		// start by getting the current status so we know what status we'll be changing to.
1796
+		$cur_status = $this->check_in_status_for_datetime($DTT_ID);
1797
+		$status_to  = $status_paths[ $cur_status ];
1798
+		// database only records true for checked IN or false for checked OUT
1799
+		// no record ( null ) means checked in NEVER, but we obviously don't save that
1800
+		$new_status = $status_to === EE_Checkin::status_checked_in;
1801
+		// add relation - note Check-ins are always creating new rows
1802
+		// because we are keeping track of Check-ins over time.
1803
+		// Eventually we'll probably want to show a list table
1804
+		// for the individual Check-ins so that they can be managed.
1805
+		$checkin = EE_Checkin::new_instance(
1806
+			[
1807
+				'REG_ID' => $this->ID(),
1808
+				'DTT_ID' => $DTT_ID,
1809
+				'CHK_in' => $new_status,
1810
+			]
1811
+		);
1812
+		// if the record could not be saved then return false
1813
+		if ($checkin->save() === 0) {
1814
+			if (WP_DEBUG) {
1815
+				global $wpdb;
1816
+				$error = sprintf(
1817
+					esc_html__(
1818
+						'Registration check in update failed because of the following database error: %1$s%2$s',
1819
+						'event_espresso'
1820
+					),
1821
+					'<br />',
1822
+					$wpdb->last_error
1823
+				);
1824
+			} else {
1825
+				$error = esc_html__(
1826
+					'Registration check in update failed because of an unknown database error',
1827
+					'event_espresso'
1828
+				);
1829
+			}
1830
+			EE_Error::add_error($error, __FILE__, __FUNCTION__, __LINE__);
1831
+			return false;
1832
+		}
1833
+		// Fire a checked_in and checkout_out action.
1834
+		$checked_status = $status_to === EE_Checkin::status_checked_in
1835
+			? 'checked_in'
1836
+			: 'checked_out';
1837
+		do_action("AHEE__EE_Registration__toggle_checkin_status__{$checked_status}", $this, $DTT_ID);
1838
+		return $status_to;
1839
+	}
1840
+
1841
+
1842
+	/**
1843
+	 * Returns the latest datetime related to this registration (via the ticket attached to the registration).
1844
+	 * "Latest" is defined by the `DTT_EVT_start` column.
1845
+	 *
1846
+	 * @return EE_Datetime|null
1847
+	 * @throws EE_Error
1848
+	 * @throws InvalidArgumentException
1849
+	 * @throws InvalidDataTypeException
1850
+	 * @throws InvalidInterfaceException
1851
+	 * @throws ReflectionException
1852
+	 */
1853
+	public function get_latest_related_datetime(): ?EE_Datetime
1854
+	{
1855
+		return EEM_Datetime::instance()->get_one(
1856
+			[
1857
+				[
1858
+					'Ticket.Registration.REG_ID' => $this->ID(),
1859
+				],
1860
+				'order_by' => ['DTT_EVT_start' => 'DESC'],
1861
+			]
1862
+		);
1863
+	}
1864
+
1865
+
1866
+	/**
1867
+	 * Returns the earliest datetime related to this registration (via the ticket attached to the registration).
1868
+	 * "Earliest" is defined by the `DTT_EVT_start` column.
1869
+	 *
1870
+	 * @return EE_Base_Class|EE_Soft_Delete_Base_Class|NULL
1871
+	 * @throws EE_Error
1872
+	 * @throws InvalidArgumentException
1873
+	 * @throws InvalidDataTypeException
1874
+	 * @throws InvalidInterfaceException
1875
+	 * @throws ReflectionException
1876
+	 */
1877
+	public function get_earliest_related_datetime()
1878
+	{
1879
+		return EEM_Datetime::instance()->get_one(
1880
+			[
1881
+				[
1882
+					'Ticket.Registration.REG_ID' => $this->ID(),
1883
+				],
1884
+				'order_by' => ['DTT_EVT_start' => 'ASC'],
1885
+			]
1886
+		);
1887
+	}
1888
+
1889
+
1890
+	/**
1891
+	 * This method simply returns the check-in status for this registration and the given datetime.
1892
+	 * If neither the datetime nor the checkin values are provided as arguments,
1893
+	 * then this will return the LATEST check-in status for the registration across all datetimes it belongs to.
1894
+	 *
1895
+	 * @param int|null        $DTT_ID  The ID of the datetime we're checking against
1896
+	 *                                 (if empty we'll get the primary datetime for
1897
+	 *                                 this registration (via event) and use it's ID);
1898
+	 * @param EE_Checkin|null $checkin If present, we use the given checkin object rather than the dtt_id.
1899
+	 * @return int                     Integer representing Check-in status.
1900
+	 * @throws EE_Error
1901
+	 * @throws ReflectionException
1902
+	 */
1903
+	public function check_in_status_for_datetime(?int $DTT_ID = 0, ?EE_Checkin $checkin = null): int
1904
+	{
1905
+		if ($checkin instanceof EE_Checkin) {
1906
+			return $checkin->status();
1907
+		}
1908
+
1909
+		if (! $DTT_ID) {
1910
+			return EE_Checkin::status_invalid;
1911
+		}
1912
+
1913
+		$checkin_query_params = [
1914
+			0          => ['DTT_ID' => $DTT_ID],
1915
+			'order_by' => ['CHK_timestamp' => 'DESC'],
1916
+		];
1917
+
1918
+		$checkin = $this->get_first_related(
1919
+			'Checkin',
1920
+			$checkin_query_params
1921
+		);
1922
+		return $checkin instanceof EE_Checkin ? $checkin->status() : EE_Checkin::status_checked_never;
1923
+	}
1924
+
1925
+
1926
+	/**
1927
+	 * This method returns a localized message for the toggled Check-in message.
1928
+	 *
1929
+	 * @param int|null $DTT_ID include specific datetime to get the correct Check-in message.  If not included or null,
1930
+	 *                         then it is assumed Check-in for primary datetime was toggled.
1931
+	 * @param bool     $error  This just flags that you want an error message returned. This is put in so that the error
1932
+	 *                         message can be customized with the attendee name.
1933
+	 * @return string internationalized message
1934
+	 * @throws EE_Error
1935
+	 * @throws ReflectionException
1936
+	 */
1937
+	public function get_checkin_msg(?int $DTT_ID, bool $error = false): string
1938
+	{
1939
+		// let's get the attendee first so we can include the name of the attendee
1940
+		$attendee = $this->attendee();
1941
+		if ($attendee instanceof EE_Attendee) {
1942
+			if ($error) {
1943
+				return sprintf(
1944
+					esc_html__("%s's check-in status was not changed.", "event_espresso"),
1945
+					$attendee->full_name()
1946
+				);
1947
+			}
1948
+			$cur_status = $this->check_in_status_for_datetime($DTT_ID);
1949
+			// what is the status message going to be?
1950
+			switch ($cur_status) {
1951
+				case EE_Checkin::status_checked_never:
1952
+					return sprintf(
1953
+						esc_html__('%s has been removed from Check-in records', 'event_espresso'),
1954
+						$attendee->full_name()
1955
+					);
1956
+				case EE_Checkin::status_checked_in:
1957
+					return sprintf(esc_html__('%s has been checked in', 'event_espresso'), $attendee->full_name());
1958
+				case EE_Checkin::status_checked_out:
1959
+					return sprintf(esc_html__('%s has been checked out', 'event_espresso'), $attendee->full_name());
1960
+			}
1961
+		}
1962
+		return esc_html__('The check-in status could not be determined.', 'event_espresso');
1963
+	}
1964
+
1965
+
1966
+	/**
1967
+	 * Returns the related EE_Transaction to this registration
1968
+	 *
1969
+	 * @return EE_Transaction
1970
+	 * @throws EE_Error
1971
+	 * @throws EntityNotFoundException
1972
+	 * @throws ReflectionException
1973
+	 */
1974
+	public function transaction(): EE_Transaction
1975
+	{
1976
+		$transaction = EEM_Transaction::instance()->get_one_by_ID($this->transaction_ID());
1977
+		if (! $transaction instanceof \EE_Transaction) {
1978
+			throw new EntityNotFoundException('Transaction ID', $this->transaction_ID());
1979
+		}
1980
+		return $transaction;
1981
+	}
1982
+
1983
+
1984
+	/**
1985
+	 * get Registration Code
1986
+	 *
1987
+	 * @return string
1988
+	 * @throws EE_Error
1989
+	 * @throws InvalidArgumentException
1990
+	 * @throws InvalidDataTypeException
1991
+	 * @throws InvalidInterfaceException
1992
+	 * @throws ReflectionException
1993
+	 */
1994
+	public function reg_code(): string
1995
+	{
1996
+		return $this->get('REG_code')
1997
+			?: '';
1998
+	}
1999
+
2000
+
2001
+	/**
2002
+	 * @return mixed
2003
+	 * @throws EE_Error
2004
+	 * @throws InvalidArgumentException
2005
+	 * @throws InvalidDataTypeException
2006
+	 * @throws InvalidInterfaceException
2007
+	 * @throws ReflectionException
2008
+	 */
2009
+	public function transaction_ID()
2010
+	{
2011
+		return $this->get('TXN_ID');
2012
+	}
2013
+
2014
+
2015
+	/**
2016
+	 * @return int
2017
+	 * @throws EE_Error
2018
+	 * @throws InvalidArgumentException
2019
+	 * @throws InvalidDataTypeException
2020
+	 * @throws InvalidInterfaceException
2021
+	 * @throws ReflectionException
2022
+	 */
2023
+	public function ticket_ID()
2024
+	{
2025
+		return $this->get('TKT_ID');
2026
+	}
2027
+
2028
+
2029
+	/**
2030
+	 * Set Registration Code
2031
+	 *
2032
+	 * @param RegCode|string $REG_code Registration Code
2033
+	 * @param boolean        $use_default
2034
+	 * @throws EE_Error
2035
+	 * @throws InvalidArgumentException
2036
+	 * @throws InvalidDataTypeException
2037
+	 * @throws InvalidInterfaceException
2038
+	 * @throws ReflectionException
2039
+	 */
2040
+	public function set_reg_code($REG_code, bool $use_default = false)
2041
+	{
2042
+		if (empty($REG_code)) {
2043
+			EE_Error::add_error(
2044
+				esc_html__('REG_code can not be empty.', 'event_espresso'),
2045
+				__FILE__,
2046
+				__FUNCTION__,
2047
+				__LINE__
2048
+			);
2049
+			return;
2050
+		}
2051
+		if (! $this->reg_code()) {
2052
+			parent::set('REG_code', $REG_code, $use_default);
2053
+		} else {
2054
+			EE_Error::doing_it_wrong(
2055
+				__CLASS__ . '::' . __FUNCTION__,
2056
+				esc_html__('Can not change a registration REG_code once it has been set.', 'event_espresso'),
2057
+				'4.6.0'
2058
+			);
2059
+		}
2060
+	}
2061
+
2062
+
2063
+	/**
2064
+	 * Returns all other registrations in the same group as this registrant who have the same ticket option.
2065
+	 * Note, if you want to just get all registrations in the same transaction (group), use:
2066
+	 *    $registration->transaction()->registrations();
2067
+	 *
2068
+	 * @return EE_Registration[] or empty array if this isn't a group registration.
2069
+	 * @throws EE_Error
2070
+	 * @throws InvalidArgumentException
2071
+	 * @throws InvalidDataTypeException
2072
+	 * @throws InvalidInterfaceException
2073
+	 * @throws ReflectionException
2074
+	 * @since 4.5.0
2075
+	 */
2076
+	public function get_all_other_registrations_in_group()
2077
+	{
2078
+		if ($this->group_size() < 2) {
2079
+			return [];
2080
+		}
2081
+
2082
+		$query[0] = [
2083
+			'TXN_ID' => $this->transaction_ID(),
2084
+			'REG_ID' => ['!=', $this->ID()],
2085
+			'TKT_ID' => $this->ticket_ID(),
2086
+		];
2087
+		/** @var EE_Registration[] $registrations */
2088
+		$registrations = $this->get_model()->get_all($query);
2089
+		return $registrations;
2090
+	}
2091
+
2092
+
2093
+	/**
2094
+	 * Return the link to the admin details for the object.
2095
+	 *
2096
+	 * @return string
2097
+	 * @throws EE_Error
2098
+	 * @throws InvalidArgumentException
2099
+	 * @throws InvalidDataTypeException
2100
+	 * @throws InvalidInterfaceException
2101
+	 * @throws ReflectionException
2102
+	 */
2103
+	public function get_admin_details_link()
2104
+	{
2105
+		EE_Registry::instance()->load_helper('URL');
2106
+		return EEH_URL::add_query_args_and_nonce(
2107
+			[
2108
+				'page'    => 'espresso_registrations',
2109
+				'action'  => 'view_registration',
2110
+				'_REG_ID' => $this->ID(),
2111
+			],
2112
+			admin_url('admin.php')
2113
+		);
2114
+	}
2115
+
2116
+
2117
+	/**
2118
+	 * Returns the link to the editor for the object.  Sometimes this is the same as the details.
2119
+	 *
2120
+	 * @return string
2121
+	 * @throws EE_Error
2122
+	 * @throws InvalidArgumentException
2123
+	 * @throws InvalidDataTypeException
2124
+	 * @throws InvalidInterfaceException
2125
+	 * @throws ReflectionException
2126
+	 */
2127
+	public function get_admin_edit_link()
2128
+	{
2129
+		return $this->get_admin_details_link();
2130
+	}
2131
+
2132
+
2133
+	/**
2134
+	 * Returns the link to a settings page for the object.
2135
+	 *
2136
+	 * @return string
2137
+	 * @throws EE_Error
2138
+	 * @throws InvalidArgumentException
2139
+	 * @throws InvalidDataTypeException
2140
+	 * @throws InvalidInterfaceException
2141
+	 * @throws ReflectionException
2142
+	 */
2143
+	public function get_admin_settings_link()
2144
+	{
2145
+		return $this->get_admin_details_link();
2146
+	}
2147
+
2148
+
2149
+	/**
2150
+	 * Returns the link to the "overview" for the object (typically the "list table" view).
2151
+	 *
2152
+	 * @return string
2153
+	 * @throws EE_Error
2154
+	 * @throws InvalidArgumentException
2155
+	 * @throws InvalidDataTypeException
2156
+	 * @throws InvalidInterfaceException
2157
+	 * @throws ReflectionException
2158
+	 */
2159
+	public function get_admin_overview_link()
2160
+	{
2161
+		EE_Registry::instance()->load_helper('URL');
2162
+		return EEH_URL::add_query_args_and_nonce(
2163
+			[
2164
+				'page' => 'espresso_registrations',
2165
+			],
2166
+			admin_url('admin.php')
2167
+		);
2168
+	}
2169
+
2170
+
2171
+	/**
2172
+	 * @param array $query_params
2173
+	 * @return EE_Base_Class[]|EE_Registration[]
2174
+	 * @throws EE_Error
2175
+	 * @throws InvalidArgumentException
2176
+	 * @throws InvalidDataTypeException
2177
+	 * @throws InvalidInterfaceException
2178
+	 * @throws ReflectionException
2179
+	 */
2180
+	public function payments($query_params = [])
2181
+	{
2182
+		return $this->get_many_related('Payment', $query_params);
2183
+	}
2184
+
2185
+
2186
+	/**
2187
+	 * @param array $query_params
2188
+	 * @return EE_Base_Class[]|EE_Registration_Payment[]
2189
+	 * @throws EE_Error
2190
+	 * @throws InvalidArgumentException
2191
+	 * @throws InvalidDataTypeException
2192
+	 * @throws InvalidInterfaceException
2193
+	 * @throws ReflectionException
2194
+	 */
2195
+	public function registration_payments($query_params = [])
2196
+	{
2197
+		return $this->get_many_related('Registration_Payment', $query_params);
2198
+	}
2199
+
2200
+
2201
+	/**
2202
+	 * This grabs the payment method corresponding to the last payment made for the amount owing on the registration.
2203
+	 * Note: if there are no payments on the registration there will be no payment method returned.
2204
+	 *
2205
+	 * @return EE_Payment|EE_Payment_Method|null
2206
+	 * @throws EE_Error
2207
+	 * @throws InvalidArgumentException
2208
+	 * @throws InvalidDataTypeException
2209
+	 * @throws InvalidInterfaceException
2210
+	 */
2211
+	public function payment_method()
2212
+	{
2213
+		return EEM_Payment_Method::instance()->get_last_used_for_registration($this);
2214
+	}
2215
+
2216
+
2217
+	/**
2218
+	 * @return \EE_Line_Item
2219
+	 * @throws EE_Error
2220
+	 * @throws EntityNotFoundException
2221
+	 * @throws InvalidArgumentException
2222
+	 * @throws InvalidDataTypeException
2223
+	 * @throws InvalidInterfaceException
2224
+	 * @throws ReflectionException
2225
+	 */
2226
+	public function ticket_line_item()
2227
+	{
2228
+		$ticket            = $this->ticket();
2229
+		$transaction       = $this->transaction();
2230
+		$line_item         = null;
2231
+		$ticket_line_items = \EEH_Line_Item::get_line_items_by_object_type_and_IDs(
2232
+			$transaction->total_line_item(),
2233
+			'Ticket',
2234
+			[$ticket->ID()]
2235
+		);
2236
+		foreach ($ticket_line_items as $ticket_line_item) {
2237
+			if (
2238
+				$ticket_line_item instanceof \EE_Line_Item
2239
+				&& $ticket_line_item->OBJ_type() === 'Ticket'
2240
+				&& $ticket_line_item->OBJ_ID() === $ticket->ID()
2241
+			) {
2242
+				$line_item = $ticket_line_item;
2243
+				break;
2244
+			}
2245
+		}
2246
+		if (! ($line_item instanceof \EE_Line_Item && $line_item->OBJ_type() === 'Ticket')) {
2247
+			throw new EntityNotFoundException('Line Item Ticket ID', $ticket->ID());
2248
+		}
2249
+		return $line_item;
2250
+	}
2251
+
2252
+
2253
+	/**
2254
+	 * Soft Deletes this model object.
2255
+	 *
2256
+	 * @param string $source function name that called this method
2257
+	 * @return boolean | int
2258
+	 * @throws DomainException
2259
+	 * @throws EE_Error
2260
+	 * @throws EntityNotFoundException
2261
+	 * @throws InvalidArgumentException
2262
+	 * @throws InvalidDataTypeException
2263
+	 * @throws InvalidInterfaceException
2264
+	 * @throws ReflectionException
2265
+	 * @throws RuntimeException
2266
+	 * @throws UnexpectedEntityException
2267
+	 */
2268
+	public function delete()
2269
+	{
2270
+		if ($this->update_extra_meta(EE_Registration::PRE_TRASH_REG_STATUS_KEY, $this->status_ID()) === true) {
2271
+			$this->set_status(EEM_Registration::status_id_cancelled);
2272
+		}
2273
+		return parent::delete();
2274
+	}
2275
+
2276
+
2277
+	/**
2278
+	 * Restores whatever the previous status was on a registration before it was trashed (if possible)
2279
+	 *
2280
+	 * @param string $source function name that called this method
2281
+	 * @return bool|int
2282
+	 * @throws DomainException
2283
+	 * @throws EE_Error
2284
+	 * @throws EntityNotFoundException
2285
+	 * @throws InvalidArgumentException
2286
+	 * @throws InvalidDataTypeException
2287
+	 * @throws InvalidInterfaceException
2288
+	 * @throws ReflectionException
2289
+	 * @throws RuntimeException
2290
+	 * @throws UnexpectedEntityException
2291
+	 */
2292
+	public function restore()
2293
+	{
2294
+		$previous_status = $this->get_extra_meta(
2295
+			EE_Registration::PRE_TRASH_REG_STATUS_KEY,
2296
+			true,
2297
+			EEM_Registration::status_id_cancelled
2298
+		);
2299
+		if ($previous_status) {
2300
+			$this->delete_extra_meta(EE_Registration::PRE_TRASH_REG_STATUS_KEY);
2301
+			$this->set_status($previous_status);
2302
+		}
2303
+		return parent::restore();
2304
+	}
2305
+
2306
+
2307
+	/**
2308
+	 * possibly toggle Registration status based on comparison of REG_paid vs REG_final_price
2309
+	 *
2310
+	 * @param boolean $trigger_set_status_logic  EE_Registration::set_status() can trigger additional logic
2311
+	 *                                           depending on whether the reg status changes to or from "Approved"
2312
+	 * @return boolean whether the Registration status was updated
2313
+	 * @throws DomainException
2314
+	 * @throws EE_Error
2315
+	 * @throws EntityNotFoundException
2316
+	 * @throws InvalidArgumentException
2317
+	 * @throws InvalidDataTypeException
2318
+	 * @throws InvalidInterfaceException
2319
+	 * @throws ReflectionException
2320
+	 * @throws RuntimeException
2321
+	 * @throws UnexpectedEntityException
2322
+	 */
2323
+	public function updateStatusBasedOnTotalPaid($trigger_set_status_logic = true)
2324
+	{
2325
+		$paid  = $this->paid();
2326
+		$price = $this->final_price();
2327
+		switch (true) {
2328
+			// overpaid or paid
2329
+			case EEH_Money::compare_floats($paid, $price, '>'):
2330
+			case EEH_Money::compare_floats($paid, $price):
2331
+				$new_status = EEM_Registration::status_id_approved;
2332
+				break;
2333
+			//  underpaid
2334
+			case EEH_Money::compare_floats($paid, $price, '<'):
2335
+				$new_status = EEM_Registration::status_id_pending_payment;
2336
+				break;
2337
+			// uhhh Houston...
2338
+			default:
2339
+				throw new RuntimeException(
2340
+					esc_html__('The total paid calculation for this registration is inaccurate.', 'event_espresso')
2341
+				);
2342
+		}
2343
+		if ($new_status !== $this->status_ID()) {
2344
+			if ($trigger_set_status_logic) {
2345
+				return $this->set_status($new_status);
2346
+			}
2347
+			parent::set('STS_ID', $new_status);
2348
+			return true;
2349
+		}
2350
+		return false;
2351
+	}
2352
+
2353
+
2354
+	/*************************** DEPRECATED ***************************/
2355
+
2356
+
2357
+	/**
2358
+	 * @deprecated
2359
+	 * @since     4.7.0
2360
+	 */
2361
+	public function price_paid()
2362
+	{
2363
+		EE_Error::doing_it_wrong(
2364
+			'EE_Registration::price_paid()',
2365
+			esc_html__(
2366
+				'This method is deprecated, please use EE_Registration::final_price() instead.',
2367
+				'event_espresso'
2368
+			),
2369
+			'4.7.0'
2370
+		);
2371
+		return $this->final_price();
2372
+	}
2373
+
2374
+
2375
+	/**
2376
+	 * @param float $REG_final_price
2377
+	 * @throws EE_Error
2378
+	 * @throws EntityNotFoundException
2379
+	 * @throws InvalidArgumentException
2380
+	 * @throws InvalidDataTypeException
2381
+	 * @throws InvalidInterfaceException
2382
+	 * @throws ReflectionException
2383
+	 * @throws RuntimeException
2384
+	 * @throws DomainException
2385
+	 * @deprecated
2386
+	 * @since     4.7.0
2387
+	 */
2388
+	public function set_price_paid($REG_final_price = 0.00)
2389
+	{
2390
+		EE_Error::doing_it_wrong(
2391
+			'EE_Registration::set_price_paid()',
2392
+			esc_html__(
2393
+				'This method is deprecated, please use EE_Registration::set_final_price() instead.',
2394
+				'event_espresso'
2395
+			),
2396
+			'4.7.0'
2397
+		);
2398
+		$this->set_final_price($REG_final_price);
2399
+	}
2400
+
2401
+
2402
+	/**
2403
+	 * @return string
2404
+	 * @throws EE_Error
2405
+	 * @throws InvalidArgumentException
2406
+	 * @throws InvalidDataTypeException
2407
+	 * @throws InvalidInterfaceException
2408
+	 * @throws ReflectionException
2409
+	 * @deprecated
2410
+	 * @since 4.7.0
2411
+	 */
2412
+	public function pretty_price_paid()
2413
+	{
2414
+		EE_Error::doing_it_wrong(
2415
+			'EE_Registration::pretty_price_paid()',
2416
+			esc_html__(
2417
+				'This method is deprecated, please use EE_Registration::pretty_final_price() instead.',
2418
+				'event_espresso'
2419
+			),
2420
+			'4.7.0'
2421
+		);
2422
+		return $this->pretty_final_price();
2423
+	}
2424
+
2425
+
2426
+	/**
2427
+	 * Gets the primary datetime related to this registration via the related Event to this registration
2428
+	 *
2429
+	 * @return EE_Datetime
2430
+	 * @throws EE_Error
2431
+	 * @throws EntityNotFoundException
2432
+	 * @throws InvalidArgumentException
2433
+	 * @throws InvalidDataTypeException
2434
+	 * @throws InvalidInterfaceException
2435
+	 * @throws ReflectionException
2436
+	 * @deprecated 4.9.17
2437
+	 */
2438
+	public function get_related_primary_datetime()
2439
+	{
2440
+		EE_Error::doing_it_wrong(
2441
+			__METHOD__,
2442
+			esc_html__(
2443
+				'Use EE_Registration::get_latest_related_datetime() or EE_Registration::get_earliest_related_datetime()',
2444
+				'event_espresso'
2445
+			),
2446
+			'4.9.17',
2447
+			'5.0.0'
2448
+		);
2449
+		return $this->event()->primary_datetime();
2450
+	}
2451
+
2452
+
2453
+	/**
2454
+	 * Returns the contact's name (or "Unknown" if there is no contact.)
2455
+	 *
2456
+	 * @return string
2457
+	 * @throws EE_Error
2458
+	 * @throws InvalidArgumentException
2459
+	 * @throws InvalidDataTypeException
2460
+	 * @throws InvalidInterfaceException
2461
+	 * @throws ReflectionException
2462
+	 * @since 4.10.12.p
2463
+	 */
2464
+	public function name()
2465
+	{
2466
+		return $this->attendeeName();
2467
+	}
2468
+
2469
+
2470
+	/**
2471
+	 * @return bool
2472
+	 * @throws EE_Error
2473
+	 * @throws ReflectionException
2474
+	 */
2475
+	public function wasMoved(): bool
2476
+	{
2477
+		// only need to check 'registration-moved-to' because
2478
+		// the existence of a new REG ID means the registration was moved
2479
+		$reg_moved = $this->get_extra_meta('registration-moved-to', true, []);
2480
+		return isset($reg_moved['NEW_REG_ID']) && $reg_moved['NEW_REG_ID'];
2481
+	}
2482
+
2483
+
2484
+	/**
2485
+	 * @param EE_Payment $payment
2486
+	 * @param float|null $amount
2487
+	 * @return float
2488
+	 * @throws EE_Error
2489
+	 * @throws ReflectionException
2490
+	 * @since 5.0.8.p
2491
+	 */
2492
+	public function applyPayment(EE_Payment $payment, ?float $amount = null): float
2493
+	{
2494
+		$payment_amount = $amount ?? $payment->amount();
2495
+		// ensure $payment_amount is NOT negative
2496
+		$payment_amount = (float) abs($payment_amount);
2497
+		$payment_amount = $payment->is_a_refund()
2498
+			? $this->processRefund($payment_amount)
2499
+			: $this->processPayment($payment_amount);
2500
+		if ($payment_amount) {
2501
+			$reg_payment = EEM_Registration_Payment::instance()->get_one(
2502
+				[['REG_ID' => $this->ID(), 'PAY_ID' => $payment->ID()]]
2503
+			);
2504
+			// if existing registration payment exists
2505
+			if ($reg_payment instanceof EE_Registration_Payment) {
2506
+				// then update that record
2507
+				$reg_payment->set_amount($payment_amount);
2508
+			} else {
2509
+				// or add new relation between registration and payment and set amount
2510
+				$reg_payment = EE_Registration_Payment::new_instance(
2511
+					[
2512
+						'REG_ID'     => $this->ID(),
2513
+						'PAY_ID'     => $payment->ID(),
2514
+						'RPY_amount' => $payment_amount,
2515
+					]
2516
+				);
2517
+			}
2518
+			$reg_payment->save();
2519
+		}
2520
+		return $payment_amount;
2521
+	}
2522
+
2523
+
2524
+	/**
2525
+	 * @throws EE_Error
2526
+	 * @throws ReflectionException
2527
+	 */
2528
+	private function processPayment(float $payment_amount): float
2529
+	{
2530
+		$paid  = $this->paid();
2531
+		$owing = $this->final_price() - $paid;
2532
+		if ($owing <= 0) {
2533
+			return 0.0;
2534
+		}
2535
+		// don't allow payment amount to exceed the incoming amount, OR the amount owing
2536
+		$payment_amount = min($payment_amount, $owing);
2537
+		$paid           = $paid + $payment_amount;
2538
+		// calculate and set new REG_paid
2539
+		$this->set_paid($paid);
2540
+		// make it stick
2541
+		$this->save();
2542
+		return (float) $payment_amount;
2543
+	}
2544
+
2545
+
2546
+	/**
2547
+	 * @throws ReflectionException
2548
+	 * @throws EE_Error
2549
+	 */
2550
+	private function processRefund(float $payment_amount): float
2551
+	{
2552
+		$paid = $this->paid();
2553
+		if ($paid <= 0) {
2554
+			return 0.0;
2555
+		}
2556
+		// don't allow refund amount to exceed the incoming amount, OR the amount paid
2557
+		$payment_amount = min($payment_amount, $paid);
2558
+		// calculate and set new REG_paid
2559
+		$paid = $paid - $payment_amount;
2560
+		$this->set_paid($paid);
2561
+		// make it stick
2562
+		$this->save();
2563
+		// convert payment amount back to a negative value for storage in the db
2564
+		return (float) $payment_amount;
2565
+	}
2566 2566
 }
Please login to merge, or discard this patch.
core/db_classes/EE_Transaction.class.php 1 patch
Indentation   +1749 added lines, -1749 removed lines patch added patch discarded remove patch
@@ -13,1753 +13,1753 @@
 block discarded – undo
13 13
  */
14 14
 class EE_Transaction extends EE_Base_Class implements EEI_Transaction
15 15
 {
16
-    /**
17
-     * The length of time in seconds that a lock is applied before being considered expired.
18
-     * It is not long because a transaction should only be locked for the duration of the request that locked it
19
-     */
20
-    const LOCK_EXPIRATION = 2;
21
-
22
-    /**
23
-     * txn status upon initial construction.
24
-     *
25
-     * @var string
26
-     */
27
-    protected $_old_txn_status;
28
-
29
-    private ?EE_Registration $_primary_registrant = null;
30
-
31
-    /**
32
-     * @param array  $props_n_values          incoming values
33
-     * @param string $timezone                incoming timezone
34
-     *                                        (if not set the timezone set for the website will be used.)
35
-     * @param array  $date_formats            incoming date_formats in an array where the first value is the
36
-     *                                        date_format and the second value is the time format
37
-     * @return EE_Transaction
38
-     * @throws EE_Error
39
-     * @throws InvalidArgumentException
40
-     * @throws InvalidDataTypeException
41
-     * @throws InvalidInterfaceException
42
-     * @throws ReflectionException
43
-     */
44
-    public static function new_instance($props_n_values = [], $timezone = '', $date_formats = [])
45
-    {
46
-        $has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
47
-        $txn        = $has_object
48
-            ? $has_object
49
-            : new self($props_n_values, false, $timezone, $date_formats);
50
-        if (! $has_object) {
51
-            $txn->set_old_txn_status($txn->status_ID());
52
-        }
53
-        return $txn;
54
-    }
55
-
56
-
57
-    /**
58
-     * @param array  $props_n_values  incoming values from the database
59
-     * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
60
-     *                                the website will be used.
61
-     * @return EE_Transaction
62
-     * @throws EE_Error
63
-     * @throws InvalidArgumentException
64
-     * @throws InvalidDataTypeException
65
-     * @throws InvalidInterfaceException
66
-     * @throws ReflectionException
67
-     */
68
-    public static function new_instance_from_db($props_n_values = [], $timezone = '')
69
-    {
70
-        $txn = new self($props_n_values, true, $timezone);
71
-        $txn->set_old_txn_status($txn->status_ID());
72
-        return $txn;
73
-    }
74
-
75
-
76
-    /**
77
-     * Sets a meta field indicating that this TXN is locked and should not be updated in the db.
78
-     * If a lock has already been set, then we will attempt to remove it in case it has expired.
79
-     * If that also fails, then an exception is thrown.
80
-     *
81
-     * @throws EE_Error
82
-     * @throws InvalidArgumentException
83
-     * @throws InvalidDataTypeException
84
-     * @throws InvalidInterfaceException
85
-     * @throws ReflectionException
86
-     */
87
-    public function lock()
88
-    {
89
-        // attempt to set lock, but if that fails...
90
-        if (! $this->add_extra_meta('lock', time(), true)) {
91
-            // then attempt to remove the lock in case it is expired
92
-            if ($this->_remove_expired_lock()) {
93
-                // if removal was successful, then try setting lock again
94
-                $this->lock();
95
-            } else {
96
-                // but if the lock can not be removed, then throw an exception
97
-                throw new EE_Error(
98
-                    sprintf(
99
-                        esc_html__(
100
-                            'Could not lock Transaction %1$d because it is already locked, meaning another part of the system is currently editing it. It should already be unlocked by the time you read this, so please refresh the page and try again.',
101
-                            'event_espresso'
102
-                        ),
103
-                        $this->ID()
104
-                    )
105
-                );
106
-            }
107
-        }
108
-    }
109
-
110
-
111
-    /**
112
-     * removes transaction lock applied in EE_Transaction::lock()
113
-     *
114
-     * @return int
115
-     * @throws EE_Error
116
-     * @throws InvalidArgumentException
117
-     * @throws InvalidDataTypeException
118
-     * @throws InvalidInterfaceException
119
-     * @throws ReflectionException
120
-     */
121
-    public function unlock()
122
-    {
123
-        return $this->delete_extra_meta('lock');
124
-    }
125
-
126
-
127
-    /**
128
-     * Decides whether or not now is the right time to update the transaction.
129
-     * This is useful because we don't always know if it is safe to update the transaction
130
-     * and its related data. why?
131
-     * because it's possible that the transaction is being used in another
132
-     * request and could overwrite anything we save.
133
-     * So we want to only update the txn once we know that won't happen.
134
-     * We also check that the lock isn't expired, and remove it if it is
135
-     *
136
-     * @return boolean
137
-     * @throws EE_Error
138
-     * @throws InvalidArgumentException
139
-     * @throws InvalidDataTypeException
140
-     * @throws InvalidInterfaceException
141
-     * @throws ReflectionException
142
-     */
143
-    public function is_locked()
144
-    {
145
-        // if TXN is not locked, then return false immediately
146
-        if (! $this->_get_lock()) {
147
-            return false;
148
-        }
149
-        // if not, then let's try and remove the lock in case it's expired...
150
-        // _remove_expired_lock() returns 0 when lock is valid (ie: removed = false)
151
-        // and a positive number if the lock was removed (ie: number of locks deleted),
152
-        // so we need to return the opposite
153
-        return ! $this->_remove_expired_lock();
154
-    }
155
-
156
-
157
-    /**
158
-     * Gets the meta field indicating that this TXN is locked
159
-     *
160
-     * @return int
161
-     * @throws EE_Error
162
-     * @throws InvalidArgumentException
163
-     * @throws InvalidDataTypeException
164
-     * @throws InvalidInterfaceException
165
-     * @throws ReflectionException
166
-     */
167
-    protected function _get_lock()
168
-    {
169
-        return (int) $this->get_extra_meta('lock', true, 0);
170
-    }
171
-
172
-
173
-    /**
174
-     * If the lock on this transaction is expired, then we want to remove it so that the transaction can be updated
175
-     *
176
-     * @return int
177
-     * @throws EE_Error
178
-     * @throws InvalidArgumentException
179
-     * @throws InvalidDataTypeException
180
-     * @throws InvalidInterfaceException
181
-     * @throws ReflectionException
182
-     */
183
-    protected function _remove_expired_lock()
184
-    {
185
-        $locked = $this->_get_lock();
186
-        if ($locked && time() - EE_Transaction::LOCK_EXPIRATION > $locked) {
187
-            return $this->unlock();
188
-        }
189
-        return 0;
190
-    }
191
-
192
-
193
-    /**
194
-     * Set transaction total
195
-     *
196
-     * @param float $total total value of transaction
197
-     * @throws EE_Error
198
-     * @throws InvalidArgumentException
199
-     * @throws InvalidDataTypeException
200
-     * @throws InvalidInterfaceException
201
-     * @throws ReflectionException
202
-     */
203
-    public function set_total($total = 0.00)
204
-    {
205
-        $this->set('TXN_total', (float) $total);
206
-    }
207
-
208
-
209
-    /**
210
-     * Set Total Amount Paid to Date
211
-     *
212
-     * @param float $total_paid total amount paid to date (sum of all payments)
213
-     * @throws EE_Error
214
-     * @throws InvalidArgumentException
215
-     * @throws InvalidDataTypeException
216
-     * @throws InvalidInterfaceException
217
-     * @throws ReflectionException
218
-     */
219
-    public function set_paid($total_paid = 0.00)
220
-    {
221
-        $this->set('TXN_paid', (float) $total_paid);
222
-    }
223
-
224
-
225
-    /**
226
-     * Set transaction status
227
-     *
228
-     * @param string $status        whether the transaction is open, declined, accepted,
229
-     *                              or any number of custom values that can be set
230
-     * @throws EE_Error
231
-     * @throws InvalidArgumentException
232
-     * @throws InvalidDataTypeException
233
-     * @throws InvalidInterfaceException
234
-     * @throws ReflectionException
235
-     */
236
-    public function set_status($status = '')
237
-    {
238
-        $this->set('STS_ID', $status);
239
-    }
240
-
241
-
242
-    /**
243
-     * Set hash salt
244
-     *
245
-     * @param string $hash_salt required for some payment gateways
246
-     * @throws EE_Error
247
-     * @throws InvalidArgumentException
248
-     * @throws InvalidDataTypeException
249
-     * @throws InvalidInterfaceException
250
-     * @throws ReflectionException
251
-     */
252
-    public function set_hash_salt($hash_salt = '')
253
-    {
254
-        $this->set('TXN_hash_salt', $hash_salt);
255
-    }
256
-
257
-
258
-    /**
259
-     * Sets TXN_reg_steps array
260
-     *
261
-     * @param array $txn_reg_steps
262
-     * @throws EE_Error
263
-     * @throws InvalidArgumentException
264
-     * @throws InvalidDataTypeException
265
-     * @throws InvalidInterfaceException
266
-     * @throws ReflectionException
267
-     */
268
-    public function set_reg_steps(array $txn_reg_steps)
269
-    {
270
-        $this->set('TXN_reg_steps', $txn_reg_steps);
271
-    }
272
-
273
-
274
-    /**
275
-     * Gets TXN_reg_steps
276
-     *
277
-     * @return array
278
-     * @throws EE_Error
279
-     * @throws InvalidArgumentException
280
-     * @throws InvalidDataTypeException
281
-     * @throws InvalidInterfaceException
282
-     * @throws ReflectionException
283
-     */
284
-    public function reg_steps()
285
-    {
286
-        $TXN_reg_steps = $this->get('TXN_reg_steps');
287
-        return is_array($TXN_reg_steps)
288
-            ? $TXN_reg_steps
289
-            : [];
290
-    }
291
-
292
-
293
-    /**
294
-     * @param string|null $schema
295
-     *     Schemas:
296
-     *     'localized_float': "3,023.00"
297
-     *     'no_currency_code': "$3,023.00"
298
-     *     null: "$3,023.00<span>USD</span>"
299
-     * @return string of transaction's total cost, with currency symbol and decimal
300
-     * @throws EE_Error
301
-     * @throws InvalidArgumentException
302
-     * @throws InvalidDataTypeException
303
-     * @throws InvalidInterfaceException
304
-     * @throws ReflectionException
305
-     */
306
-    public function pretty_total(?string $schema = null)
307
-    {
308
-        return $this->get_pretty('TXN_total', $schema);
309
-    }
310
-
311
-
312
-    /**
313
-     * Gets the amount paid in a pretty string (formatted and with currency symbol)
314
-     *
315
-     * @param string|null $schema
316
-     *     Schemas:
317
-     *     'localized_float': "3,023.00"
318
-     *     'no_currency_code': "$3,023.00"
319
-     *     null: "$3,023.00<span>USD</span>"
320
-     * @return string
321
-     * @throws EE_Error
322
-     * @throws InvalidArgumentException
323
-     * @throws InvalidDataTypeException
324
-     * @throws InvalidInterfaceException
325
-     * @throws ReflectionException
326
-     */
327
-    public function pretty_paid(?string $schema = null)
328
-    {
329
-        return $this->get_pretty('TXN_paid', $schema);
330
-    }
331
-
332
-
333
-    /**
334
-     * calculate the amount remaining for this transaction and return;
335
-     *
336
-     * @return float amount remaining
337
-     * @throws EE_Error
338
-     * @throws InvalidArgumentException
339
-     * @throws InvalidDataTypeException
340
-     * @throws InvalidInterfaceException
341
-     * @throws ReflectionException
342
-     */
343
-    public function remaining()
344
-    {
345
-        return $this->total() - $this->paid();
346
-    }
347
-
348
-
349
-    /**
350
-     * get Transaction Total
351
-     *
352
-     * @return float
353
-     * @throws EE_Error
354
-     * @throws InvalidArgumentException
355
-     * @throws InvalidDataTypeException
356
-     * @throws InvalidInterfaceException
357
-     * @throws ReflectionException
358
-     */
359
-    public function total()
360
-    {
361
-        return (float) $this->get('TXN_total');
362
-    }
363
-
364
-
365
-    /**
366
-     * get Total Amount Paid to Date
367
-     *
368
-     * @return float
369
-     * @throws EE_Error
370
-     * @throws InvalidArgumentException
371
-     * @throws InvalidDataTypeException
372
-     * @throws InvalidInterfaceException
373
-     * @throws ReflectionException
374
-     */
375
-    public function paid()
376
-    {
377
-        return (float) $this->get('TXN_paid');
378
-    }
379
-
380
-
381
-    /**
382
-     * @return mixed|null
383
-     * @throws EE_Error
384
-     * @throws InvalidArgumentException
385
-     * @throws InvalidDataTypeException
386
-     * @throws InvalidInterfaceException
387
-     * @throws ReflectionException
388
-     */
389
-    public function get_cart_session()
390
-    {
391
-        $session_data = (array) $this->get('TXN_session_data');
392
-        return isset($session_data['cart']) && $session_data['cart'] instanceof EE_Cart
393
-            ? $session_data['cart']
394
-            : null;
395
-    }
396
-
397
-
398
-    /**
399
-     * get Transaction session data
400
-     *
401
-     * @return array|mixed
402
-     * @throws EE_Error
403
-     * @throws InvalidArgumentException
404
-     * @throws InvalidDataTypeException
405
-     * @throws InvalidInterfaceException
406
-     * @throws ReflectionException
407
-     */
408
-    public function session_data()
409
-    {
410
-        $session_data = $this->get('TXN_session_data');
411
-        if (empty($session_data)) {
412
-            $session_data = [
413
-                'id'            => null,
414
-                'user_id'       => null,
415
-                'ip_address'    => null,
416
-                'user_agent'    => null,
417
-                'init_access'   => null,
418
-                'last_access'   => null,
419
-                'pages_visited' => [],
420
-            ];
421
-        }
422
-        return $session_data;
423
-    }
424
-
425
-
426
-    /**
427
-     * Set session data within the TXN object
428
-     *
429
-     * @param EE_Session|array|null $session_data
430
-     * @throws EE_Error
431
-     * @throws InvalidArgumentException
432
-     * @throws InvalidDataTypeException
433
-     * @throws InvalidInterfaceException
434
-     * @throws ReflectionException
435
-     */
436
-    public function set_txn_session_data($session_data)
437
-    {
438
-        if ($session_data instanceof EE_Session) {
439
-            $this->set('TXN_session_data', $session_data->get_session_data(null, true));
440
-        } else {
441
-            $this->set('TXN_session_data', $session_data);
442
-        }
443
-    }
444
-
445
-
446
-    /**
447
-     * get Transaction hash salt
448
-     *
449
-     * @return mixed
450
-     * @throws EE_Error
451
-     * @throws InvalidArgumentException
452
-     * @throws InvalidDataTypeException
453
-     * @throws InvalidInterfaceException
454
-     * @throws ReflectionException
455
-     */
456
-    public function hash_salt_()
457
-    {
458
-        return $this->get('TXN_hash_salt');
459
-    }
460
-
461
-
462
-    /**
463
-     * Returns the transaction datetime as either:
464
-     *            - unix timestamp format ($format = false, $gmt = true)
465
-     *            - formatted date string including the UTC (timezone) offset ($format = true ($gmt
466
-     *              has no affect with this option)), this also may include a timezone abbreviation if the
467
-     *              set timezone in this class differs from what the timezone is on the blog.
468
-     *            - formatted date string including the UTC (timezone) offset (default).
469
-     *
470
-     * @param boolean $format   - whether to return a unix timestamp (default) or formatted date string
471
-     * @param boolean $gmt      - whether to return a unix timestamp with UTC offset applied (default)
472
-     *                          or no UTC offset applied
473
-     * @return string | int
474
-     * @throws EE_Error
475
-     * @throws InvalidArgumentException
476
-     * @throws InvalidDataTypeException
477
-     * @throws InvalidInterfaceException
478
-     * @throws ReflectionException
479
-     */
480
-    public function datetime($format = false, $gmt = false)
481
-    {
482
-        if ($format) {
483
-            return $this->get_pretty('TXN_timestamp');
484
-        }
485
-        if ($gmt) {
486
-            return $this->get_raw('TXN_timestamp');
487
-        }
488
-        return $this->get('TXN_timestamp');
489
-    }
490
-
491
-
492
-    /**
493
-     * Gets registrations on this transaction
494
-     *
495
-     * @param array   $query_params array of query parameters
496
-     * @param boolean $get_cached   TRUE to retrieve cached registrations or FALSE to pull from the db
497
-     * @return EE_Base_Class[]|EE_Registration[]
498
-     * @throws EE_Error
499
-     * @throws InvalidArgumentException
500
-     * @throws InvalidDataTypeException
501
-     * @throws InvalidInterfaceException
502
-     * @throws ReflectionException
503
-     */
504
-    public function registrations($query_params = [], $get_cached = false)
505
-    {
506
-        $query_params = (empty($query_params) || ! is_array($query_params))
507
-            ? [
508
-                'order_by' => [
509
-                    'Event.EVT_name'     => 'ASC',
510
-                    'Attendee.ATT_lname' => 'ASC',
511
-                    'Attendee.ATT_fname' => 'ASC',
512
-                    'REG_ID'             => 'ASC',
513
-                ],
514
-            ]
515
-            : $query_params;
516
-        $query_params = $get_cached
517
-            ? []
518
-            : $query_params;
519
-        return $this->get_many_related('Registration', $query_params);
520
-    }
521
-
522
-
523
-    /**
524
-     * Gets all the attendees for this transaction (handy for use with EE_Attendee's get_registrations_for_event
525
-     * function for getting attendees and how many registrations they each have for an event)
526
-     *
527
-     * @return mixed EE_Attendee[] by default, int if $output is set to 'COUNT'
528
-     * @throws EE_Error
529
-     * @throws InvalidArgumentException
530
-     * @throws InvalidDataTypeException
531
-     * @throws InvalidInterfaceException
532
-     * @throws ReflectionException
533
-     */
534
-    public function attendees()
535
-    {
536
-        return $this->get_many_related('Attendee', [['Registration.Transaction.TXN_ID' => $this->ID()]]);
537
-    }
538
-
539
-
540
-    /**
541
-     * Gets payments for this transaction. Unlike other such functions, order by 'DESC' by default
542
-     *
543
-     * @param array $query_params @see
544
-     *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
545
-     * @return EE_Base_Class[]|EE_Payment[]
546
-     * @throws EE_Error
547
-     * @throws InvalidArgumentException
548
-     * @throws InvalidDataTypeException
549
-     * @throws InvalidInterfaceException
550
-     * @throws ReflectionException
551
-     */
552
-    public function payments($query_params = [])
553
-    {
554
-        return $this->get_many_related('Payment', $query_params);
555
-    }
556
-
557
-
558
-    /**
559
-     * gets only approved payments for this transaction
560
-     *
561
-     * @return EE_Base_Class[]|EE_Payment[]
562
-     * @throws EE_Error
563
-     * @throws InvalidArgumentException
564
-     * @throws ReflectionException
565
-     * @throws InvalidDataTypeException
566
-     * @throws InvalidInterfaceException
567
-     */
568
-    public function approved_payments()
569
-    {
570
-        EE_Registry::instance()->load_model('Payment');
571
-        return $this->get_many_related(
572
-            'Payment',
573
-            [
574
-                ['STS_ID' => EEM_Payment::status_id_approved],
575
-                'order_by' => ['PAY_timestamp' => 'DESC'],
576
-            ]
577
-        );
578
-    }
579
-
580
-
581
-    /**
582
-     * Gets all payments which have not been approved
583
-     *
584
-     * @return EE_Base_Class[]|EEI_Payment[]
585
-     * @throws EE_Error if a model is misconfigured somehow
586
-     * @throws InvalidArgumentException
587
-     * @throws InvalidDataTypeException
588
-     * @throws InvalidInterfaceException
589
-     * @throws ReflectionException
590
-     */
591
-    public function pending_payments()
592
-    {
593
-        return $this->get_many_related(
594
-            'Payment',
595
-            [
596
-                [
597
-                    'STS_ID' => EEM_Payment::status_id_pending,
598
-                ],
599
-                'order_by' => [
600
-                    'PAY_timestamp' => 'DESC',
601
-                ],
602
-            ]
603
-        );
604
-    }
605
-
606
-
607
-    /**
608
-     * echoes $this->pretty_status()
609
-     *
610
-     * @param bool $show_icons
611
-     * @throws EE_Error
612
-     * @throws InvalidArgumentException
613
-     * @throws InvalidDataTypeException
614
-     * @throws InvalidInterfaceException
615
-     * @throws ReflectionException
616
-     */
617
-    public function e_pretty_status($show_icons = false)
618
-    {
619
-        echo wp_kses($this->pretty_status($show_icons), AllowedTags::getAllowedTags());
620
-    }
621
-
622
-
623
-    /**
624
-     * returns a pretty version of the status, good for displaying to users
625
-     *
626
-     * @param bool $show_icons
627
-     * @return string
628
-     * @throws EE_Error
629
-     * @throws InvalidArgumentException
630
-     * @throws InvalidDataTypeException
631
-     * @throws InvalidInterfaceException
632
-     * @throws ReflectionException
633
-     */
634
-    public function pretty_status($show_icons = false)
635
-    {
636
-        $status = EEM_Status::instance()->localized_status(
637
-            [$this->status_ID() => esc_html__('unknown', 'event_espresso')],
638
-            false,
639
-            'sentence'
640
-        );
641
-        $icon   = '';
642
-        switch ($this->status_ID()) {
643
-            case EEM_Transaction::complete_status_code:
644
-                $icon = $show_icons
645
-                    ? '<span class="dashicons dashicons-yes ee-icon-size-24 green-text"></span>'
646
-                    : '';
647
-                break;
648
-            case EEM_Transaction::incomplete_status_code:
649
-                $icon = $show_icons
650
-                    ? '<span class="dashicons dashicons-marker ee-icon-size-16 lt-blue-text"></span>'
651
-                    : '';
652
-                break;
653
-            case EEM_Transaction::abandoned_status_code:
654
-                $icon = $show_icons
655
-                    ? '<span class="dashicons dashicons-marker ee-icon-size-16 red-text"></span>'
656
-                    : '';
657
-                break;
658
-            case EEM_Transaction::failed_status_code:
659
-                $icon = $show_icons
660
-                    ? '<span class="dashicons dashicons-no ee-icon-size-16 red-text"></span>'
661
-                    : '';
662
-                break;
663
-            case EEM_Transaction::overpaid_status_code:
664
-                $icon = $show_icons
665
-                    ? '<span class="dashicons dashicons-plus ee-icon-size-16 orange-text"></span>'
666
-                    : '';
667
-                break;
668
-        }
669
-        return $icon . $status[ $this->status_ID() ];
670
-    }
671
-
672
-
673
-    /**
674
-     * get Transaction Status
675
-     *
676
-     * @return mixed
677
-     * @throws EE_Error
678
-     * @throws InvalidArgumentException
679
-     * @throws InvalidDataTypeException
680
-     * @throws InvalidInterfaceException
681
-     * @throws ReflectionException
682
-     */
683
-    public function status_ID()
684
-    {
685
-        return $this->get('STS_ID');
686
-    }
687
-
688
-
689
-    /**
690
-     * Returns TRUE or FALSE for whether or not this transaction cost any money
691
-     *
692
-     * @return boolean
693
-     * @throws EE_Error
694
-     * @throws InvalidArgumentException
695
-     * @throws InvalidDataTypeException
696
-     * @throws InvalidInterfaceException
697
-     * @throws ReflectionException
698
-     */
699
-    public function is_free()
700
-    {
701
-        return EEH_Money::compare_floats($this->get('TXN_total'), 0, '==');
702
-    }
703
-
704
-
705
-    /**
706
-     * Returns whether this transaction is complete
707
-     * Useful in templates and other logic for deciding if we should ask for another payment...
708
-     *
709
-     * @return boolean
710
-     * @throws EE_Error
711
-     * @throws InvalidArgumentException
712
-     * @throws InvalidDataTypeException
713
-     * @throws InvalidInterfaceException
714
-     * @throws ReflectionException
715
-     */
716
-    public function is_completed()
717
-    {
718
-        return $this->status_ID() === EEM_Transaction::complete_status_code;
719
-    }
720
-
721
-
722
-    /**
723
-     * Returns whether this transaction is incomplete
724
-     * Useful in templates and other logic for deciding if we should ask for another payment...
725
-     *
726
-     * @return boolean
727
-     * @throws EE_Error
728
-     * @throws InvalidArgumentException
729
-     * @throws InvalidDataTypeException
730
-     * @throws InvalidInterfaceException
731
-     * @throws ReflectionException
732
-     */
733
-    public function is_incomplete()
734
-    {
735
-        return $this->status_ID() === EEM_Transaction::incomplete_status_code;
736
-    }
737
-
738
-
739
-    /**
740
-     * Returns whether this transaction is overpaid
741
-     * Useful in templates and other logic for deciding if monies need to be refunded
742
-     *
743
-     * @return boolean
744
-     * @throws EE_Error
745
-     * @throws InvalidArgumentException
746
-     * @throws InvalidDataTypeException
747
-     * @throws InvalidInterfaceException
748
-     * @throws ReflectionException
749
-     */
750
-    public function is_overpaid()
751
-    {
752
-        return $this->status_ID() === EEM_Transaction::overpaid_status_code;
753
-    }
754
-
755
-
756
-    /**
757
-     * Returns whether this transaction was abandoned
758
-     * meaning that the transaction/registration process was somehow interrupted and never completed
759
-     * but that contact information exists for at least one registrant
760
-     *
761
-     * @return boolean
762
-     * @throws EE_Error
763
-     * @throws InvalidArgumentException
764
-     * @throws InvalidDataTypeException
765
-     * @throws InvalidInterfaceException
766
-     * @throws ReflectionException
767
-     */
768
-    public function is_abandoned()
769
-    {
770
-        return $this->status_ID() === EEM_Transaction::abandoned_status_code;
771
-    }
772
-
773
-
774
-    /**
775
-     * Returns whether this transaction failed
776
-     * meaning that the transaction/registration process was somehow interrupted and never completed
777
-     * and that NO contact information exists for any registrants
778
-     *
779
-     * @return boolean
780
-     * @throws EE_Error
781
-     * @throws InvalidArgumentException
782
-     * @throws InvalidDataTypeException
783
-     * @throws InvalidInterfaceException
784
-     * @throws ReflectionException
785
-     */
786
-    public function failed()
787
-    {
788
-        return $this->status_ID() === EEM_Transaction::failed_status_code;
789
-    }
790
-
791
-
792
-    /**
793
-     * This returns the url for the invoice of this transaction
794
-     *
795
-     * @param string $type 'html' or 'pdf' (default is pdf)
796
-     * @return string
797
-     * @throws DomainException
798
-     * @throws EE_Error
799
-     * @throws InvalidArgumentException
800
-     * @throws InvalidDataTypeException
801
-     * @throws InvalidInterfaceException
802
-     * @throws ReflectionException
803
-     */
804
-    public function invoice_url($type = 'html')
805
-    {
806
-        $REG = $this->primary_registration();
807
-        if (! $REG instanceof EE_Registration) {
808
-            return '';
809
-        }
810
-        return $REG->invoice_url($type);
811
-    }
812
-
813
-
814
-    /**
815
-     * Gets the primary registration only
816
-     *
817
-     * @return EE_Base_Class|EE_Registration
818
-     * @throws EE_Error
819
-     * @throws InvalidArgumentException
820
-     * @throws InvalidDataTypeException
821
-     * @throws InvalidInterfaceException
822
-     * @throws ReflectionException
823
-     */
824
-    public function primary_registration()
825
-    {
826
-        if($this->_primary_registrant instanceof EE_Registration) {
827
-            return $this->_primary_registrant;
828
-        }
829
-
830
-        $registrations = (array) $this->get_many_related(
831
-            'Registration',
832
-            [['REG_count' => EEM_Registration::PRIMARY_REGISTRANT_COUNT]]
833
-        );
834
-        foreach ($registrations as $registration) {
835
-            // valid registration that is NOT cancelled or declined ?
836
-            if (
837
-                $registration instanceof EE_Registration
838
-                && ! in_array($registration->status_ID(), EEM_Registration::closed_reg_statuses(), true)
839
-            ) {
840
-                $this->_primary_registrant = $registration;
841
-                return $registration;
842
-            }
843
-        }
844
-        // nothing valid found, so just return first thing from array of results
845
-        $primary_registrant = reset($registrations);
846
-        $this->_primary_registrant = $primary_registrant instanceof EE_Registration ? $primary_registrant : null;
847
-        return $this->_primary_registrant;
848
-    }
849
-
850
-
851
-    /**
852
-     * Gets the URL for viewing the receipt
853
-     *
854
-     * @param string $type 'pdf' or 'html' (default is 'html')
855
-     * @return string
856
-     * @throws DomainException
857
-     * @throws EE_Error
858
-     * @throws InvalidArgumentException
859
-     * @throws InvalidDataTypeException
860
-     * @throws InvalidInterfaceException
861
-     * @throws ReflectionException
862
-     */
863
-    public function receipt_url($type = 'html')
864
-    {
865
-        $REG = $this->primary_registration();
866
-        if (! $REG instanceof EE_Registration) {
867
-            return '';
868
-        }
869
-        return $REG->receipt_url($type);
870
-    }
871
-
872
-
873
-    /**
874
-     * Gets the URL of the thank you page with this registration REG_url_link added as
875
-     * a query parameter
876
-     *
877
-     * @return string
878
-     * @throws EE_Error
879
-     * @throws InvalidArgumentException
880
-     * @throws InvalidDataTypeException
881
-     * @throws InvalidInterfaceException
882
-     * @throws ReflectionException
883
-     */
884
-    public function payment_overview_url()
885
-    {
886
-        $primary_registration = $this->primary_registration();
887
-        return $primary_registration instanceof EE_Registration
888
-            ? $primary_registration->payment_overview_url()
889
-            : false;
890
-    }
891
-
892
-
893
-    /**
894
-     * @return string
895
-     * @throws EE_Error
896
-     * @throws InvalidArgumentException
897
-     * @throws InvalidDataTypeException
898
-     * @throws InvalidInterfaceException
899
-     * @throws ReflectionException
900
-     */
901
-    public function gateway_response_on_transaction()
902
-    {
903
-        $payment = $this->get_first_related('Payment');
904
-        return $payment instanceof EE_Payment
905
-            ? $payment->gateway_response()
906
-            : '';
907
-    }
908
-
909
-
910
-    /**
911
-     * Get the status object of this object
912
-     *
913
-     * @return EE_Base_Class|EE_Status
914
-     * @throws EE_Error
915
-     * @throws InvalidArgumentException
916
-     * @throws InvalidDataTypeException
917
-     * @throws InvalidInterfaceException
918
-     * @throws ReflectionException
919
-     */
920
-    public function status_obj()
921
-    {
922
-        return $this->get_first_related('Status');
923
-    }
924
-
925
-
926
-    /**
927
-     * Gets all the extra meta info on this payment
928
-     *
929
-     * @param array $query_params
930
-     * @return EE_Base_Class[]|EE_Extra_Meta
931
-     * @throws EE_Error
932
-     * @throws InvalidArgumentException
933
-     * @throws InvalidDataTypeException
934
-     * @throws InvalidInterfaceException
935
-     * @throws ReflectionException
936
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
937
-     */
938
-    public function extra_meta($query_params = [])
939
-    {
940
-        return $this->get_many_related('Extra_Meta', $query_params);
941
-    }
942
-
943
-
944
-    /**
945
-     * Wrapper for _add_relation_to
946
-     *
947
-     * @param EE_Registration $registration
948
-     * @return EE_Base_Class the relation was added to
949
-     * @throws EE_Error
950
-     * @throws InvalidArgumentException
951
-     * @throws InvalidDataTypeException
952
-     * @throws InvalidInterfaceException
953
-     * @throws ReflectionException
954
-     */
955
-    public function add_registration(EE_Registration $registration)
956
-    {
957
-        return $this->_add_relation_to($registration, 'Registration');
958
-    }
959
-
960
-
961
-    /**
962
-     * Removes the given registration from being related (even before saving this transaction).
963
-     * If an ID/index is provided and this transaction isn't saved yet, removes it from list of cached relations
964
-     *
965
-     * @param int $registration_or_id
966
-     * @return EE_Base_Class that was removed from being related
967
-     * @throws EE_Error
968
-     * @throws InvalidArgumentException
969
-     * @throws InvalidDataTypeException
970
-     * @throws InvalidInterfaceException
971
-     * @throws ReflectionException
972
-     */
973
-    public function remove_registration_with_id($registration_or_id)
974
-    {
975
-        return $this->_remove_relation_to($registration_or_id, 'Registration');
976
-    }
977
-
978
-
979
-    /**
980
-     * Gets all the line items which are for ACTUAL items
981
-     *
982
-     * @return EE_Line_Item[]
983
-     * @throws EE_Error
984
-     * @throws InvalidArgumentException
985
-     * @throws InvalidDataTypeException
986
-     * @throws InvalidInterfaceException
987
-     * @throws ReflectionException
988
-     */
989
-    public function items_purchased()
990
-    {
991
-        return $this->line_items([['LIN_type' => EEM_Line_Item::type_line_item]]);
992
-    }
993
-
994
-
995
-    /**
996
-     * Wrapper for _add_relation_to
997
-     *
998
-     * @param EE_Line_Item $line_item
999
-     * @return EE_Base_Class the relation was added to
1000
-     * @throws EE_Error
1001
-     * @throws InvalidArgumentException
1002
-     * @throws InvalidDataTypeException
1003
-     * @throws InvalidInterfaceException
1004
-     * @throws ReflectionException
1005
-     */
1006
-    public function add_line_item(EE_Line_Item $line_item)
1007
-    {
1008
-        return $this->_add_relation_to($line_item, 'Line_Item');
1009
-    }
1010
-
1011
-
1012
-    /**
1013
-     * Gets ALL the line items related to this transaction (unstructured)
1014
-     *
1015
-     * @param array $query_params
1016
-     * @return EE_Base_Class[]|EE_Line_Item[]
1017
-     * @throws EE_Error
1018
-     * @throws InvalidArgumentException
1019
-     * @throws InvalidDataTypeException
1020
-     * @throws InvalidInterfaceException
1021
-     * @throws ReflectionException
1022
-     */
1023
-    public function line_items($query_params = [])
1024
-    {
1025
-        return $this->get_many_related('Line_Item', $query_params);
1026
-    }
1027
-
1028
-
1029
-    /**
1030
-     * Gets all the line items which are taxes on the total
1031
-     *
1032
-     * @return EE_Line_Item[]
1033
-     * @throws EE_Error
1034
-     * @throws InvalidArgumentException
1035
-     * @throws InvalidDataTypeException
1036
-     * @throws InvalidInterfaceException
1037
-     * @throws ReflectionException
1038
-     */
1039
-    public function tax_items()
1040
-    {
1041
-        return $this->line_items([['LIN_type' => EEM_Line_Item::type_tax]]);
1042
-    }
1043
-
1044
-
1045
-    /**
1046
-     * Gets the total line item (which is a parent of all other related line items,
1047
-     * meaning it takes them all into account on its total)
1048
-     *
1049
-     * @param bool $create_if_not_found
1050
-     * @return EE_Line_Item|null
1051
-     * @throws EE_Error
1052
-     * @throws InvalidArgumentException
1053
-     * @throws InvalidDataTypeException
1054
-     * @throws InvalidInterfaceException
1055
-     * @throws ReflectionException
1056
-     */
1057
-    public function total_line_item(bool $create_if_not_found = true): ?EE_Line_Item
1058
-    {
1059
-        $item = $this->get_first_related('Line_Item', [['LIN_type' => EEM_Line_Item::type_total]]);
1060
-        if ($item instanceof EE_Line_Item) {
1061
-            return $item;
1062
-        }
1063
-        return $create_if_not_found
1064
-            ? EEH_Line_Item::create_total_line_item($this)
1065
-            : null;
1066
-    }
1067
-
1068
-
1069
-    /**
1070
-     * Returns the total amount of tax on this transaction
1071
-     * (assumes there's only one tax subtotal line item)
1072
-     *
1073
-     * @return float
1074
-     * @throws EE_Error
1075
-     * @throws InvalidArgumentException
1076
-     * @throws InvalidDataTypeException
1077
-     * @throws InvalidInterfaceException
1078
-     * @throws ReflectionException
1079
-     */
1080
-    public function tax_total()
1081
-    {
1082
-        $tax_line_item = $this->tax_total_line_item();
1083
-        if ($tax_line_item) {
1084
-            return (float) $tax_line_item->total();
1085
-        }
1086
-        return (float) 0;
1087
-    }
1088
-
1089
-
1090
-    /**
1091
-     * Gets the tax subtotal line item (assumes there's only one)
1092
-     *
1093
-     * @return EE_Line_Item
1094
-     * @throws EE_Error
1095
-     * @throws InvalidArgumentException
1096
-     * @throws InvalidDataTypeException
1097
-     * @throws InvalidInterfaceException
1098
-     * @throws ReflectionException
1099
-     */
1100
-    public function tax_total_line_item()
1101
-    {
1102
-        return EEH_Line_Item::get_taxes_subtotal($this->total_line_item());
1103
-    }
1104
-
1105
-
1106
-    /**
1107
-     * Gets the array of billing info for the gateway and for this transaction's primary registration's attendee.
1108
-     *
1109
-     * @return EE_Form_Section_Proper
1110
-     * @throws EE_Error
1111
-     * @throws InvalidArgumentException
1112
-     * @throws InvalidDataTypeException
1113
-     * @throws InvalidInterfaceException
1114
-     * @throws ReflectionException
1115
-     */
1116
-    public function billing_info()
1117
-    {
1118
-        $payment_method = $this->payment_method();
1119
-        if (! $payment_method) {
1120
-            EE_Error::add_error(
1121
-                esc_html__(
1122
-                    'Could not find billing info for transaction because no gateway has been used for it yet',
1123
-                    'event_espresso'
1124
-                ),
1125
-                __FILE__,
1126
-                __FUNCTION__,
1127
-                __LINE__
1128
-            );
1129
-            return null;
1130
-        }
1131
-        $primary_reg = $this->primary_registration();
1132
-        if (! $primary_reg) {
1133
-            EE_Error::add_error(
1134
-                esc_html__(
1135
-                    'Cannot get billing info for gateway %s on transaction because no primary registration exists',
1136
-                    'event_espresso'
1137
-                ),
1138
-                __FILE__,
1139
-                __FUNCTION__,
1140
-                __LINE__
1141
-            );
1142
-            return null;
1143
-        }
1144
-        $attendee = $primary_reg->attendee();
1145
-        if (! $attendee) {
1146
-            EE_Error::add_error(
1147
-                esc_html__(
1148
-                    'Cannot get billing info for gateway %s on transaction because the primary registration has no attendee exists',
1149
-                    'event_espresso'
1150
-                ),
1151
-                __FILE__,
1152
-                __FUNCTION__,
1153
-                __LINE__
1154
-            );
1155
-            return null;
1156
-        }
1157
-        return $attendee->billing_info_for_payment_method($payment_method);
1158
-    }
1159
-
1160
-
1161
-    /**
1162
-     * Gets PMD_ID
1163
-     *
1164
-     * @return int
1165
-     * @throws EE_Error
1166
-     * @throws InvalidArgumentException
1167
-     * @throws InvalidDataTypeException
1168
-     * @throws InvalidInterfaceException
1169
-     * @throws ReflectionException
1170
-     */
1171
-    public function payment_method_ID()
1172
-    {
1173
-        return $this->get('PMD_ID');
1174
-    }
1175
-
1176
-
1177
-    /**
1178
-     * Sets PMD_ID
1179
-     *
1180
-     * @param int $PMD_ID
1181
-     * @throws EE_Error
1182
-     * @throws InvalidArgumentException
1183
-     * @throws InvalidDataTypeException
1184
-     * @throws InvalidInterfaceException
1185
-     * @throws ReflectionException
1186
-     */
1187
-    public function set_payment_method_ID($PMD_ID)
1188
-    {
1189
-        $this->set('PMD_ID', $PMD_ID);
1190
-    }
1191
-
1192
-
1193
-    /**
1194
-     * Gets the last-used payment method on this transaction
1195
-     * (we COULD just use the last-made payment, but some payment methods, namely
1196
-     * offline ones, dont' create payments)
1197
-     *
1198
-     * @return EE_Payment_Method
1199
-     * @throws EE_Error
1200
-     * @throws InvalidArgumentException
1201
-     * @throws InvalidDataTypeException
1202
-     * @throws InvalidInterfaceException
1203
-     * @throws ReflectionException
1204
-     */
1205
-    public function payment_method()
1206
-    {
1207
-        $PMD_ID = $this->payment_method_ID();
1208
-        if($PMD_ID) {
1209
-            $pm = EEM_Payment_Method::instance()->get_one_by_ID($this->payment_method_ID());
1210
-            if ($pm instanceof EE_Payment_Method) {
1211
-                return $pm;
1212
-            }
1213
-        }
1214
-        $last_payment = $this->last_payment();
1215
-        if ($last_payment instanceof EE_Payment && $last_payment->payment_method()) {
1216
-            return $last_payment->payment_method();
1217
-        }
1218
-        return null;
1219
-    }
1220
-
1221
-
1222
-    /**
1223
-     * Gets the last payment made
1224
-     *
1225
-     * @return EE_Base_Class|EE_Payment|null
1226
-     * @throws EE_Error
1227
-     * @throws InvalidArgumentException
1228
-     * @throws InvalidDataTypeException
1229
-     * @throws InvalidInterfaceException
1230
-     * @throws ReflectionException
1231
-     */
1232
-    public function last_payment(): ?EE_Payment
1233
-    {
1234
-        return $this->get_first_related('Payment', ['order_by' => ['PAY_ID' => 'desc']]);
1235
-    }
1236
-
1237
-
1238
-    /**
1239
-     * Gets all the line items which are unrelated to tickets on this transaction
1240
-     *
1241
-     * @return EE_Line_Item[]
1242
-     * @throws EE_Error
1243
-     * @throws InvalidArgumentException
1244
-     * @throws InvalidDataTypeException
1245
-     * @throws InvalidInterfaceException
1246
-     * @throws ReflectionException
1247
-     */
1248
-    public function non_ticket_line_items()
1249
-    {
1250
-        return EEM_Line_Item::instance()->get_all_non_ticket_line_items_for_transaction($this->ID());
1251
-    }
1252
-
1253
-
1254
-    /**
1255
-     * possibly toggles TXN status
1256
-     *
1257
-     * @param boolean $update whether to save the TXN
1258
-     * @return bool whether the TXN was saved
1259
-     * @throws EE_Error
1260
-     * @throws InvalidArgumentException
1261
-     * @throws InvalidDataTypeException
1262
-     * @throws InvalidInterfaceException
1263
-     * @throws ReflectionException
1264
-     * @throws RuntimeException
1265
-     */
1266
-    public function update_status_based_on_total_paid($update = true)
1267
-    {
1268
-        // set transaction status based on comparison of TXN_paid vs TXN_total
1269
-        if (EEH_Money::compare_floats($this->paid(), $this->total(), '>')) {
1270
-            $new_txn_status = EEM_Transaction::overpaid_status_code;
1271
-        } elseif (EEH_Money::compare_floats($this->paid(), $this->total())) {
1272
-            $new_txn_status = EEM_Transaction::complete_status_code;
1273
-        } elseif (EEH_Money::compare_floats($this->paid(), $this->total(), '<')) {
1274
-            $new_txn_status = EEM_Transaction::incomplete_status_code;
1275
-        } else {
1276
-            throw new RuntimeException(
1277
-                esc_html__('The total paid calculation for this transaction is inaccurate.', 'event_espresso')
1278
-            );
1279
-        }
1280
-        if ($new_txn_status !== $this->status_ID()) {
1281
-            $this->set_status($new_txn_status);
1282
-            if ($update) {
1283
-                return (bool) $this->save();
1284
-            }
1285
-        }
1286
-        return false;
1287
-    }
1288
-
1289
-
1290
-    /**
1291
-     * Updates the transaction's status and total_paid based on all the payments
1292
-     * that apply to it
1293
-     *
1294
-     * @return array|bool
1295
-     * @throws EE_Error
1296
-     * @throws InvalidArgumentException
1297
-     * @throws ReflectionException
1298
-     * @throws InvalidDataTypeException
1299
-     * @throws InvalidInterfaceException
1300
-     * @deprecated
1301
-     */
1302
-    public function update_based_on_payments()
1303
-    {
1304
-        EE_Error::doing_it_wrong(
1305
-            __CLASS__ . '::' . __FUNCTION__,
1306
-            sprintf(
1307
-                esc_html__('This method is deprecated. Please use "%s" instead', 'event_espresso'),
1308
-                'EE_Transaction_Processor::update_transaction_and_registrations_after_checkout_or_payment()'
1309
-            ),
1310
-            '4.6.0'
1311
-        );
1312
-        /** @type EE_Transaction_Processor $transaction_processor */
1313
-        $transaction_processor = EE_Registry::instance()->load_class('Transaction_Processor');
1314
-        return $transaction_processor->update_transaction_and_registrations_after_checkout_or_payment($this);
1315
-    }
1316
-
1317
-
1318
-    /**
1319
-     * @return string
1320
-     */
1321
-    public function old_txn_status()
1322
-    {
1323
-        return $this->_old_txn_status;
1324
-    }
1325
-
1326
-
1327
-    /**
1328
-     * @param string $old_txn_status
1329
-     */
1330
-    public function set_old_txn_status($old_txn_status)
1331
-    {
1332
-        // only set the first time
1333
-        if ($this->_old_txn_status === null) {
1334
-            $this->_old_txn_status = $old_txn_status;
1335
-        }
1336
-    }
1337
-
1338
-
1339
-    /**
1340
-     * reg_status_updated
1341
-     *
1342
-     * @return bool
1343
-     * @throws EE_Error
1344
-     * @throws InvalidArgumentException
1345
-     * @throws InvalidDataTypeException
1346
-     * @throws InvalidInterfaceException
1347
-     * @throws ReflectionException
1348
-     */
1349
-    public function txn_status_updated()
1350
-    {
1351
-        return $this->status_ID() !== $this->_old_txn_status && $this->_old_txn_status !== null;
1352
-    }
1353
-
1354
-
1355
-    /**
1356
-     * _reg_steps_completed
1357
-     * if $check_all is TRUE, then returns TRUE if ALL reg steps have been marked as completed,
1358
-     * if a $reg_step_slug is provided, then this step will be skipped when testing for completion
1359
-     * if $check_all is FALSE and a $reg_step_slug is provided, then ONLY that reg step will be tested for completion
1360
-     *
1361
-     * @param string $reg_step_slug
1362
-     * @param bool   $check_all
1363
-     * @return bool|int
1364
-     * @throws EE_Error
1365
-     * @throws InvalidArgumentException
1366
-     * @throws InvalidDataTypeException
1367
-     * @throws InvalidInterfaceException
1368
-     * @throws ReflectionException
1369
-     */
1370
-    private function _reg_steps_completed($reg_step_slug = '', $check_all = true)
1371
-    {
1372
-        $reg_steps = $this->reg_steps();
1373
-        if (! is_array($reg_steps) || empty($reg_steps)) {
1374
-            return false;
1375
-        }
1376
-        // loop thru reg steps array)
1377
-        foreach ($reg_steps as $slug => $reg_step_completed) {
1378
-            // if NOT checking ALL steps (only checking one step)
1379
-            if (! $check_all) {
1380
-                // and this is the one
1381
-                if ($slug === $reg_step_slug) {
1382
-                    return $reg_step_completed;
1383
-                }
1384
-                // skip to next reg step in loop
1385
-                continue;
1386
-            }
1387
-            // $check_all must be true, else we would never have gotten to this point
1388
-            if ($slug === $reg_step_slug) {
1389
-                // if we reach this point, then we are testing either:
1390
-                // all_reg_steps_completed_except() or
1391
-                // all_reg_steps_completed_except_final_step(),
1392
-                // and since this is the reg step EXCEPTION being tested
1393
-                // we want to return true (yes true) if this reg step is NOT completed
1394
-                // ie: "is everything completed except the final step?"
1395
-                // "that is correct... the final step is not completed, but all others are."
1396
-                return $reg_step_completed !== true;
1397
-            }
1398
-            if ($reg_step_completed !== true) {
1399
-                // if any reg step is NOT completed, then ALL steps are not completed
1400
-                return false;
1401
-            }
1402
-        }
1403
-        return true;
1404
-    }
1405
-
1406
-
1407
-    /**
1408
-     * all_reg_steps_completed
1409
-     * returns:
1410
-     *    true if ALL reg steps have been marked as completed
1411
-     *        or false if any step is not completed
1412
-     *
1413
-     * @return bool
1414
-     * @throws EE_Error
1415
-     * @throws InvalidArgumentException
1416
-     * @throws InvalidDataTypeException
1417
-     * @throws InvalidInterfaceException
1418
-     * @throws ReflectionException
1419
-     */
1420
-    public function all_reg_steps_completed()
1421
-    {
1422
-        return $this->_reg_steps_completed();
1423
-    }
1424
-
1425
-
1426
-    /**
1427
-     * all_reg_steps_completed_except
1428
-     * returns:
1429
-     *        true if ALL reg steps, except a particular step that you wish to skip over, have been marked as completed
1430
-     *        or false if any other step is not completed
1431
-     *        or false if ALL steps are completed including the exception you are testing !!!
1432
-     *
1433
-     * @param string $exception
1434
-     * @return bool
1435
-     * @throws EE_Error
1436
-     * @throws InvalidArgumentException
1437
-     * @throws InvalidDataTypeException
1438
-     * @throws InvalidInterfaceException
1439
-     * @throws ReflectionException
1440
-     */
1441
-    public function all_reg_steps_completed_except($exception = '')
1442
-    {
1443
-        return $this->_reg_steps_completed($exception);
1444
-    }
1445
-
1446
-
1447
-    /**
1448
-     * all_reg_steps_completed_except
1449
-     * returns:
1450
-     *        true if ALL reg steps, except the final step, have been marked as completed
1451
-     *        or false if any step is not completed
1452
-     *    or false if ALL steps are completed including the final step !!!
1453
-     *
1454
-     * @return bool
1455
-     * @throws EE_Error
1456
-     * @throws InvalidArgumentException
1457
-     * @throws InvalidDataTypeException
1458
-     * @throws InvalidInterfaceException
1459
-     * @throws ReflectionException
1460
-     */
1461
-    public function all_reg_steps_completed_except_final_step()
1462
-    {
1463
-        return $this->_reg_steps_completed('finalize_registration');
1464
-    }
1465
-
1466
-
1467
-    /**
1468
-     * reg_step_completed
1469
-     * returns:
1470
-     *    true if a specific reg step has been marked as completed
1471
-     *    a Unix timestamp if it has been initialized but not yet completed,
1472
-     *    or false if it has not yet been initialized
1473
-     *
1474
-     * @param string $reg_step_slug
1475
-     * @return bool|int
1476
-     * @throws EE_Error
1477
-     * @throws InvalidArgumentException
1478
-     * @throws InvalidDataTypeException
1479
-     * @throws InvalidInterfaceException
1480
-     * @throws ReflectionException
1481
-     */
1482
-    public function reg_step_completed($reg_step_slug)
1483
-    {
1484
-        return $this->_reg_steps_completed($reg_step_slug, false);
1485
-    }
1486
-
1487
-
1488
-    /**
1489
-     * completed_final_reg_step
1490
-     * returns:
1491
-     *    true if the finalize_registration reg step has been marked as completed
1492
-     *    a Unix timestamp if it has been initialized but not yet completed,
1493
-     *    or false if it has not yet been initialized
1494
-     *
1495
-     * @return bool|int
1496
-     * @throws EE_Error
1497
-     * @throws InvalidArgumentException
1498
-     * @throws InvalidDataTypeException
1499
-     * @throws InvalidInterfaceException
1500
-     * @throws ReflectionException
1501
-     */
1502
-    public function final_reg_step_completed()
1503
-    {
1504
-        return $this->_reg_steps_completed('finalize_registration', false);
1505
-    }
1506
-
1507
-
1508
-    /**
1509
-     * set_reg_step_initiated
1510
-     * given a valid TXN_reg_step, this sets it's value to a unix timestamp
1511
-     *
1512
-     * @param string $reg_step_slug
1513
-     * @return boolean
1514
-     * @throws EE_Error
1515
-     * @throws InvalidArgumentException
1516
-     * @throws InvalidDataTypeException
1517
-     * @throws InvalidInterfaceException
1518
-     * @throws ReflectionException
1519
-     */
1520
-    public function set_reg_step_initiated($reg_step_slug)
1521
-    {
1522
-        return $this->_set_reg_step_completed_status($reg_step_slug, time());
1523
-    }
1524
-
1525
-
1526
-    /**
1527
-     * set_reg_step_completed
1528
-     * given a valid TXN_reg_step, this sets the step as completed
1529
-     *
1530
-     * @param string $reg_step_slug
1531
-     * @return boolean
1532
-     * @throws EE_Error
1533
-     * @throws InvalidArgumentException
1534
-     * @throws InvalidDataTypeException
1535
-     * @throws InvalidInterfaceException
1536
-     * @throws ReflectionException
1537
-     */
1538
-    public function set_reg_step_completed($reg_step_slug)
1539
-    {
1540
-        return $this->_set_reg_step_completed_status($reg_step_slug, true);
1541
-    }
1542
-
1543
-
1544
-    /**
1545
-     * set_reg_step_completed
1546
-     * given a valid TXN_reg_step slug, this sets the step as NOT completed
1547
-     *
1548
-     * @param string $reg_step_slug
1549
-     * @return boolean
1550
-     * @throws EE_Error
1551
-     * @throws InvalidArgumentException
1552
-     * @throws InvalidDataTypeException
1553
-     * @throws InvalidInterfaceException
1554
-     * @throws ReflectionException
1555
-     */
1556
-    public function set_reg_step_not_completed($reg_step_slug)
1557
-    {
1558
-        return $this->_set_reg_step_completed_status($reg_step_slug, false);
1559
-    }
1560
-
1561
-
1562
-    /**
1563
-     * set_reg_step_completed
1564
-     * given a valid reg step slug, this sets the TXN_reg_step completed status which is either:
1565
-     *
1566
-     * @param string      $reg_step_slug
1567
-     * @param boolean|int $status
1568
-     * @return boolean
1569
-     * @throws EE_Error
1570
-     * @throws InvalidArgumentException
1571
-     * @throws InvalidDataTypeException
1572
-     * @throws InvalidInterfaceException
1573
-     * @throws ReflectionException
1574
-     */
1575
-    private function _set_reg_step_completed_status($reg_step_slug, $status)
1576
-    {
1577
-        // validate status
1578
-        $status = is_bool($status) || is_int($status)
1579
-            ? $status
1580
-            : false;
1581
-        // get reg steps array
1582
-        $txn_reg_steps = $this->reg_steps();
1583
-        // if reg step does NOT exist
1584
-        if (! isset($txn_reg_steps[ $reg_step_slug ])) {
1585
-            return false;
1586
-        }
1587
-        // if  we're trying to complete a step that is already completed
1588
-        if ($txn_reg_steps[ $reg_step_slug ] === true) {
1589
-            return true;
1590
-        }
1591
-        // if  we're trying to complete a step that hasn't even started
1592
-        if ($status === true && $txn_reg_steps[ $reg_step_slug ] === false) {
1593
-            return false;
1594
-        }
1595
-        // if current status value matches the incoming value (no change)
1596
-        // type casting as int means values should collapse to either 0, 1, or a timestamp like 1234567890
1597
-        if ((int) $txn_reg_steps[ $reg_step_slug ] === (int) $status) {
1598
-            // this will happen in cases where multiple AJAX requests occur during the same step
1599
-            return true;
1600
-        }
1601
-        // if we're trying to set a start time, but it has already been set...
1602
-        if (is_numeric($status) && is_numeric($txn_reg_steps[ $reg_step_slug ])) {
1603
-            // skip the update below, but don't return FALSE so that errors won't be displayed
1604
-            return true;
1605
-        }
1606
-        // update completed status
1607
-        $txn_reg_steps[ $reg_step_slug ] = $status;
1608
-        $this->set_reg_steps($txn_reg_steps);
1609
-        $this->save();
1610
-        return true;
1611
-    }
1612
-
1613
-
1614
-    /**
1615
-     * remove_reg_step
1616
-     * given a valid TXN_reg_step slug, this will remove (unset)
1617
-     * the reg step from the TXN reg step array
1618
-     *
1619
-     * @param string $reg_step_slug
1620
-     * @return void
1621
-     * @throws EE_Error
1622
-     * @throws InvalidArgumentException
1623
-     * @throws InvalidDataTypeException
1624
-     * @throws InvalidInterfaceException
1625
-     * @throws ReflectionException
1626
-     */
1627
-    public function remove_reg_step($reg_step_slug)
1628
-    {
1629
-        // get reg steps array
1630
-        $txn_reg_steps = $this->reg_steps();
1631
-        unset($txn_reg_steps[ $reg_step_slug ]);
1632
-        $this->set_reg_steps($txn_reg_steps);
1633
-    }
1634
-
1635
-
1636
-    /**
1637
-     * toggle_failed_transaction_status
1638
-     * upgrades a TXNs status from failed to abandoned,
1639
-     * meaning that contact information has been captured for at least one registrant
1640
-     *
1641
-     * @param bool $save
1642
-     * @return bool
1643
-     * @throws EE_Error
1644
-     * @throws InvalidArgumentException
1645
-     * @throws InvalidDataTypeException
1646
-     * @throws InvalidInterfaceException
1647
-     * @throws ReflectionException
1648
-     */
1649
-    public function toggle_failed_transaction_status($save = true)
1650
-    {
1651
-        // if TXN status is still set as "failed"...
1652
-        if ($this->status_ID() === EEM_Transaction::failed_status_code) {
1653
-            $this->set_status(EEM_Transaction::abandoned_status_code);
1654
-            if ($save) {
1655
-                $this->save();
1656
-            }
1657
-            return true;
1658
-        }
1659
-        return false;
1660
-    }
1661
-
1662
-
1663
-    /**
1664
-     * toggle_abandoned_transaction_status
1665
-     * upgrades a TXNs status from failed or abandoned to incomplete
1666
-     *
1667
-     * @return bool
1668
-     * @throws EE_Error
1669
-     * @throws InvalidArgumentException
1670
-     * @throws InvalidDataTypeException
1671
-     * @throws InvalidInterfaceException
1672
-     * @throws ReflectionException
1673
-     */
1674
-    public function toggle_abandoned_transaction_status()
1675
-    {
1676
-        // if TXN status has not been updated already due to a payment, and is still set as "failed" or "abandoned"...
1677
-        $txn_status = $this->status_ID();
1678
-        if (
1679
-            $txn_status === EEM_Transaction::failed_status_code
1680
-            || $txn_status === EEM_Transaction::abandoned_status_code
1681
-        ) {
1682
-            // if a contact record for the primary registrant has been created
1683
-            if (
1684
-                $this->primary_registration() instanceof EE_Registration
1685
-                && $this->primary_registration()->attendee() instanceof EE_Attendee
1686
-            ) {
1687
-                $this->set_status(EEM_Transaction::incomplete_status_code);
1688
-            } else {
1689
-                // no contact record? yer abandoned!
1690
-                $this->set_status(EEM_Transaction::abandoned_status_code);
1691
-            }
1692
-            return true;
1693
-        }
1694
-        return false;
1695
-    }
1696
-
1697
-
1698
-    /**
1699
-     * checks if an Abandoned TXN has any related payments, and if so,
1700
-     * updates the TXN status based on the amount paid
1701
-     *
1702
-     * @throws EE_Error
1703
-     * @throws InvalidArgumentException
1704
-     * @throws InvalidDataTypeException
1705
-     * @throws InvalidInterfaceException
1706
-     * @throws ReflectionException
1707
-     * @throws RuntimeException
1708
-     * @throws ReflectionException
1709
-     */
1710
-    public function verify_abandoned_transaction_status()
1711
-    {
1712
-        if ($this->status_ID() !== EEM_Transaction::abandoned_status_code) {
1713
-            return;
1714
-        }
1715
-        $payments = $this->get_many_related('Payment');
1716
-        if (! empty($payments)) {
1717
-            foreach ($payments as $payment) {
1718
-                if ($payment instanceof EE_Payment) {
1719
-                    // kk this TXN should NOT be abandoned
1720
-                    $this->update_status_based_on_total_paid();
1721
-                    if (! (defined('DOING_AJAX') && DOING_AJAX) && is_admin()) {
1722
-                        EE_Error::add_attention(
1723
-                            sprintf(
1724
-                                esc_html__(
1725
-                                    'The status for Transaction #%1$d has been updated from "Abandoned" to "%2$s", because at least one payment has been made towards it. If the payment appears in the "Payment Details" table below, you may need to edit its status and/or other details as well.',
1726
-                                    'event_espresso'
1727
-                                ),
1728
-                                $this->ID(),
1729
-                                $this->pretty_status()
1730
-                            )
1731
-                        );
1732
-                    }
1733
-                    // get final reg step status
1734
-                    $finalized = $this->final_reg_step_completed();
1735
-                    // if the 'finalize_registration' step has been initiated (has a timestamp)
1736
-                    // but has not yet been fully completed (TRUE)
1737
-                    if (is_int($finalized) && $finalized !== false && $finalized !== true) {
1738
-                        $this->set_reg_step_completed('finalize_registration');
1739
-                        $this->save();
1740
-                    }
1741
-                }
1742
-            }
1743
-        }
1744
-    }
1745
-
1746
-
1747
-    /**
1748
-     * @throws EE_Error
1749
-     * @throws InvalidArgumentException
1750
-     * @throws InvalidDataTypeException
1751
-     * @throws InvalidInterfaceException
1752
-     * @throws ReflectionException
1753
-     * @throws RuntimeException
1754
-     * @since 4.10.4.p
1755
-     */
1756
-    public function recalculateLineItems()
1757
-    {
1758
-        $total_line_item = $this->total_line_item(false);
1759
-        if ($total_line_item instanceof EE_Line_Item) {
1760
-            EEH_Line_Item::resetIsTaxableForTickets($total_line_item);
1761
-            return EEH_Line_Item::apply_taxes($total_line_item, true);
1762
-        }
1763
-        return false;
1764
-    }
16
+	/**
17
+	 * The length of time in seconds that a lock is applied before being considered expired.
18
+	 * It is not long because a transaction should only be locked for the duration of the request that locked it
19
+	 */
20
+	const LOCK_EXPIRATION = 2;
21
+
22
+	/**
23
+	 * txn status upon initial construction.
24
+	 *
25
+	 * @var string
26
+	 */
27
+	protected $_old_txn_status;
28
+
29
+	private ?EE_Registration $_primary_registrant = null;
30
+
31
+	/**
32
+	 * @param array  $props_n_values          incoming values
33
+	 * @param string $timezone                incoming timezone
34
+	 *                                        (if not set the timezone set for the website will be used.)
35
+	 * @param array  $date_formats            incoming date_formats in an array where the first value is the
36
+	 *                                        date_format and the second value is the time format
37
+	 * @return EE_Transaction
38
+	 * @throws EE_Error
39
+	 * @throws InvalidArgumentException
40
+	 * @throws InvalidDataTypeException
41
+	 * @throws InvalidInterfaceException
42
+	 * @throws ReflectionException
43
+	 */
44
+	public static function new_instance($props_n_values = [], $timezone = '', $date_formats = [])
45
+	{
46
+		$has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
47
+		$txn        = $has_object
48
+			? $has_object
49
+			: new self($props_n_values, false, $timezone, $date_formats);
50
+		if (! $has_object) {
51
+			$txn->set_old_txn_status($txn->status_ID());
52
+		}
53
+		return $txn;
54
+	}
55
+
56
+
57
+	/**
58
+	 * @param array  $props_n_values  incoming values from the database
59
+	 * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
60
+	 *                                the website will be used.
61
+	 * @return EE_Transaction
62
+	 * @throws EE_Error
63
+	 * @throws InvalidArgumentException
64
+	 * @throws InvalidDataTypeException
65
+	 * @throws InvalidInterfaceException
66
+	 * @throws ReflectionException
67
+	 */
68
+	public static function new_instance_from_db($props_n_values = [], $timezone = '')
69
+	{
70
+		$txn = new self($props_n_values, true, $timezone);
71
+		$txn->set_old_txn_status($txn->status_ID());
72
+		return $txn;
73
+	}
74
+
75
+
76
+	/**
77
+	 * Sets a meta field indicating that this TXN is locked and should not be updated in the db.
78
+	 * If a lock has already been set, then we will attempt to remove it in case it has expired.
79
+	 * If that also fails, then an exception is thrown.
80
+	 *
81
+	 * @throws EE_Error
82
+	 * @throws InvalidArgumentException
83
+	 * @throws InvalidDataTypeException
84
+	 * @throws InvalidInterfaceException
85
+	 * @throws ReflectionException
86
+	 */
87
+	public function lock()
88
+	{
89
+		// attempt to set lock, but if that fails...
90
+		if (! $this->add_extra_meta('lock', time(), true)) {
91
+			// then attempt to remove the lock in case it is expired
92
+			if ($this->_remove_expired_lock()) {
93
+				// if removal was successful, then try setting lock again
94
+				$this->lock();
95
+			} else {
96
+				// but if the lock can not be removed, then throw an exception
97
+				throw new EE_Error(
98
+					sprintf(
99
+						esc_html__(
100
+							'Could not lock Transaction %1$d because it is already locked, meaning another part of the system is currently editing it. It should already be unlocked by the time you read this, so please refresh the page and try again.',
101
+							'event_espresso'
102
+						),
103
+						$this->ID()
104
+					)
105
+				);
106
+			}
107
+		}
108
+	}
109
+
110
+
111
+	/**
112
+	 * removes transaction lock applied in EE_Transaction::lock()
113
+	 *
114
+	 * @return int
115
+	 * @throws EE_Error
116
+	 * @throws InvalidArgumentException
117
+	 * @throws InvalidDataTypeException
118
+	 * @throws InvalidInterfaceException
119
+	 * @throws ReflectionException
120
+	 */
121
+	public function unlock()
122
+	{
123
+		return $this->delete_extra_meta('lock');
124
+	}
125
+
126
+
127
+	/**
128
+	 * Decides whether or not now is the right time to update the transaction.
129
+	 * This is useful because we don't always know if it is safe to update the transaction
130
+	 * and its related data. why?
131
+	 * because it's possible that the transaction is being used in another
132
+	 * request and could overwrite anything we save.
133
+	 * So we want to only update the txn once we know that won't happen.
134
+	 * We also check that the lock isn't expired, and remove it if it is
135
+	 *
136
+	 * @return boolean
137
+	 * @throws EE_Error
138
+	 * @throws InvalidArgumentException
139
+	 * @throws InvalidDataTypeException
140
+	 * @throws InvalidInterfaceException
141
+	 * @throws ReflectionException
142
+	 */
143
+	public function is_locked()
144
+	{
145
+		// if TXN is not locked, then return false immediately
146
+		if (! $this->_get_lock()) {
147
+			return false;
148
+		}
149
+		// if not, then let's try and remove the lock in case it's expired...
150
+		// _remove_expired_lock() returns 0 when lock is valid (ie: removed = false)
151
+		// and a positive number if the lock was removed (ie: number of locks deleted),
152
+		// so we need to return the opposite
153
+		return ! $this->_remove_expired_lock();
154
+	}
155
+
156
+
157
+	/**
158
+	 * Gets the meta field indicating that this TXN is locked
159
+	 *
160
+	 * @return int
161
+	 * @throws EE_Error
162
+	 * @throws InvalidArgumentException
163
+	 * @throws InvalidDataTypeException
164
+	 * @throws InvalidInterfaceException
165
+	 * @throws ReflectionException
166
+	 */
167
+	protected function _get_lock()
168
+	{
169
+		return (int) $this->get_extra_meta('lock', true, 0);
170
+	}
171
+
172
+
173
+	/**
174
+	 * If the lock on this transaction is expired, then we want to remove it so that the transaction can be updated
175
+	 *
176
+	 * @return int
177
+	 * @throws EE_Error
178
+	 * @throws InvalidArgumentException
179
+	 * @throws InvalidDataTypeException
180
+	 * @throws InvalidInterfaceException
181
+	 * @throws ReflectionException
182
+	 */
183
+	protected function _remove_expired_lock()
184
+	{
185
+		$locked = $this->_get_lock();
186
+		if ($locked && time() - EE_Transaction::LOCK_EXPIRATION > $locked) {
187
+			return $this->unlock();
188
+		}
189
+		return 0;
190
+	}
191
+
192
+
193
+	/**
194
+	 * Set transaction total
195
+	 *
196
+	 * @param float $total total value of transaction
197
+	 * @throws EE_Error
198
+	 * @throws InvalidArgumentException
199
+	 * @throws InvalidDataTypeException
200
+	 * @throws InvalidInterfaceException
201
+	 * @throws ReflectionException
202
+	 */
203
+	public function set_total($total = 0.00)
204
+	{
205
+		$this->set('TXN_total', (float) $total);
206
+	}
207
+
208
+
209
+	/**
210
+	 * Set Total Amount Paid to Date
211
+	 *
212
+	 * @param float $total_paid total amount paid to date (sum of all payments)
213
+	 * @throws EE_Error
214
+	 * @throws InvalidArgumentException
215
+	 * @throws InvalidDataTypeException
216
+	 * @throws InvalidInterfaceException
217
+	 * @throws ReflectionException
218
+	 */
219
+	public function set_paid($total_paid = 0.00)
220
+	{
221
+		$this->set('TXN_paid', (float) $total_paid);
222
+	}
223
+
224
+
225
+	/**
226
+	 * Set transaction status
227
+	 *
228
+	 * @param string $status        whether the transaction is open, declined, accepted,
229
+	 *                              or any number of custom values that can be set
230
+	 * @throws EE_Error
231
+	 * @throws InvalidArgumentException
232
+	 * @throws InvalidDataTypeException
233
+	 * @throws InvalidInterfaceException
234
+	 * @throws ReflectionException
235
+	 */
236
+	public function set_status($status = '')
237
+	{
238
+		$this->set('STS_ID', $status);
239
+	}
240
+
241
+
242
+	/**
243
+	 * Set hash salt
244
+	 *
245
+	 * @param string $hash_salt required for some payment gateways
246
+	 * @throws EE_Error
247
+	 * @throws InvalidArgumentException
248
+	 * @throws InvalidDataTypeException
249
+	 * @throws InvalidInterfaceException
250
+	 * @throws ReflectionException
251
+	 */
252
+	public function set_hash_salt($hash_salt = '')
253
+	{
254
+		$this->set('TXN_hash_salt', $hash_salt);
255
+	}
256
+
257
+
258
+	/**
259
+	 * Sets TXN_reg_steps array
260
+	 *
261
+	 * @param array $txn_reg_steps
262
+	 * @throws EE_Error
263
+	 * @throws InvalidArgumentException
264
+	 * @throws InvalidDataTypeException
265
+	 * @throws InvalidInterfaceException
266
+	 * @throws ReflectionException
267
+	 */
268
+	public function set_reg_steps(array $txn_reg_steps)
269
+	{
270
+		$this->set('TXN_reg_steps', $txn_reg_steps);
271
+	}
272
+
273
+
274
+	/**
275
+	 * Gets TXN_reg_steps
276
+	 *
277
+	 * @return array
278
+	 * @throws EE_Error
279
+	 * @throws InvalidArgumentException
280
+	 * @throws InvalidDataTypeException
281
+	 * @throws InvalidInterfaceException
282
+	 * @throws ReflectionException
283
+	 */
284
+	public function reg_steps()
285
+	{
286
+		$TXN_reg_steps = $this->get('TXN_reg_steps');
287
+		return is_array($TXN_reg_steps)
288
+			? $TXN_reg_steps
289
+			: [];
290
+	}
291
+
292
+
293
+	/**
294
+	 * @param string|null $schema
295
+	 *     Schemas:
296
+	 *     'localized_float': "3,023.00"
297
+	 *     'no_currency_code': "$3,023.00"
298
+	 *     null: "$3,023.00<span>USD</span>"
299
+	 * @return string of transaction's total cost, with currency symbol and decimal
300
+	 * @throws EE_Error
301
+	 * @throws InvalidArgumentException
302
+	 * @throws InvalidDataTypeException
303
+	 * @throws InvalidInterfaceException
304
+	 * @throws ReflectionException
305
+	 */
306
+	public function pretty_total(?string $schema = null)
307
+	{
308
+		return $this->get_pretty('TXN_total', $schema);
309
+	}
310
+
311
+
312
+	/**
313
+	 * Gets the amount paid in a pretty string (formatted and with currency symbol)
314
+	 *
315
+	 * @param string|null $schema
316
+	 *     Schemas:
317
+	 *     'localized_float': "3,023.00"
318
+	 *     'no_currency_code': "$3,023.00"
319
+	 *     null: "$3,023.00<span>USD</span>"
320
+	 * @return string
321
+	 * @throws EE_Error
322
+	 * @throws InvalidArgumentException
323
+	 * @throws InvalidDataTypeException
324
+	 * @throws InvalidInterfaceException
325
+	 * @throws ReflectionException
326
+	 */
327
+	public function pretty_paid(?string $schema = null)
328
+	{
329
+		return $this->get_pretty('TXN_paid', $schema);
330
+	}
331
+
332
+
333
+	/**
334
+	 * calculate the amount remaining for this transaction and return;
335
+	 *
336
+	 * @return float amount remaining
337
+	 * @throws EE_Error
338
+	 * @throws InvalidArgumentException
339
+	 * @throws InvalidDataTypeException
340
+	 * @throws InvalidInterfaceException
341
+	 * @throws ReflectionException
342
+	 */
343
+	public function remaining()
344
+	{
345
+		return $this->total() - $this->paid();
346
+	}
347
+
348
+
349
+	/**
350
+	 * get Transaction Total
351
+	 *
352
+	 * @return float
353
+	 * @throws EE_Error
354
+	 * @throws InvalidArgumentException
355
+	 * @throws InvalidDataTypeException
356
+	 * @throws InvalidInterfaceException
357
+	 * @throws ReflectionException
358
+	 */
359
+	public function total()
360
+	{
361
+		return (float) $this->get('TXN_total');
362
+	}
363
+
364
+
365
+	/**
366
+	 * get Total Amount Paid to Date
367
+	 *
368
+	 * @return float
369
+	 * @throws EE_Error
370
+	 * @throws InvalidArgumentException
371
+	 * @throws InvalidDataTypeException
372
+	 * @throws InvalidInterfaceException
373
+	 * @throws ReflectionException
374
+	 */
375
+	public function paid()
376
+	{
377
+		return (float) $this->get('TXN_paid');
378
+	}
379
+
380
+
381
+	/**
382
+	 * @return mixed|null
383
+	 * @throws EE_Error
384
+	 * @throws InvalidArgumentException
385
+	 * @throws InvalidDataTypeException
386
+	 * @throws InvalidInterfaceException
387
+	 * @throws ReflectionException
388
+	 */
389
+	public function get_cart_session()
390
+	{
391
+		$session_data = (array) $this->get('TXN_session_data');
392
+		return isset($session_data['cart']) && $session_data['cart'] instanceof EE_Cart
393
+			? $session_data['cart']
394
+			: null;
395
+	}
396
+
397
+
398
+	/**
399
+	 * get Transaction session data
400
+	 *
401
+	 * @return array|mixed
402
+	 * @throws EE_Error
403
+	 * @throws InvalidArgumentException
404
+	 * @throws InvalidDataTypeException
405
+	 * @throws InvalidInterfaceException
406
+	 * @throws ReflectionException
407
+	 */
408
+	public function session_data()
409
+	{
410
+		$session_data = $this->get('TXN_session_data');
411
+		if (empty($session_data)) {
412
+			$session_data = [
413
+				'id'            => null,
414
+				'user_id'       => null,
415
+				'ip_address'    => null,
416
+				'user_agent'    => null,
417
+				'init_access'   => null,
418
+				'last_access'   => null,
419
+				'pages_visited' => [],
420
+			];
421
+		}
422
+		return $session_data;
423
+	}
424
+
425
+
426
+	/**
427
+	 * Set session data within the TXN object
428
+	 *
429
+	 * @param EE_Session|array|null $session_data
430
+	 * @throws EE_Error
431
+	 * @throws InvalidArgumentException
432
+	 * @throws InvalidDataTypeException
433
+	 * @throws InvalidInterfaceException
434
+	 * @throws ReflectionException
435
+	 */
436
+	public function set_txn_session_data($session_data)
437
+	{
438
+		if ($session_data instanceof EE_Session) {
439
+			$this->set('TXN_session_data', $session_data->get_session_data(null, true));
440
+		} else {
441
+			$this->set('TXN_session_data', $session_data);
442
+		}
443
+	}
444
+
445
+
446
+	/**
447
+	 * get Transaction hash salt
448
+	 *
449
+	 * @return mixed
450
+	 * @throws EE_Error
451
+	 * @throws InvalidArgumentException
452
+	 * @throws InvalidDataTypeException
453
+	 * @throws InvalidInterfaceException
454
+	 * @throws ReflectionException
455
+	 */
456
+	public function hash_salt_()
457
+	{
458
+		return $this->get('TXN_hash_salt');
459
+	}
460
+
461
+
462
+	/**
463
+	 * Returns the transaction datetime as either:
464
+	 *            - unix timestamp format ($format = false, $gmt = true)
465
+	 *            - formatted date string including the UTC (timezone) offset ($format = true ($gmt
466
+	 *              has no affect with this option)), this also may include a timezone abbreviation if the
467
+	 *              set timezone in this class differs from what the timezone is on the blog.
468
+	 *            - formatted date string including the UTC (timezone) offset (default).
469
+	 *
470
+	 * @param boolean $format   - whether to return a unix timestamp (default) or formatted date string
471
+	 * @param boolean $gmt      - whether to return a unix timestamp with UTC offset applied (default)
472
+	 *                          or no UTC offset applied
473
+	 * @return string | int
474
+	 * @throws EE_Error
475
+	 * @throws InvalidArgumentException
476
+	 * @throws InvalidDataTypeException
477
+	 * @throws InvalidInterfaceException
478
+	 * @throws ReflectionException
479
+	 */
480
+	public function datetime($format = false, $gmt = false)
481
+	{
482
+		if ($format) {
483
+			return $this->get_pretty('TXN_timestamp');
484
+		}
485
+		if ($gmt) {
486
+			return $this->get_raw('TXN_timestamp');
487
+		}
488
+		return $this->get('TXN_timestamp');
489
+	}
490
+
491
+
492
+	/**
493
+	 * Gets registrations on this transaction
494
+	 *
495
+	 * @param array   $query_params array of query parameters
496
+	 * @param boolean $get_cached   TRUE to retrieve cached registrations or FALSE to pull from the db
497
+	 * @return EE_Base_Class[]|EE_Registration[]
498
+	 * @throws EE_Error
499
+	 * @throws InvalidArgumentException
500
+	 * @throws InvalidDataTypeException
501
+	 * @throws InvalidInterfaceException
502
+	 * @throws ReflectionException
503
+	 */
504
+	public function registrations($query_params = [], $get_cached = false)
505
+	{
506
+		$query_params = (empty($query_params) || ! is_array($query_params))
507
+			? [
508
+				'order_by' => [
509
+					'Event.EVT_name'     => 'ASC',
510
+					'Attendee.ATT_lname' => 'ASC',
511
+					'Attendee.ATT_fname' => 'ASC',
512
+					'REG_ID'             => 'ASC',
513
+				],
514
+			]
515
+			: $query_params;
516
+		$query_params = $get_cached
517
+			? []
518
+			: $query_params;
519
+		return $this->get_many_related('Registration', $query_params);
520
+	}
521
+
522
+
523
+	/**
524
+	 * Gets all the attendees for this transaction (handy for use with EE_Attendee's get_registrations_for_event
525
+	 * function for getting attendees and how many registrations they each have for an event)
526
+	 *
527
+	 * @return mixed EE_Attendee[] by default, int if $output is set to 'COUNT'
528
+	 * @throws EE_Error
529
+	 * @throws InvalidArgumentException
530
+	 * @throws InvalidDataTypeException
531
+	 * @throws InvalidInterfaceException
532
+	 * @throws ReflectionException
533
+	 */
534
+	public function attendees()
535
+	{
536
+		return $this->get_many_related('Attendee', [['Registration.Transaction.TXN_ID' => $this->ID()]]);
537
+	}
538
+
539
+
540
+	/**
541
+	 * Gets payments for this transaction. Unlike other such functions, order by 'DESC' by default
542
+	 *
543
+	 * @param array $query_params @see
544
+	 *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
545
+	 * @return EE_Base_Class[]|EE_Payment[]
546
+	 * @throws EE_Error
547
+	 * @throws InvalidArgumentException
548
+	 * @throws InvalidDataTypeException
549
+	 * @throws InvalidInterfaceException
550
+	 * @throws ReflectionException
551
+	 */
552
+	public function payments($query_params = [])
553
+	{
554
+		return $this->get_many_related('Payment', $query_params);
555
+	}
556
+
557
+
558
+	/**
559
+	 * gets only approved payments for this transaction
560
+	 *
561
+	 * @return EE_Base_Class[]|EE_Payment[]
562
+	 * @throws EE_Error
563
+	 * @throws InvalidArgumentException
564
+	 * @throws ReflectionException
565
+	 * @throws InvalidDataTypeException
566
+	 * @throws InvalidInterfaceException
567
+	 */
568
+	public function approved_payments()
569
+	{
570
+		EE_Registry::instance()->load_model('Payment');
571
+		return $this->get_many_related(
572
+			'Payment',
573
+			[
574
+				['STS_ID' => EEM_Payment::status_id_approved],
575
+				'order_by' => ['PAY_timestamp' => 'DESC'],
576
+			]
577
+		);
578
+	}
579
+
580
+
581
+	/**
582
+	 * Gets all payments which have not been approved
583
+	 *
584
+	 * @return EE_Base_Class[]|EEI_Payment[]
585
+	 * @throws EE_Error if a model is misconfigured somehow
586
+	 * @throws InvalidArgumentException
587
+	 * @throws InvalidDataTypeException
588
+	 * @throws InvalidInterfaceException
589
+	 * @throws ReflectionException
590
+	 */
591
+	public function pending_payments()
592
+	{
593
+		return $this->get_many_related(
594
+			'Payment',
595
+			[
596
+				[
597
+					'STS_ID' => EEM_Payment::status_id_pending,
598
+				],
599
+				'order_by' => [
600
+					'PAY_timestamp' => 'DESC',
601
+				],
602
+			]
603
+		);
604
+	}
605
+
606
+
607
+	/**
608
+	 * echoes $this->pretty_status()
609
+	 *
610
+	 * @param bool $show_icons
611
+	 * @throws EE_Error
612
+	 * @throws InvalidArgumentException
613
+	 * @throws InvalidDataTypeException
614
+	 * @throws InvalidInterfaceException
615
+	 * @throws ReflectionException
616
+	 */
617
+	public function e_pretty_status($show_icons = false)
618
+	{
619
+		echo wp_kses($this->pretty_status($show_icons), AllowedTags::getAllowedTags());
620
+	}
621
+
622
+
623
+	/**
624
+	 * returns a pretty version of the status, good for displaying to users
625
+	 *
626
+	 * @param bool $show_icons
627
+	 * @return string
628
+	 * @throws EE_Error
629
+	 * @throws InvalidArgumentException
630
+	 * @throws InvalidDataTypeException
631
+	 * @throws InvalidInterfaceException
632
+	 * @throws ReflectionException
633
+	 */
634
+	public function pretty_status($show_icons = false)
635
+	{
636
+		$status = EEM_Status::instance()->localized_status(
637
+			[$this->status_ID() => esc_html__('unknown', 'event_espresso')],
638
+			false,
639
+			'sentence'
640
+		);
641
+		$icon   = '';
642
+		switch ($this->status_ID()) {
643
+			case EEM_Transaction::complete_status_code:
644
+				$icon = $show_icons
645
+					? '<span class="dashicons dashicons-yes ee-icon-size-24 green-text"></span>'
646
+					: '';
647
+				break;
648
+			case EEM_Transaction::incomplete_status_code:
649
+				$icon = $show_icons
650
+					? '<span class="dashicons dashicons-marker ee-icon-size-16 lt-blue-text"></span>'
651
+					: '';
652
+				break;
653
+			case EEM_Transaction::abandoned_status_code:
654
+				$icon = $show_icons
655
+					? '<span class="dashicons dashicons-marker ee-icon-size-16 red-text"></span>'
656
+					: '';
657
+				break;
658
+			case EEM_Transaction::failed_status_code:
659
+				$icon = $show_icons
660
+					? '<span class="dashicons dashicons-no ee-icon-size-16 red-text"></span>'
661
+					: '';
662
+				break;
663
+			case EEM_Transaction::overpaid_status_code:
664
+				$icon = $show_icons
665
+					? '<span class="dashicons dashicons-plus ee-icon-size-16 orange-text"></span>'
666
+					: '';
667
+				break;
668
+		}
669
+		return $icon . $status[ $this->status_ID() ];
670
+	}
671
+
672
+
673
+	/**
674
+	 * get Transaction Status
675
+	 *
676
+	 * @return mixed
677
+	 * @throws EE_Error
678
+	 * @throws InvalidArgumentException
679
+	 * @throws InvalidDataTypeException
680
+	 * @throws InvalidInterfaceException
681
+	 * @throws ReflectionException
682
+	 */
683
+	public function status_ID()
684
+	{
685
+		return $this->get('STS_ID');
686
+	}
687
+
688
+
689
+	/**
690
+	 * Returns TRUE or FALSE for whether or not this transaction cost any money
691
+	 *
692
+	 * @return boolean
693
+	 * @throws EE_Error
694
+	 * @throws InvalidArgumentException
695
+	 * @throws InvalidDataTypeException
696
+	 * @throws InvalidInterfaceException
697
+	 * @throws ReflectionException
698
+	 */
699
+	public function is_free()
700
+	{
701
+		return EEH_Money::compare_floats($this->get('TXN_total'), 0, '==');
702
+	}
703
+
704
+
705
+	/**
706
+	 * Returns whether this transaction is complete
707
+	 * Useful in templates and other logic for deciding if we should ask for another payment...
708
+	 *
709
+	 * @return boolean
710
+	 * @throws EE_Error
711
+	 * @throws InvalidArgumentException
712
+	 * @throws InvalidDataTypeException
713
+	 * @throws InvalidInterfaceException
714
+	 * @throws ReflectionException
715
+	 */
716
+	public function is_completed()
717
+	{
718
+		return $this->status_ID() === EEM_Transaction::complete_status_code;
719
+	}
720
+
721
+
722
+	/**
723
+	 * Returns whether this transaction is incomplete
724
+	 * Useful in templates and other logic for deciding if we should ask for another payment...
725
+	 *
726
+	 * @return boolean
727
+	 * @throws EE_Error
728
+	 * @throws InvalidArgumentException
729
+	 * @throws InvalidDataTypeException
730
+	 * @throws InvalidInterfaceException
731
+	 * @throws ReflectionException
732
+	 */
733
+	public function is_incomplete()
734
+	{
735
+		return $this->status_ID() === EEM_Transaction::incomplete_status_code;
736
+	}
737
+
738
+
739
+	/**
740
+	 * Returns whether this transaction is overpaid
741
+	 * Useful in templates and other logic for deciding if monies need to be refunded
742
+	 *
743
+	 * @return boolean
744
+	 * @throws EE_Error
745
+	 * @throws InvalidArgumentException
746
+	 * @throws InvalidDataTypeException
747
+	 * @throws InvalidInterfaceException
748
+	 * @throws ReflectionException
749
+	 */
750
+	public function is_overpaid()
751
+	{
752
+		return $this->status_ID() === EEM_Transaction::overpaid_status_code;
753
+	}
754
+
755
+
756
+	/**
757
+	 * Returns whether this transaction was abandoned
758
+	 * meaning that the transaction/registration process was somehow interrupted and never completed
759
+	 * but that contact information exists for at least one registrant
760
+	 *
761
+	 * @return boolean
762
+	 * @throws EE_Error
763
+	 * @throws InvalidArgumentException
764
+	 * @throws InvalidDataTypeException
765
+	 * @throws InvalidInterfaceException
766
+	 * @throws ReflectionException
767
+	 */
768
+	public function is_abandoned()
769
+	{
770
+		return $this->status_ID() === EEM_Transaction::abandoned_status_code;
771
+	}
772
+
773
+
774
+	/**
775
+	 * Returns whether this transaction failed
776
+	 * meaning that the transaction/registration process was somehow interrupted and never completed
777
+	 * and that NO contact information exists for any registrants
778
+	 *
779
+	 * @return boolean
780
+	 * @throws EE_Error
781
+	 * @throws InvalidArgumentException
782
+	 * @throws InvalidDataTypeException
783
+	 * @throws InvalidInterfaceException
784
+	 * @throws ReflectionException
785
+	 */
786
+	public function failed()
787
+	{
788
+		return $this->status_ID() === EEM_Transaction::failed_status_code;
789
+	}
790
+
791
+
792
+	/**
793
+	 * This returns the url for the invoice of this transaction
794
+	 *
795
+	 * @param string $type 'html' or 'pdf' (default is pdf)
796
+	 * @return string
797
+	 * @throws DomainException
798
+	 * @throws EE_Error
799
+	 * @throws InvalidArgumentException
800
+	 * @throws InvalidDataTypeException
801
+	 * @throws InvalidInterfaceException
802
+	 * @throws ReflectionException
803
+	 */
804
+	public function invoice_url($type = 'html')
805
+	{
806
+		$REG = $this->primary_registration();
807
+		if (! $REG instanceof EE_Registration) {
808
+			return '';
809
+		}
810
+		return $REG->invoice_url($type);
811
+	}
812
+
813
+
814
+	/**
815
+	 * Gets the primary registration only
816
+	 *
817
+	 * @return EE_Base_Class|EE_Registration
818
+	 * @throws EE_Error
819
+	 * @throws InvalidArgumentException
820
+	 * @throws InvalidDataTypeException
821
+	 * @throws InvalidInterfaceException
822
+	 * @throws ReflectionException
823
+	 */
824
+	public function primary_registration()
825
+	{
826
+		if($this->_primary_registrant instanceof EE_Registration) {
827
+			return $this->_primary_registrant;
828
+		}
829
+
830
+		$registrations = (array) $this->get_many_related(
831
+			'Registration',
832
+			[['REG_count' => EEM_Registration::PRIMARY_REGISTRANT_COUNT]]
833
+		);
834
+		foreach ($registrations as $registration) {
835
+			// valid registration that is NOT cancelled or declined ?
836
+			if (
837
+				$registration instanceof EE_Registration
838
+				&& ! in_array($registration->status_ID(), EEM_Registration::closed_reg_statuses(), true)
839
+			) {
840
+				$this->_primary_registrant = $registration;
841
+				return $registration;
842
+			}
843
+		}
844
+		// nothing valid found, so just return first thing from array of results
845
+		$primary_registrant = reset($registrations);
846
+		$this->_primary_registrant = $primary_registrant instanceof EE_Registration ? $primary_registrant : null;
847
+		return $this->_primary_registrant;
848
+	}
849
+
850
+
851
+	/**
852
+	 * Gets the URL for viewing the receipt
853
+	 *
854
+	 * @param string $type 'pdf' or 'html' (default is 'html')
855
+	 * @return string
856
+	 * @throws DomainException
857
+	 * @throws EE_Error
858
+	 * @throws InvalidArgumentException
859
+	 * @throws InvalidDataTypeException
860
+	 * @throws InvalidInterfaceException
861
+	 * @throws ReflectionException
862
+	 */
863
+	public function receipt_url($type = 'html')
864
+	{
865
+		$REG = $this->primary_registration();
866
+		if (! $REG instanceof EE_Registration) {
867
+			return '';
868
+		}
869
+		return $REG->receipt_url($type);
870
+	}
871
+
872
+
873
+	/**
874
+	 * Gets the URL of the thank you page with this registration REG_url_link added as
875
+	 * a query parameter
876
+	 *
877
+	 * @return string
878
+	 * @throws EE_Error
879
+	 * @throws InvalidArgumentException
880
+	 * @throws InvalidDataTypeException
881
+	 * @throws InvalidInterfaceException
882
+	 * @throws ReflectionException
883
+	 */
884
+	public function payment_overview_url()
885
+	{
886
+		$primary_registration = $this->primary_registration();
887
+		return $primary_registration instanceof EE_Registration
888
+			? $primary_registration->payment_overview_url()
889
+			: false;
890
+	}
891
+
892
+
893
+	/**
894
+	 * @return string
895
+	 * @throws EE_Error
896
+	 * @throws InvalidArgumentException
897
+	 * @throws InvalidDataTypeException
898
+	 * @throws InvalidInterfaceException
899
+	 * @throws ReflectionException
900
+	 */
901
+	public function gateway_response_on_transaction()
902
+	{
903
+		$payment = $this->get_first_related('Payment');
904
+		return $payment instanceof EE_Payment
905
+			? $payment->gateway_response()
906
+			: '';
907
+	}
908
+
909
+
910
+	/**
911
+	 * Get the status object of this object
912
+	 *
913
+	 * @return EE_Base_Class|EE_Status
914
+	 * @throws EE_Error
915
+	 * @throws InvalidArgumentException
916
+	 * @throws InvalidDataTypeException
917
+	 * @throws InvalidInterfaceException
918
+	 * @throws ReflectionException
919
+	 */
920
+	public function status_obj()
921
+	{
922
+		return $this->get_first_related('Status');
923
+	}
924
+
925
+
926
+	/**
927
+	 * Gets all the extra meta info on this payment
928
+	 *
929
+	 * @param array $query_params
930
+	 * @return EE_Base_Class[]|EE_Extra_Meta
931
+	 * @throws EE_Error
932
+	 * @throws InvalidArgumentException
933
+	 * @throws InvalidDataTypeException
934
+	 * @throws InvalidInterfaceException
935
+	 * @throws ReflectionException
936
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
937
+	 */
938
+	public function extra_meta($query_params = [])
939
+	{
940
+		return $this->get_many_related('Extra_Meta', $query_params);
941
+	}
942
+
943
+
944
+	/**
945
+	 * Wrapper for _add_relation_to
946
+	 *
947
+	 * @param EE_Registration $registration
948
+	 * @return EE_Base_Class the relation was added to
949
+	 * @throws EE_Error
950
+	 * @throws InvalidArgumentException
951
+	 * @throws InvalidDataTypeException
952
+	 * @throws InvalidInterfaceException
953
+	 * @throws ReflectionException
954
+	 */
955
+	public function add_registration(EE_Registration $registration)
956
+	{
957
+		return $this->_add_relation_to($registration, 'Registration');
958
+	}
959
+
960
+
961
+	/**
962
+	 * Removes the given registration from being related (even before saving this transaction).
963
+	 * If an ID/index is provided and this transaction isn't saved yet, removes it from list of cached relations
964
+	 *
965
+	 * @param int $registration_or_id
966
+	 * @return EE_Base_Class that was removed from being related
967
+	 * @throws EE_Error
968
+	 * @throws InvalidArgumentException
969
+	 * @throws InvalidDataTypeException
970
+	 * @throws InvalidInterfaceException
971
+	 * @throws ReflectionException
972
+	 */
973
+	public function remove_registration_with_id($registration_or_id)
974
+	{
975
+		return $this->_remove_relation_to($registration_or_id, 'Registration');
976
+	}
977
+
978
+
979
+	/**
980
+	 * Gets all the line items which are for ACTUAL items
981
+	 *
982
+	 * @return EE_Line_Item[]
983
+	 * @throws EE_Error
984
+	 * @throws InvalidArgumentException
985
+	 * @throws InvalidDataTypeException
986
+	 * @throws InvalidInterfaceException
987
+	 * @throws ReflectionException
988
+	 */
989
+	public function items_purchased()
990
+	{
991
+		return $this->line_items([['LIN_type' => EEM_Line_Item::type_line_item]]);
992
+	}
993
+
994
+
995
+	/**
996
+	 * Wrapper for _add_relation_to
997
+	 *
998
+	 * @param EE_Line_Item $line_item
999
+	 * @return EE_Base_Class the relation was added to
1000
+	 * @throws EE_Error
1001
+	 * @throws InvalidArgumentException
1002
+	 * @throws InvalidDataTypeException
1003
+	 * @throws InvalidInterfaceException
1004
+	 * @throws ReflectionException
1005
+	 */
1006
+	public function add_line_item(EE_Line_Item $line_item)
1007
+	{
1008
+		return $this->_add_relation_to($line_item, 'Line_Item');
1009
+	}
1010
+
1011
+
1012
+	/**
1013
+	 * Gets ALL the line items related to this transaction (unstructured)
1014
+	 *
1015
+	 * @param array $query_params
1016
+	 * @return EE_Base_Class[]|EE_Line_Item[]
1017
+	 * @throws EE_Error
1018
+	 * @throws InvalidArgumentException
1019
+	 * @throws InvalidDataTypeException
1020
+	 * @throws InvalidInterfaceException
1021
+	 * @throws ReflectionException
1022
+	 */
1023
+	public function line_items($query_params = [])
1024
+	{
1025
+		return $this->get_many_related('Line_Item', $query_params);
1026
+	}
1027
+
1028
+
1029
+	/**
1030
+	 * Gets all the line items which are taxes on the total
1031
+	 *
1032
+	 * @return EE_Line_Item[]
1033
+	 * @throws EE_Error
1034
+	 * @throws InvalidArgumentException
1035
+	 * @throws InvalidDataTypeException
1036
+	 * @throws InvalidInterfaceException
1037
+	 * @throws ReflectionException
1038
+	 */
1039
+	public function tax_items()
1040
+	{
1041
+		return $this->line_items([['LIN_type' => EEM_Line_Item::type_tax]]);
1042
+	}
1043
+
1044
+
1045
+	/**
1046
+	 * Gets the total line item (which is a parent of all other related line items,
1047
+	 * meaning it takes them all into account on its total)
1048
+	 *
1049
+	 * @param bool $create_if_not_found
1050
+	 * @return EE_Line_Item|null
1051
+	 * @throws EE_Error
1052
+	 * @throws InvalidArgumentException
1053
+	 * @throws InvalidDataTypeException
1054
+	 * @throws InvalidInterfaceException
1055
+	 * @throws ReflectionException
1056
+	 */
1057
+	public function total_line_item(bool $create_if_not_found = true): ?EE_Line_Item
1058
+	{
1059
+		$item = $this->get_first_related('Line_Item', [['LIN_type' => EEM_Line_Item::type_total]]);
1060
+		if ($item instanceof EE_Line_Item) {
1061
+			return $item;
1062
+		}
1063
+		return $create_if_not_found
1064
+			? EEH_Line_Item::create_total_line_item($this)
1065
+			: null;
1066
+	}
1067
+
1068
+
1069
+	/**
1070
+	 * Returns the total amount of tax on this transaction
1071
+	 * (assumes there's only one tax subtotal line item)
1072
+	 *
1073
+	 * @return float
1074
+	 * @throws EE_Error
1075
+	 * @throws InvalidArgumentException
1076
+	 * @throws InvalidDataTypeException
1077
+	 * @throws InvalidInterfaceException
1078
+	 * @throws ReflectionException
1079
+	 */
1080
+	public function tax_total()
1081
+	{
1082
+		$tax_line_item = $this->tax_total_line_item();
1083
+		if ($tax_line_item) {
1084
+			return (float) $tax_line_item->total();
1085
+		}
1086
+		return (float) 0;
1087
+	}
1088
+
1089
+
1090
+	/**
1091
+	 * Gets the tax subtotal line item (assumes there's only one)
1092
+	 *
1093
+	 * @return EE_Line_Item
1094
+	 * @throws EE_Error
1095
+	 * @throws InvalidArgumentException
1096
+	 * @throws InvalidDataTypeException
1097
+	 * @throws InvalidInterfaceException
1098
+	 * @throws ReflectionException
1099
+	 */
1100
+	public function tax_total_line_item()
1101
+	{
1102
+		return EEH_Line_Item::get_taxes_subtotal($this->total_line_item());
1103
+	}
1104
+
1105
+
1106
+	/**
1107
+	 * Gets the array of billing info for the gateway and for this transaction's primary registration's attendee.
1108
+	 *
1109
+	 * @return EE_Form_Section_Proper
1110
+	 * @throws EE_Error
1111
+	 * @throws InvalidArgumentException
1112
+	 * @throws InvalidDataTypeException
1113
+	 * @throws InvalidInterfaceException
1114
+	 * @throws ReflectionException
1115
+	 */
1116
+	public function billing_info()
1117
+	{
1118
+		$payment_method = $this->payment_method();
1119
+		if (! $payment_method) {
1120
+			EE_Error::add_error(
1121
+				esc_html__(
1122
+					'Could not find billing info for transaction because no gateway has been used for it yet',
1123
+					'event_espresso'
1124
+				),
1125
+				__FILE__,
1126
+				__FUNCTION__,
1127
+				__LINE__
1128
+			);
1129
+			return null;
1130
+		}
1131
+		$primary_reg = $this->primary_registration();
1132
+		if (! $primary_reg) {
1133
+			EE_Error::add_error(
1134
+				esc_html__(
1135
+					'Cannot get billing info for gateway %s on transaction because no primary registration exists',
1136
+					'event_espresso'
1137
+				),
1138
+				__FILE__,
1139
+				__FUNCTION__,
1140
+				__LINE__
1141
+			);
1142
+			return null;
1143
+		}
1144
+		$attendee = $primary_reg->attendee();
1145
+		if (! $attendee) {
1146
+			EE_Error::add_error(
1147
+				esc_html__(
1148
+					'Cannot get billing info for gateway %s on transaction because the primary registration has no attendee exists',
1149
+					'event_espresso'
1150
+				),
1151
+				__FILE__,
1152
+				__FUNCTION__,
1153
+				__LINE__
1154
+			);
1155
+			return null;
1156
+		}
1157
+		return $attendee->billing_info_for_payment_method($payment_method);
1158
+	}
1159
+
1160
+
1161
+	/**
1162
+	 * Gets PMD_ID
1163
+	 *
1164
+	 * @return int
1165
+	 * @throws EE_Error
1166
+	 * @throws InvalidArgumentException
1167
+	 * @throws InvalidDataTypeException
1168
+	 * @throws InvalidInterfaceException
1169
+	 * @throws ReflectionException
1170
+	 */
1171
+	public function payment_method_ID()
1172
+	{
1173
+		return $this->get('PMD_ID');
1174
+	}
1175
+
1176
+
1177
+	/**
1178
+	 * Sets PMD_ID
1179
+	 *
1180
+	 * @param int $PMD_ID
1181
+	 * @throws EE_Error
1182
+	 * @throws InvalidArgumentException
1183
+	 * @throws InvalidDataTypeException
1184
+	 * @throws InvalidInterfaceException
1185
+	 * @throws ReflectionException
1186
+	 */
1187
+	public function set_payment_method_ID($PMD_ID)
1188
+	{
1189
+		$this->set('PMD_ID', $PMD_ID);
1190
+	}
1191
+
1192
+
1193
+	/**
1194
+	 * Gets the last-used payment method on this transaction
1195
+	 * (we COULD just use the last-made payment, but some payment methods, namely
1196
+	 * offline ones, dont' create payments)
1197
+	 *
1198
+	 * @return EE_Payment_Method
1199
+	 * @throws EE_Error
1200
+	 * @throws InvalidArgumentException
1201
+	 * @throws InvalidDataTypeException
1202
+	 * @throws InvalidInterfaceException
1203
+	 * @throws ReflectionException
1204
+	 */
1205
+	public function payment_method()
1206
+	{
1207
+		$PMD_ID = $this->payment_method_ID();
1208
+		if($PMD_ID) {
1209
+			$pm = EEM_Payment_Method::instance()->get_one_by_ID($this->payment_method_ID());
1210
+			if ($pm instanceof EE_Payment_Method) {
1211
+				return $pm;
1212
+			}
1213
+		}
1214
+		$last_payment = $this->last_payment();
1215
+		if ($last_payment instanceof EE_Payment && $last_payment->payment_method()) {
1216
+			return $last_payment->payment_method();
1217
+		}
1218
+		return null;
1219
+	}
1220
+
1221
+
1222
+	/**
1223
+	 * Gets the last payment made
1224
+	 *
1225
+	 * @return EE_Base_Class|EE_Payment|null
1226
+	 * @throws EE_Error
1227
+	 * @throws InvalidArgumentException
1228
+	 * @throws InvalidDataTypeException
1229
+	 * @throws InvalidInterfaceException
1230
+	 * @throws ReflectionException
1231
+	 */
1232
+	public function last_payment(): ?EE_Payment
1233
+	{
1234
+		return $this->get_first_related('Payment', ['order_by' => ['PAY_ID' => 'desc']]);
1235
+	}
1236
+
1237
+
1238
+	/**
1239
+	 * Gets all the line items which are unrelated to tickets on this transaction
1240
+	 *
1241
+	 * @return EE_Line_Item[]
1242
+	 * @throws EE_Error
1243
+	 * @throws InvalidArgumentException
1244
+	 * @throws InvalidDataTypeException
1245
+	 * @throws InvalidInterfaceException
1246
+	 * @throws ReflectionException
1247
+	 */
1248
+	public function non_ticket_line_items()
1249
+	{
1250
+		return EEM_Line_Item::instance()->get_all_non_ticket_line_items_for_transaction($this->ID());
1251
+	}
1252
+
1253
+
1254
+	/**
1255
+	 * possibly toggles TXN status
1256
+	 *
1257
+	 * @param boolean $update whether to save the TXN
1258
+	 * @return bool whether the TXN was saved
1259
+	 * @throws EE_Error
1260
+	 * @throws InvalidArgumentException
1261
+	 * @throws InvalidDataTypeException
1262
+	 * @throws InvalidInterfaceException
1263
+	 * @throws ReflectionException
1264
+	 * @throws RuntimeException
1265
+	 */
1266
+	public function update_status_based_on_total_paid($update = true)
1267
+	{
1268
+		// set transaction status based on comparison of TXN_paid vs TXN_total
1269
+		if (EEH_Money::compare_floats($this->paid(), $this->total(), '>')) {
1270
+			$new_txn_status = EEM_Transaction::overpaid_status_code;
1271
+		} elseif (EEH_Money::compare_floats($this->paid(), $this->total())) {
1272
+			$new_txn_status = EEM_Transaction::complete_status_code;
1273
+		} elseif (EEH_Money::compare_floats($this->paid(), $this->total(), '<')) {
1274
+			$new_txn_status = EEM_Transaction::incomplete_status_code;
1275
+		} else {
1276
+			throw new RuntimeException(
1277
+				esc_html__('The total paid calculation for this transaction is inaccurate.', 'event_espresso')
1278
+			);
1279
+		}
1280
+		if ($new_txn_status !== $this->status_ID()) {
1281
+			$this->set_status($new_txn_status);
1282
+			if ($update) {
1283
+				return (bool) $this->save();
1284
+			}
1285
+		}
1286
+		return false;
1287
+	}
1288
+
1289
+
1290
+	/**
1291
+	 * Updates the transaction's status and total_paid based on all the payments
1292
+	 * that apply to it
1293
+	 *
1294
+	 * @return array|bool
1295
+	 * @throws EE_Error
1296
+	 * @throws InvalidArgumentException
1297
+	 * @throws ReflectionException
1298
+	 * @throws InvalidDataTypeException
1299
+	 * @throws InvalidInterfaceException
1300
+	 * @deprecated
1301
+	 */
1302
+	public function update_based_on_payments()
1303
+	{
1304
+		EE_Error::doing_it_wrong(
1305
+			__CLASS__ . '::' . __FUNCTION__,
1306
+			sprintf(
1307
+				esc_html__('This method is deprecated. Please use "%s" instead', 'event_espresso'),
1308
+				'EE_Transaction_Processor::update_transaction_and_registrations_after_checkout_or_payment()'
1309
+			),
1310
+			'4.6.0'
1311
+		);
1312
+		/** @type EE_Transaction_Processor $transaction_processor */
1313
+		$transaction_processor = EE_Registry::instance()->load_class('Transaction_Processor');
1314
+		return $transaction_processor->update_transaction_and_registrations_after_checkout_or_payment($this);
1315
+	}
1316
+
1317
+
1318
+	/**
1319
+	 * @return string
1320
+	 */
1321
+	public function old_txn_status()
1322
+	{
1323
+		return $this->_old_txn_status;
1324
+	}
1325
+
1326
+
1327
+	/**
1328
+	 * @param string $old_txn_status
1329
+	 */
1330
+	public function set_old_txn_status($old_txn_status)
1331
+	{
1332
+		// only set the first time
1333
+		if ($this->_old_txn_status === null) {
1334
+			$this->_old_txn_status = $old_txn_status;
1335
+		}
1336
+	}
1337
+
1338
+
1339
+	/**
1340
+	 * reg_status_updated
1341
+	 *
1342
+	 * @return bool
1343
+	 * @throws EE_Error
1344
+	 * @throws InvalidArgumentException
1345
+	 * @throws InvalidDataTypeException
1346
+	 * @throws InvalidInterfaceException
1347
+	 * @throws ReflectionException
1348
+	 */
1349
+	public function txn_status_updated()
1350
+	{
1351
+		return $this->status_ID() !== $this->_old_txn_status && $this->_old_txn_status !== null;
1352
+	}
1353
+
1354
+
1355
+	/**
1356
+	 * _reg_steps_completed
1357
+	 * if $check_all is TRUE, then returns TRUE if ALL reg steps have been marked as completed,
1358
+	 * if a $reg_step_slug is provided, then this step will be skipped when testing for completion
1359
+	 * if $check_all is FALSE and a $reg_step_slug is provided, then ONLY that reg step will be tested for completion
1360
+	 *
1361
+	 * @param string $reg_step_slug
1362
+	 * @param bool   $check_all
1363
+	 * @return bool|int
1364
+	 * @throws EE_Error
1365
+	 * @throws InvalidArgumentException
1366
+	 * @throws InvalidDataTypeException
1367
+	 * @throws InvalidInterfaceException
1368
+	 * @throws ReflectionException
1369
+	 */
1370
+	private function _reg_steps_completed($reg_step_slug = '', $check_all = true)
1371
+	{
1372
+		$reg_steps = $this->reg_steps();
1373
+		if (! is_array($reg_steps) || empty($reg_steps)) {
1374
+			return false;
1375
+		}
1376
+		// loop thru reg steps array)
1377
+		foreach ($reg_steps as $slug => $reg_step_completed) {
1378
+			// if NOT checking ALL steps (only checking one step)
1379
+			if (! $check_all) {
1380
+				// and this is the one
1381
+				if ($slug === $reg_step_slug) {
1382
+					return $reg_step_completed;
1383
+				}
1384
+				// skip to next reg step in loop
1385
+				continue;
1386
+			}
1387
+			// $check_all must be true, else we would never have gotten to this point
1388
+			if ($slug === $reg_step_slug) {
1389
+				// if we reach this point, then we are testing either:
1390
+				// all_reg_steps_completed_except() or
1391
+				// all_reg_steps_completed_except_final_step(),
1392
+				// and since this is the reg step EXCEPTION being tested
1393
+				// we want to return true (yes true) if this reg step is NOT completed
1394
+				// ie: "is everything completed except the final step?"
1395
+				// "that is correct... the final step is not completed, but all others are."
1396
+				return $reg_step_completed !== true;
1397
+			}
1398
+			if ($reg_step_completed !== true) {
1399
+				// if any reg step is NOT completed, then ALL steps are not completed
1400
+				return false;
1401
+			}
1402
+		}
1403
+		return true;
1404
+	}
1405
+
1406
+
1407
+	/**
1408
+	 * all_reg_steps_completed
1409
+	 * returns:
1410
+	 *    true if ALL reg steps have been marked as completed
1411
+	 *        or false if any step is not completed
1412
+	 *
1413
+	 * @return bool
1414
+	 * @throws EE_Error
1415
+	 * @throws InvalidArgumentException
1416
+	 * @throws InvalidDataTypeException
1417
+	 * @throws InvalidInterfaceException
1418
+	 * @throws ReflectionException
1419
+	 */
1420
+	public function all_reg_steps_completed()
1421
+	{
1422
+		return $this->_reg_steps_completed();
1423
+	}
1424
+
1425
+
1426
+	/**
1427
+	 * all_reg_steps_completed_except
1428
+	 * returns:
1429
+	 *        true if ALL reg steps, except a particular step that you wish to skip over, have been marked as completed
1430
+	 *        or false if any other step is not completed
1431
+	 *        or false if ALL steps are completed including the exception you are testing !!!
1432
+	 *
1433
+	 * @param string $exception
1434
+	 * @return bool
1435
+	 * @throws EE_Error
1436
+	 * @throws InvalidArgumentException
1437
+	 * @throws InvalidDataTypeException
1438
+	 * @throws InvalidInterfaceException
1439
+	 * @throws ReflectionException
1440
+	 */
1441
+	public function all_reg_steps_completed_except($exception = '')
1442
+	{
1443
+		return $this->_reg_steps_completed($exception);
1444
+	}
1445
+
1446
+
1447
+	/**
1448
+	 * all_reg_steps_completed_except
1449
+	 * returns:
1450
+	 *        true if ALL reg steps, except the final step, have been marked as completed
1451
+	 *        or false if any step is not completed
1452
+	 *    or false if ALL steps are completed including the final step !!!
1453
+	 *
1454
+	 * @return bool
1455
+	 * @throws EE_Error
1456
+	 * @throws InvalidArgumentException
1457
+	 * @throws InvalidDataTypeException
1458
+	 * @throws InvalidInterfaceException
1459
+	 * @throws ReflectionException
1460
+	 */
1461
+	public function all_reg_steps_completed_except_final_step()
1462
+	{
1463
+		return $this->_reg_steps_completed('finalize_registration');
1464
+	}
1465
+
1466
+
1467
+	/**
1468
+	 * reg_step_completed
1469
+	 * returns:
1470
+	 *    true if a specific reg step has been marked as completed
1471
+	 *    a Unix timestamp if it has been initialized but not yet completed,
1472
+	 *    or false if it has not yet been initialized
1473
+	 *
1474
+	 * @param string $reg_step_slug
1475
+	 * @return bool|int
1476
+	 * @throws EE_Error
1477
+	 * @throws InvalidArgumentException
1478
+	 * @throws InvalidDataTypeException
1479
+	 * @throws InvalidInterfaceException
1480
+	 * @throws ReflectionException
1481
+	 */
1482
+	public function reg_step_completed($reg_step_slug)
1483
+	{
1484
+		return $this->_reg_steps_completed($reg_step_slug, false);
1485
+	}
1486
+
1487
+
1488
+	/**
1489
+	 * completed_final_reg_step
1490
+	 * returns:
1491
+	 *    true if the finalize_registration reg step has been marked as completed
1492
+	 *    a Unix timestamp if it has been initialized but not yet completed,
1493
+	 *    or false if it has not yet been initialized
1494
+	 *
1495
+	 * @return bool|int
1496
+	 * @throws EE_Error
1497
+	 * @throws InvalidArgumentException
1498
+	 * @throws InvalidDataTypeException
1499
+	 * @throws InvalidInterfaceException
1500
+	 * @throws ReflectionException
1501
+	 */
1502
+	public function final_reg_step_completed()
1503
+	{
1504
+		return $this->_reg_steps_completed('finalize_registration', false);
1505
+	}
1506
+
1507
+
1508
+	/**
1509
+	 * set_reg_step_initiated
1510
+	 * given a valid TXN_reg_step, this sets it's value to a unix timestamp
1511
+	 *
1512
+	 * @param string $reg_step_slug
1513
+	 * @return boolean
1514
+	 * @throws EE_Error
1515
+	 * @throws InvalidArgumentException
1516
+	 * @throws InvalidDataTypeException
1517
+	 * @throws InvalidInterfaceException
1518
+	 * @throws ReflectionException
1519
+	 */
1520
+	public function set_reg_step_initiated($reg_step_slug)
1521
+	{
1522
+		return $this->_set_reg_step_completed_status($reg_step_slug, time());
1523
+	}
1524
+
1525
+
1526
+	/**
1527
+	 * set_reg_step_completed
1528
+	 * given a valid TXN_reg_step, this sets the step as completed
1529
+	 *
1530
+	 * @param string $reg_step_slug
1531
+	 * @return boolean
1532
+	 * @throws EE_Error
1533
+	 * @throws InvalidArgumentException
1534
+	 * @throws InvalidDataTypeException
1535
+	 * @throws InvalidInterfaceException
1536
+	 * @throws ReflectionException
1537
+	 */
1538
+	public function set_reg_step_completed($reg_step_slug)
1539
+	{
1540
+		return $this->_set_reg_step_completed_status($reg_step_slug, true);
1541
+	}
1542
+
1543
+
1544
+	/**
1545
+	 * set_reg_step_completed
1546
+	 * given a valid TXN_reg_step slug, this sets the step as NOT completed
1547
+	 *
1548
+	 * @param string $reg_step_slug
1549
+	 * @return boolean
1550
+	 * @throws EE_Error
1551
+	 * @throws InvalidArgumentException
1552
+	 * @throws InvalidDataTypeException
1553
+	 * @throws InvalidInterfaceException
1554
+	 * @throws ReflectionException
1555
+	 */
1556
+	public function set_reg_step_not_completed($reg_step_slug)
1557
+	{
1558
+		return $this->_set_reg_step_completed_status($reg_step_slug, false);
1559
+	}
1560
+
1561
+
1562
+	/**
1563
+	 * set_reg_step_completed
1564
+	 * given a valid reg step slug, this sets the TXN_reg_step completed status which is either:
1565
+	 *
1566
+	 * @param string      $reg_step_slug
1567
+	 * @param boolean|int $status
1568
+	 * @return boolean
1569
+	 * @throws EE_Error
1570
+	 * @throws InvalidArgumentException
1571
+	 * @throws InvalidDataTypeException
1572
+	 * @throws InvalidInterfaceException
1573
+	 * @throws ReflectionException
1574
+	 */
1575
+	private function _set_reg_step_completed_status($reg_step_slug, $status)
1576
+	{
1577
+		// validate status
1578
+		$status = is_bool($status) || is_int($status)
1579
+			? $status
1580
+			: false;
1581
+		// get reg steps array
1582
+		$txn_reg_steps = $this->reg_steps();
1583
+		// if reg step does NOT exist
1584
+		if (! isset($txn_reg_steps[ $reg_step_slug ])) {
1585
+			return false;
1586
+		}
1587
+		// if  we're trying to complete a step that is already completed
1588
+		if ($txn_reg_steps[ $reg_step_slug ] === true) {
1589
+			return true;
1590
+		}
1591
+		// if  we're trying to complete a step that hasn't even started
1592
+		if ($status === true && $txn_reg_steps[ $reg_step_slug ] === false) {
1593
+			return false;
1594
+		}
1595
+		// if current status value matches the incoming value (no change)
1596
+		// type casting as int means values should collapse to either 0, 1, or a timestamp like 1234567890
1597
+		if ((int) $txn_reg_steps[ $reg_step_slug ] === (int) $status) {
1598
+			// this will happen in cases where multiple AJAX requests occur during the same step
1599
+			return true;
1600
+		}
1601
+		// if we're trying to set a start time, but it has already been set...
1602
+		if (is_numeric($status) && is_numeric($txn_reg_steps[ $reg_step_slug ])) {
1603
+			// skip the update below, but don't return FALSE so that errors won't be displayed
1604
+			return true;
1605
+		}
1606
+		// update completed status
1607
+		$txn_reg_steps[ $reg_step_slug ] = $status;
1608
+		$this->set_reg_steps($txn_reg_steps);
1609
+		$this->save();
1610
+		return true;
1611
+	}
1612
+
1613
+
1614
+	/**
1615
+	 * remove_reg_step
1616
+	 * given a valid TXN_reg_step slug, this will remove (unset)
1617
+	 * the reg step from the TXN reg step array
1618
+	 *
1619
+	 * @param string $reg_step_slug
1620
+	 * @return void
1621
+	 * @throws EE_Error
1622
+	 * @throws InvalidArgumentException
1623
+	 * @throws InvalidDataTypeException
1624
+	 * @throws InvalidInterfaceException
1625
+	 * @throws ReflectionException
1626
+	 */
1627
+	public function remove_reg_step($reg_step_slug)
1628
+	{
1629
+		// get reg steps array
1630
+		$txn_reg_steps = $this->reg_steps();
1631
+		unset($txn_reg_steps[ $reg_step_slug ]);
1632
+		$this->set_reg_steps($txn_reg_steps);
1633
+	}
1634
+
1635
+
1636
+	/**
1637
+	 * toggle_failed_transaction_status
1638
+	 * upgrades a TXNs status from failed to abandoned,
1639
+	 * meaning that contact information has been captured for at least one registrant
1640
+	 *
1641
+	 * @param bool $save
1642
+	 * @return bool
1643
+	 * @throws EE_Error
1644
+	 * @throws InvalidArgumentException
1645
+	 * @throws InvalidDataTypeException
1646
+	 * @throws InvalidInterfaceException
1647
+	 * @throws ReflectionException
1648
+	 */
1649
+	public function toggle_failed_transaction_status($save = true)
1650
+	{
1651
+		// if TXN status is still set as "failed"...
1652
+		if ($this->status_ID() === EEM_Transaction::failed_status_code) {
1653
+			$this->set_status(EEM_Transaction::abandoned_status_code);
1654
+			if ($save) {
1655
+				$this->save();
1656
+			}
1657
+			return true;
1658
+		}
1659
+		return false;
1660
+	}
1661
+
1662
+
1663
+	/**
1664
+	 * toggle_abandoned_transaction_status
1665
+	 * upgrades a TXNs status from failed or abandoned to incomplete
1666
+	 *
1667
+	 * @return bool
1668
+	 * @throws EE_Error
1669
+	 * @throws InvalidArgumentException
1670
+	 * @throws InvalidDataTypeException
1671
+	 * @throws InvalidInterfaceException
1672
+	 * @throws ReflectionException
1673
+	 */
1674
+	public function toggle_abandoned_transaction_status()
1675
+	{
1676
+		// if TXN status has not been updated already due to a payment, and is still set as "failed" or "abandoned"...
1677
+		$txn_status = $this->status_ID();
1678
+		if (
1679
+			$txn_status === EEM_Transaction::failed_status_code
1680
+			|| $txn_status === EEM_Transaction::abandoned_status_code
1681
+		) {
1682
+			// if a contact record for the primary registrant has been created
1683
+			if (
1684
+				$this->primary_registration() instanceof EE_Registration
1685
+				&& $this->primary_registration()->attendee() instanceof EE_Attendee
1686
+			) {
1687
+				$this->set_status(EEM_Transaction::incomplete_status_code);
1688
+			} else {
1689
+				// no contact record? yer abandoned!
1690
+				$this->set_status(EEM_Transaction::abandoned_status_code);
1691
+			}
1692
+			return true;
1693
+		}
1694
+		return false;
1695
+	}
1696
+
1697
+
1698
+	/**
1699
+	 * checks if an Abandoned TXN has any related payments, and if so,
1700
+	 * updates the TXN status based on the amount paid
1701
+	 *
1702
+	 * @throws EE_Error
1703
+	 * @throws InvalidArgumentException
1704
+	 * @throws InvalidDataTypeException
1705
+	 * @throws InvalidInterfaceException
1706
+	 * @throws ReflectionException
1707
+	 * @throws RuntimeException
1708
+	 * @throws ReflectionException
1709
+	 */
1710
+	public function verify_abandoned_transaction_status()
1711
+	{
1712
+		if ($this->status_ID() !== EEM_Transaction::abandoned_status_code) {
1713
+			return;
1714
+		}
1715
+		$payments = $this->get_many_related('Payment');
1716
+		if (! empty($payments)) {
1717
+			foreach ($payments as $payment) {
1718
+				if ($payment instanceof EE_Payment) {
1719
+					// kk this TXN should NOT be abandoned
1720
+					$this->update_status_based_on_total_paid();
1721
+					if (! (defined('DOING_AJAX') && DOING_AJAX) && is_admin()) {
1722
+						EE_Error::add_attention(
1723
+							sprintf(
1724
+								esc_html__(
1725
+									'The status for Transaction #%1$d has been updated from "Abandoned" to "%2$s", because at least one payment has been made towards it. If the payment appears in the "Payment Details" table below, you may need to edit its status and/or other details as well.',
1726
+									'event_espresso'
1727
+								),
1728
+								$this->ID(),
1729
+								$this->pretty_status()
1730
+							)
1731
+						);
1732
+					}
1733
+					// get final reg step status
1734
+					$finalized = $this->final_reg_step_completed();
1735
+					// if the 'finalize_registration' step has been initiated (has a timestamp)
1736
+					// but has not yet been fully completed (TRUE)
1737
+					if (is_int($finalized) && $finalized !== false && $finalized !== true) {
1738
+						$this->set_reg_step_completed('finalize_registration');
1739
+						$this->save();
1740
+					}
1741
+				}
1742
+			}
1743
+		}
1744
+	}
1745
+
1746
+
1747
+	/**
1748
+	 * @throws EE_Error
1749
+	 * @throws InvalidArgumentException
1750
+	 * @throws InvalidDataTypeException
1751
+	 * @throws InvalidInterfaceException
1752
+	 * @throws ReflectionException
1753
+	 * @throws RuntimeException
1754
+	 * @since 4.10.4.p
1755
+	 */
1756
+	public function recalculateLineItems()
1757
+	{
1758
+		$total_line_item = $this->total_line_item(false);
1759
+		if ($total_line_item instanceof EE_Line_Item) {
1760
+			EEH_Line_Item::resetIsTaxableForTickets($total_line_item);
1761
+			return EEH_Line_Item::apply_taxes($total_line_item, true);
1762
+		}
1763
+		return false;
1764
+	}
1765 1765
 }
Please login to merge, or discard this patch.
data_migration_scripts/4_1_0_stages/EE_DMS_4_1_0_attendees.dmsstage.php 2 patches
Indentation   +912 added lines, -912 removed lines patch added patch discarded remove patch
@@ -225,937 +225,937 @@
 block discarded – undo
225 225
 class EE_DMS_4_1_0_attendees extends EE_Data_Migration_Script_Stage_Table
226 226
 {
227 227
 
228
-    private string $_new_attendee_cpt_table;
228
+	private string $_new_attendee_cpt_table;
229 229
 
230
-    private string $_new_attendee_meta_table;
230
+	private string $_new_attendee_meta_table;
231 231
 
232
-    private string $_new_reg_table;
232
+	private string $_new_reg_table;
233 233
 
234
-    private string $_new_transaction_table;
234
+	private string $_new_transaction_table;
235 235
 
236
-    private string $_new_payment_table;
236
+	private string $_new_payment_table;
237 237
 
238
-    private string $_old_mer_table;
238
+	private string $_old_mer_table;
239 239
 
240
-    private string $_new_ticket_table;
240
+	private string $_new_ticket_table;
241 241
 
242
-    private string $_new_ticket_datetime_table;
242
+	private string $_new_ticket_datetime_table;
243 243
 
244
-    private string $_new_datetime_table;
244
+	private string $_new_datetime_table;
245 245
 
246
-    private string $_new_datetime_ticket_table;
246
+	private string $_new_datetime_ticket_table;
247 247
 
248
-    private string $_new_price_table;
248
+	private string $_new_price_table;
249 249
 
250
-    private string $_new_ticket_price_table;
250
+	private string $_new_ticket_price_table;
251 251
 
252
-    /**
253
-     * Remembers whether the mer table exists
254
-     *
255
-     * @var boolean
256
-     */
257
-    private ?bool $_mer_tables_exist = null;
252
+	/**
253
+	 * Remembers whether the mer table exists
254
+	 *
255
+	 * @var boolean
256
+	 */
257
+	private ?bool $_mer_tables_exist = null;
258 258
 
259 259
 
260
-    public function __construct()
261
-    {
262
-        global $wpdb;
263
-        $this->_pretty_name = esc_html__("Attendees", "event_espresso");
264
-        $this->_old_table   = $wpdb->prefix . "events_attendee";
265
-        // Only select the event status column from the event table.
266
-        $this->select_expression = 'att.*, e.event_status';
267
-        // Only select attendees for events that aren't deleted.
268
-        $this->_extra_where_sql           = 'AS att
260
+	public function __construct()
261
+	{
262
+		global $wpdb;
263
+		$this->_pretty_name = esc_html__("Attendees", "event_espresso");
264
+		$this->_old_table   = $wpdb->prefix . "events_attendee";
265
+		// Only select the event status column from the event table.
266
+		$this->select_expression = 'att.*, e.event_status';
267
+		// Only select attendees for events that aren't deleted.
268
+		$this->_extra_where_sql           = 'AS att
269 269
             INNER JOIN ' . $wpdb->prefix . 'events_detail AS e ON att.event_id=e.id
270 270
             WHERE e.event_status!="D"';
271
-        $this->_old_mer_table             = $wpdb->prefix . "events_multi_event_registration_id_group";
272
-        $this->_new_attendee_cpt_table    = $wpdb->posts;
273
-        $this->_new_attendee_meta_table   = $wpdb->prefix . "esp_attendee_meta";
274
-        $this->_new_reg_table             = $wpdb->prefix . "esp_registration";
275
-        $this->_new_transaction_table     = $wpdb->prefix . "esp_transaction";
276
-        $this->_new_payment_table         = $wpdb->prefix . "esp_payment";
277
-        $this->_new_ticket_table          = $wpdb->prefix . "esp_ticket";
278
-        $this->_new_ticket_datetime_table = $wpdb->prefix . "esp_datetime_ticket";
279
-        $this->_new_datetime_table        = $wpdb->prefix . "esp_datetime";
280
-        $this->_new_datetime_ticket_table = $wpdb->prefix . "esp_datetime_ticket";
281
-        $this->_new_price_table           = $wpdb->prefix . "esp_price";
282
-        $this->_new_ticket_price_table    = $wpdb->prefix . "esp_ticket_price";
283
-        parent::__construct();
284
-    }
285
-
286
-
287
-    /**
288
-     * @throws EE_Error
289
-     */
290
-    protected function _migrate_old_row($old_row)
291
-    {
292
-        // first check if there's already a new attendee with similar characteristics
293
-        $new_att_id = $this->_find_attendee_cpt_matching($old_row);
294
-        if (! $new_att_id) {
295
-            $new_att_id = $this->_insert_new_attendee_cpt($old_row);
296
-            if (! $new_att_id) {
297
-                // if we couldn't even make an attendee, abandon all hope
298
-                return;
299
-            }
300
-            $new_att_meta_id = $this->_insert_attendee_meta_row($old_row, $new_att_id);
301
-            if ($new_att_meta_id) {
302
-                $this->get_migration_script()->set_mapping(
303
-                    $this->_old_table,
304
-                    $old_row['id'],
305
-                    $this->_new_attendee_meta_table,
306
-                    $new_att_meta_id
307
-                );
308
-            }
309
-        }
310
-        $this->get_migration_script()->set_mapping(
311
-            $this->_old_table,
312
-            $old_row['id'],
313
-            $this->_new_attendee_cpt_table,
314
-            $new_att_id
315
-        );
316
-
317
-        $txn_id = $this->_insert_new_transaction($old_row);
318
-        if (! $txn_id) {
319
-            // if we couldn't make the transaction, also abandon all hope
320
-            return;
321
-        }
322
-        $this->get_migration_script()->set_mapping(
323
-            $this->_old_table,
324
-            $old_row['id'],
325
-            $this->_new_transaction_table,
326
-            $txn_id
327
-        );
328
-        $pay_id = $this->_insert_new_payment($old_row, $txn_id);
329
-        if ($pay_id) {
330
-            $this->get_migration_script()->set_mapping(
331
-                $this->_old_table,
332
-                $old_row['id'],
333
-                $this->_new_payment_table,
334
-                $pay_id
335
-            );
336
-        }
337
-
338
-
339
-        // even if there was no payment, we can go ahead with adding the reg
340
-        $new_regs = $this->_insert_new_registrations($old_row, $new_att_id, $txn_id);
341
-        if ($new_regs) {
342
-            $this->get_migration_script()->set_mapping(
343
-                $this->_old_table,
344
-                $old_row['id'],
345
-                $this->_new_reg_table,
346
-                $new_regs
347
-            );
348
-        }
349
-    }
350
-
351
-
352
-    /**
353
-     * Checks if there's already an attendee CPT in the db that has the same
354
-     * first and last name, and email. If so, returns its ID as an int.
355
-     *
356
-     * @param array $old_attendee
357
-     * @return int
358
-     * @global wpdb $wpdb
359
-     */
360
-    private function _find_attendee_cpt_matching(array $old_attendee): int
361
-    {
362
-        global $wpdb;
363
-        $existing_attendee_id =
364
-            $wpdb->get_var(
365
-                $wpdb->prepare(
366
-                    "SELECT id FROM " .
367
-                    $this->_new_attendee_cpt_table .
368
-                    " AS cpt INNER JOIN " .
369
-                    $this->_new_attendee_meta_table .
370
-                    " AS meta ON cpt.ID = meta.ATT_ID WHERE meta.ATT_fname = %s AND meta.ATT_lname = %s AND meta.ATT_email = %s LIMIT 1",
371
-                    $old_attendee['fname'],
372
-                    $old_attendee['lname'],
373
-                    $old_attendee['email']
374
-                )
375
-            );
376
-        return intval($existing_attendee_id);
377
-    }
378
-
379
-
380
-    private function _insert_new_attendee_cpt($old_attendee): int
381
-    {
382
-        global $wpdb;
383
-        $cols_n_values = [
384
-            'post_title'    => stripslashes($old_attendee['fname'] . " " . $old_attendee['lname']),// ATT_full_name
385
-            'post_content'  => '',// ATT_bio
386
-            'post_name'     => sanitize_title($old_attendee['fname'] . "-" . $old_attendee['lname']),// ATT_slug
387
-            'post_date'     => $this->get_migration_script()->convert_date_string_to_utc(
388
-                $this,
389
-                $old_attendee,
390
-                $old_attendee['date']
391
-            ),// ATT_created
392
-            'post_excerpt'  => '',// ATT_short_bio
393
-            'post_modified' => $this->get_migration_script()->convert_date_string_to_utc(
394
-                $this,
395
-                $old_attendee,
396
-                $old_attendee['date']
397
-            ),// ATT_modified
398
-            'post_author'   => 0,// ATT_author
399
-            'post_parent'   => 0,// ATT_parent
400
-            'post_type'     => EspressoPostType::ATTENDEES,
401
-            'post_status'   => 'publish',// status
402
-        ];
403
-        $data_types    = [
404
-            '%s',// ATT_full_name
405
-            '%s',// ATT_bio
406
-            '%s',// ATT_slug
407
-            '%s',// ATT_created
408
-            '%s',// ATT_short_bio
409
-            '%s',// ATT_modified
410
-            '%d',// ATT_author
411
-            '%d',// ATT_parent
412
-            '%s',// post_type
413
-            '%s',// status
414
-        ];
415
-        $success       = $wpdb->insert($this->_new_attendee_cpt_table, $cols_n_values, $data_types);
416
-        if (! $success) {
417
-            $this->add_error(
418
-                $this->get_migration_script()->_create_error_message_for_db_insertion(
419
-                    $this->_old_table,
420
-                    $old_attendee,
421
-                    $this->_new_attendee_cpt_table,
422
-                    $cols_n_values,
423
-                    $data_types
424
-                )
425
-            );
426
-            return 0;
427
-        }
428
-        return $wpdb->insert_id;
429
-    }
430
-
431
-
432
-    private function _insert_attendee_meta_row($old_attendee, $new_attendee_cpt_id): int
433
-    {
434
-        global $wpdb;
435
-        // get the state and country ids from the old row
436
-        try {
437
-            $new_country     =
438
-                $this->get_migration_script()->get_or_create_country(stripslashes($old_attendee['country_id']));
439
-            $new_country_iso = $new_country['CNT_ISO'];
440
-        } catch (EE_Error $exception) {
441
-            $new_country_iso = $this->get_migration_script()->get_default_country_iso();
442
-        }
443
-        try {
444
-            $new_state    =
445
-                $this->get_migration_script()->get_or_create_state(
446
-                    stripslashes($old_attendee['state']),
447
-                    $new_country_iso
448
-                );
449
-            $new_state_id = $new_state['STA_ID'];
450
-        } catch (EE_Error $exception) {
451
-            $new_state_id = 0;
452
-        }
453
-        $cols_n_values = [
454
-            'ATT_ID'       => $new_attendee_cpt_id,
455
-            'ATT_fname'    => stripslashes($old_attendee['fname']),
456
-            'ATT_lname'    => stripslashes($old_attendee['lname']),
457
-            'ATT_address'  => stripslashes($old_attendee['address']),
458
-            'ATT_address2' => stripslashes($old_attendee['address2']),
459
-            'ATT_city'     => stripslashes($old_attendee['city']),
460
-            'STA_ID'       => $new_state_id,
461
-            'CNT_ISO'      => $new_country_iso,
462
-            'ATT_zip'      => stripslashes($old_attendee['zip']),
463
-            'ATT_email'    => stripslashes($old_attendee['email']),
464
-            'ATT_phone'    => stripslashes($old_attendee['phone']),
465
-        ];
466
-        $data_types    = [
467
-            '%d',// ATT_ID
468
-            '%s',// ATT_fname
469
-            '%s',// ATT_lname
470
-            '%s',// ATT_address
471
-            '%s',// ATT_address2
472
-            '%s',// ATT_city
473
-            '%d',// STA_ID
474
-            '%s',// CNT_ISO
475
-            '%s',// ATT_zip
476
-            '%s',// ATT_email
477
-            '%s',// ATT_phone
478
-        ];
479
-        $success       = $wpdb->insert($this->_new_attendee_meta_table, $cols_n_values, $data_types);
480
-        if (! $success) {
481
-            $this->add_error(
482
-                $this->get_migration_script()->_create_error_message_for_db_insertion(
483
-                    $this->_old_table,
484
-                    $old_attendee,
485
-                    $this->_new_attendee_meta_table,
486
-                    $cols_n_values,
487
-                    $data_types
488
-                )
489
-            );
490
-            return 0;
491
-        }
492
-        return $wpdb->insert_id;
493
-    }
494
-
495
-
496
-    /**
497
-     * Note: we don't necessarily create a new transaction for each attendee row.
498
-     * Only if the old attendee 'is_primary' is true; otherwise we find the old attendee row that
499
-     * 'is_primary' and has the same 'txn_id', then we return ITS new transaction id
500
-     *
501
-     * @param array $old_attendee
502
-     * @return int new transaction id
503
-     * @throws EE_Error
504
-     * @global wpdb $wpdb
505
-     */
506
-    private function _insert_new_transaction(array $old_attendee): int
507
-    {
508
-        global $wpdb;
509
-
510
-        // first: let's check for an existing transaction for this old attendee
511
-        if (intval($old_attendee['is_primary'])) {// primary attendee, so create txn
512
-            $txn_id =
513
-                $this->get_migration_script()->get_mapping_new_pk(
514
-                    $this->_old_table,
515
-                    intval($old_attendee['id']),
516
-                    $this->_new_transaction_table
517
-                );
518
-        } else { // non-primary attendee, so find its primary attendee's transaction
519
-            $primary_attendee_old_id =
520
-                $wpdb->get_var(
521
-                    $wpdb->prepare(
522
-                        "SELECT id FROM " . $this->_old_table . " WHERE is_primary=1 and registration_id=%s",
523
-                        $old_attendee['registration_id']
524
-                    )
525
-                );
526
-            if (! $primary_attendee_old_id) {
527
-                $primary_attendee        =
528
-                    $this->_find_mer_primary_attendee_using_mer_tables($old_attendee['registration_id']);
529
-                $primary_attendee_old_id = is_array($primary_attendee) ? $primary_attendee['id'] : null;
530
-            }
531
-            $txn_id =
532
-                $this->get_migration_script()->get_mapping_new_pk(
533
-                    $this->_old_table,
534
-                    intval($primary_attendee_old_id),
535
-                    $this->_new_transaction_table
536
-                );
537
-            if (! $txn_id) {
538
-                $this->add_error(
539
-                    sprintf(
540
-                        esc_html__(
541
-                            "Could not find primary attendee's new transaction. Current attendee is: %s, we think the 3.1 primary attendee for it has id %d, but there's no 4.1 transaction for that primary attendee id.",
542
-                            "event_espresso"
543
-                        ),
544
-                        $this->_json_encode($old_attendee),
545
-                        $primary_attendee_old_id
546
-                    )
547
-                );
548
-                $txn_id = 0;
549
-            }
550
-        }
551
-        // if there isn't yet a transaction row for this, create one
552
-        // (so even if it was a non-primary attendee with no EE3 primary attendee,
553
-        // it ought to have SOME transaction, so we'll make one)
554
-        if (! $txn_id) {
555
-            // maps 3.1 payment stati onto 4.1 transaction stati
556
-            $txn_status_mapping = [
557
-                'Completed'        => 'TCM',
558
-                'Pending'          => 'TIN',
559
-                'Payment Declined' => 'TIN',
560
-                'Incomplete'       => 'TIN',
561
-                'Not Completed'    => 'TIN',
562
-                'Cancelled'        => 'TIN',
563
-                'Declined'         => 'TIN',
564
-            ];
565
-            $STS_ID             = $txn_status_mapping[ $old_attendee['payment_status'] ] ?? 'TIN';
566
-            $cols_n_values      = [
567
-                'TXN_timestamp' => $this->get_migration_script()->convert_date_string_to_utc(
568
-                    $this,
569
-                    $old_attendee,
570
-                    $old_attendee['date']
571
-                ),
572
-                'TXN_total'     => floatval($old_attendee['total_cost']),
573
-                'TXN_paid'      => floatval($old_attendee['amount_pd']),
574
-                'STS_ID'        => $STS_ID,
575
-                'TXN_hash_salt' => $old_attendee['hashSalt'],
576
-            ];
577
-            $data_types         = [
578
-                '%s',// TXN_timestamp
579
-                '%f',// TXN_total
580
-                '%f',// TXN_paid
581
-                '%s',// STS_ID
582
-                '%s',// TXN_hash_salt
583
-            ];
584
-            $success            = $wpdb->insert($this->_new_transaction_table, $cols_n_values, $data_types);
585
-            if (! $success) {
586
-                $this->add_error(
587
-                    $this->get_migration_script()->_create_error_message_for_db_insertion(
588
-                        $this->_old_table,
589
-                        $old_attendee,
590
-                        $this->_new_transaction_table,
591
-                        $cols_n_values,
592
-                        $data_types
593
-                    )
594
-                );
595
-                return 0;
596
-            }
597
-            $txn_id = $wpdb->insert_id;
598
-        }
599
-
600
-        return $txn_id;
601
-    }
602
-
603
-
604
-    /**
605
-     * Detects if the MER tables exist
606
-     *
607
-     * @return boolean
608
-     * @global wpdb $wpdb
609
-     */
610
-    private function _mer_tables_exist(): ?bool
611
-    {
612
-        if ($this->_mer_tables_exist === null) {
613
-            global $wpdb;
614
-
615
-            if ($wpdb->get_var("SHOW TABLES LIKE '$this->_old_mer_table'") != $this->_old_mer_table) {
616
-                $this->_mer_tables_exist = false;
617
-            } else {
618
-                $this->_mer_tables_exist = true;
619
-            }
620
-        }
621
-        return $this->_mer_tables_exist;
622
-    }
623
-
624
-
625
-    /**
626
-     * Gets the 4.1 registration's status given the 3.1 attendee row. We consider
627
-     * whether the event required pre-approval or not,a dn the 4.1 payment status.
628
-     *
629
-     * @param array $old_attendee_row
630
-     * @return string
631
-     * @global wpdb $wpdb
632
-     */
633
-    private function _get_reg_status_for_old_payment_status(array $old_attendee_row): string
634
-    {
635
-        // need event default reg status and if pre_approval was required
636
-        global $wpdb;
637
-        $event_required_pre_approval =
638
-            $wpdb->get_var(
639
-                $wpdb->prepare(
640
-                    "SELECT require_pre_approval FROM " . $wpdb->prefix . "events_detail WHERE id = %d",
641
-                    $old_attendee_row['event_id']
642
-                )
643
-            );
644
-        return $this->get_migration_script()->convert_3_1_payment_status_to_4_1_STS_ID(
645
-            $old_attendee_row['payment_status'],
646
-            intval($event_required_pre_approval) && intval($old_attendee_row['pre_approve'])
647
-        );
648
-    }
649
-
650
-
651
-    /**
652
-     * Adds however many registrations are indicated by the old attendee's QUANTITY field,
653
-     * and returns an array of their IDs
654
-     *
655
-     * @param array $old_attendee
656
-     * @param int   $new_attendee_id
657
-     * @param int   $new_txn_id
658
-     * @return array of new registration ids
659
-     * @throws EE_Error
660
-     * @global wpdb $wpdb
661
-     */
662
-    private function _insert_new_registrations(array $old_attendee, int $new_attendee_id, int $new_txn_id): array
663
-    {
664
-        global $wpdb;
665
-
666
-        $STS_ID       = $this->_get_reg_status_for_old_payment_status($old_attendee);
667
-        $new_event_id =
668
-            $this->get_migration_script()->get_mapping_new_pk(
669
-                $wpdb->prefix . 'events_detail',
670
-                $old_attendee['event_id'],
671
-                $wpdb->posts
672
-            );
673
-        if (! $new_event_id) {
674
-            $this->add_error(
675
-                sprintf(
676
-                    esc_html__(
677
-                        "Could not find NEW event CPT ID for old event '%d' on old attendee %s",
678
-                        "event_espresso"
679
-                    ),
680
-                    $old_attendee['event_id'],
681
-                    $this->_json_encode($old_attendee)
682
-                )
683
-            );
684
-        }
685
-
686
-        $ticket_id = $this->_try_to_find_new_ticket_id($old_attendee, $new_event_id);
687
-        if (! $ticket_id) {
688
-            $ticket_id = $this->_insert_new_ticket_because_none_found($old_attendee, $new_event_id);
689
-            $this->add_error(
690
-                sprintf(
691
-                    esc_html__(
692
-                        'Could not find a ticket for old attendee with id %d for new event %d, so created a new ticket with id %d',
693
-                        'event_espresso'
694
-                    ),
695
-                    $old_attendee['id'],
696
-                    $new_event_id,
697
-                    $ticket_id
698
-                )
699
-            );
700
-        }
701
-        $regs_on_this_row = intval($old_attendee['quantity']);
702
-        $new_regs         = [];
703
-        // 4 cases we need to account for:
704
-        // 1 old attendee_details row with a quantity of X (no mer)
705
-        // Y old attendee_details rows with a quantity of 1 (no mer) joined by their common registration_id
706
-        // Y old attendee_details rows with a quantity of x (because of mer)
707
-        // Y old attendee_details rows with a quantity of 1 (because of mer) joined by wp_events_multi_event_registration_id_group
708
-        for ($count = 1; $count <= $regs_on_this_row; $count++) {
709
-            // sum regs on older rows
710
-            $regs_on_this_event_and_txn = $this->_sum_old_attendees_on_old_txn($old_attendee, true);
711
-            $cols_n_values              = [
712
-                'EVT_ID'           => $new_event_id,
713
-                'ATT_ID'           => $new_attendee_id,
714
-                'TXN_ID'           => $new_txn_id,
715
-                'TKT_ID'           => $ticket_id,
716
-                'STS_ID'           => $STS_ID,
717
-                'REG_date'         => $this->get_migration_script()->convert_date_string_to_utc(
718
-                    $this,
719
-                    $old_attendee,
720
-                    $old_attendee['date']
721
-                ),
722
-                'REG_final_price'  => $old_attendee['final_price'],
723
-                'REG_session'      => substr($old_attendee['attendee_session'], 0, 44),
724
-                'REG_code'         => sanitize_key($old_attendee['registration_id']),
725
-                'REG_url_link'     => sanitize_key($old_attendee['registration_id'] . '-' . $count),
726
-                'REG_count'        => $regs_on_this_event_and_txn + $count,
727
-                'REG_group_size'   => $this->_sum_old_attendees_on_old_txn($old_attendee),
728
-                'REG_att_is_going' => true,
729
-                'REG_deleted'      => false,
730
-            ];
731
-            $data_types                 = [
732
-                '%d',// EVT_ID
733
-                '%d',// ATT_ID
734
-                '%d',// TXN_ID
735
-                '%d',// TKT_ID
736
-                '%s',// STS_ID
737
-                '%s',// REG_date
738
-                '%f',// REG_final_price
739
-                '%s',// REG_session
740
-                '%s',// REG_code
741
-                '%s',// REG_url_link
742
-                '%d',// REG_count
743
-                '%d',// REG_group_size
744
-                '%d',// REG_att_is_going
745
-                '%d',// REG_deleted
746
-            ];
747
-            $success                    = $wpdb->insert($this->_new_reg_table, $cols_n_values, $data_types);
748
-            if (! $success) {
749
-                $this->add_error(
750
-                    $this->get_migration_script()->_create_error_message_for_db_insertion(
751
-                        $this->_old_table,
752
-                        $old_attendee,
753
-                        $this->_new_reg_table,
754
-                        $cols_n_values,
755
-                        $data_types
756
-                    )
757
-                );
758
-                return [];
759
-            }
760
-            $cols_n_values['REG_ID'] = $wpdb->insert_id;
761
-            $new_regs[]              = $wpdb->insert_id;
762
-        }
763
-        $this->_add_regs_to_ticket_and_datetimes($ticket_id, count($new_regs), $STS_ID);
764
-        return $new_regs;
765
-    }
766
-
767
-
768
-    /**
769
-     * Increments the sold values on the ticket and its related datetimes by the amount sold,
770
-     * which should be done directly after adding the rows. Yes this means we're constantly incrementing
771
-     * the sold amounts as we go, and is less efficient than a single big query,
772
-     * but it's safer because we KNOW these regs have been added, rather than inferring
773
-     * that they WILL be added (because the attendees stage runs nearly last during
774
-     * the migration script)
775
-     *
776
-     * @param int    $new_ticket_id
777
-     * @param int    $quantity_sold
778
-     * @param string $STS_ID
779
-     * @return boolean whether they were successfully updated or not
780
-     */
781
-    protected function _add_regs_to_ticket_and_datetimes(int $new_ticket_id, int $quantity_sold, string $STS_ID): bool
782
-    {
783
-        if ($STS_ID != 'RAP') {
784
-            return true;
785
-        }
786
-        global $wpdb;
787
-        $success =
788
-            $wpdb->query(
789
-                $wpdb->prepare(
790
-                    "UPDATE $this->_new_ticket_table SET TKT_sold=TKT_sold+%d WHERE TKT_ID=%d",
791
-                    $quantity_sold,
792
-                    $new_ticket_id
793
-                )
794
-            );
795
-        if ($success) {
796
-            // get the ticket's datetimes, and increment them too
797
-            $success_update_datetimes = $wpdb->query(
798
-                $wpdb->prepare(
799
-                    "UPDATE $this->_new_ticket_table TKT
271
+		$this->_old_mer_table             = $wpdb->prefix . "events_multi_event_registration_id_group";
272
+		$this->_new_attendee_cpt_table    = $wpdb->posts;
273
+		$this->_new_attendee_meta_table   = $wpdb->prefix . "esp_attendee_meta";
274
+		$this->_new_reg_table             = $wpdb->prefix . "esp_registration";
275
+		$this->_new_transaction_table     = $wpdb->prefix . "esp_transaction";
276
+		$this->_new_payment_table         = $wpdb->prefix . "esp_payment";
277
+		$this->_new_ticket_table          = $wpdb->prefix . "esp_ticket";
278
+		$this->_new_ticket_datetime_table = $wpdb->prefix . "esp_datetime_ticket";
279
+		$this->_new_datetime_table        = $wpdb->prefix . "esp_datetime";
280
+		$this->_new_datetime_ticket_table = $wpdb->prefix . "esp_datetime_ticket";
281
+		$this->_new_price_table           = $wpdb->prefix . "esp_price";
282
+		$this->_new_ticket_price_table    = $wpdb->prefix . "esp_ticket_price";
283
+		parent::__construct();
284
+	}
285
+
286
+
287
+	/**
288
+	 * @throws EE_Error
289
+	 */
290
+	protected function _migrate_old_row($old_row)
291
+	{
292
+		// first check if there's already a new attendee with similar characteristics
293
+		$new_att_id = $this->_find_attendee_cpt_matching($old_row);
294
+		if (! $new_att_id) {
295
+			$new_att_id = $this->_insert_new_attendee_cpt($old_row);
296
+			if (! $new_att_id) {
297
+				// if we couldn't even make an attendee, abandon all hope
298
+				return;
299
+			}
300
+			$new_att_meta_id = $this->_insert_attendee_meta_row($old_row, $new_att_id);
301
+			if ($new_att_meta_id) {
302
+				$this->get_migration_script()->set_mapping(
303
+					$this->_old_table,
304
+					$old_row['id'],
305
+					$this->_new_attendee_meta_table,
306
+					$new_att_meta_id
307
+				);
308
+			}
309
+		}
310
+		$this->get_migration_script()->set_mapping(
311
+			$this->_old_table,
312
+			$old_row['id'],
313
+			$this->_new_attendee_cpt_table,
314
+			$new_att_id
315
+		);
316
+
317
+		$txn_id = $this->_insert_new_transaction($old_row);
318
+		if (! $txn_id) {
319
+			// if we couldn't make the transaction, also abandon all hope
320
+			return;
321
+		}
322
+		$this->get_migration_script()->set_mapping(
323
+			$this->_old_table,
324
+			$old_row['id'],
325
+			$this->_new_transaction_table,
326
+			$txn_id
327
+		);
328
+		$pay_id = $this->_insert_new_payment($old_row, $txn_id);
329
+		if ($pay_id) {
330
+			$this->get_migration_script()->set_mapping(
331
+				$this->_old_table,
332
+				$old_row['id'],
333
+				$this->_new_payment_table,
334
+				$pay_id
335
+			);
336
+		}
337
+
338
+
339
+		// even if there was no payment, we can go ahead with adding the reg
340
+		$new_regs = $this->_insert_new_registrations($old_row, $new_att_id, $txn_id);
341
+		if ($new_regs) {
342
+			$this->get_migration_script()->set_mapping(
343
+				$this->_old_table,
344
+				$old_row['id'],
345
+				$this->_new_reg_table,
346
+				$new_regs
347
+			);
348
+		}
349
+	}
350
+
351
+
352
+	/**
353
+	 * Checks if there's already an attendee CPT in the db that has the same
354
+	 * first and last name, and email. If so, returns its ID as an int.
355
+	 *
356
+	 * @param array $old_attendee
357
+	 * @return int
358
+	 * @global wpdb $wpdb
359
+	 */
360
+	private function _find_attendee_cpt_matching(array $old_attendee): int
361
+	{
362
+		global $wpdb;
363
+		$existing_attendee_id =
364
+			$wpdb->get_var(
365
+				$wpdb->prepare(
366
+					"SELECT id FROM " .
367
+					$this->_new_attendee_cpt_table .
368
+					" AS cpt INNER JOIN " .
369
+					$this->_new_attendee_meta_table .
370
+					" AS meta ON cpt.ID = meta.ATT_ID WHERE meta.ATT_fname = %s AND meta.ATT_lname = %s AND meta.ATT_email = %s LIMIT 1",
371
+					$old_attendee['fname'],
372
+					$old_attendee['lname'],
373
+					$old_attendee['email']
374
+				)
375
+			);
376
+		return intval($existing_attendee_id);
377
+	}
378
+
379
+
380
+	private function _insert_new_attendee_cpt($old_attendee): int
381
+	{
382
+		global $wpdb;
383
+		$cols_n_values = [
384
+			'post_title'    => stripslashes($old_attendee['fname'] . " " . $old_attendee['lname']),// ATT_full_name
385
+			'post_content'  => '',// ATT_bio
386
+			'post_name'     => sanitize_title($old_attendee['fname'] . "-" . $old_attendee['lname']),// ATT_slug
387
+			'post_date'     => $this->get_migration_script()->convert_date_string_to_utc(
388
+				$this,
389
+				$old_attendee,
390
+				$old_attendee['date']
391
+			),// ATT_created
392
+			'post_excerpt'  => '',// ATT_short_bio
393
+			'post_modified' => $this->get_migration_script()->convert_date_string_to_utc(
394
+				$this,
395
+				$old_attendee,
396
+				$old_attendee['date']
397
+			),// ATT_modified
398
+			'post_author'   => 0,// ATT_author
399
+			'post_parent'   => 0,// ATT_parent
400
+			'post_type'     => EspressoPostType::ATTENDEES,
401
+			'post_status'   => 'publish',// status
402
+		];
403
+		$data_types    = [
404
+			'%s',// ATT_full_name
405
+			'%s',// ATT_bio
406
+			'%s',// ATT_slug
407
+			'%s',// ATT_created
408
+			'%s',// ATT_short_bio
409
+			'%s',// ATT_modified
410
+			'%d',// ATT_author
411
+			'%d',// ATT_parent
412
+			'%s',// post_type
413
+			'%s',// status
414
+		];
415
+		$success       = $wpdb->insert($this->_new_attendee_cpt_table, $cols_n_values, $data_types);
416
+		if (! $success) {
417
+			$this->add_error(
418
+				$this->get_migration_script()->_create_error_message_for_db_insertion(
419
+					$this->_old_table,
420
+					$old_attendee,
421
+					$this->_new_attendee_cpt_table,
422
+					$cols_n_values,
423
+					$data_types
424
+				)
425
+			);
426
+			return 0;
427
+		}
428
+		return $wpdb->insert_id;
429
+	}
430
+
431
+
432
+	private function _insert_attendee_meta_row($old_attendee, $new_attendee_cpt_id): int
433
+	{
434
+		global $wpdb;
435
+		// get the state and country ids from the old row
436
+		try {
437
+			$new_country     =
438
+				$this->get_migration_script()->get_or_create_country(stripslashes($old_attendee['country_id']));
439
+			$new_country_iso = $new_country['CNT_ISO'];
440
+		} catch (EE_Error $exception) {
441
+			$new_country_iso = $this->get_migration_script()->get_default_country_iso();
442
+		}
443
+		try {
444
+			$new_state    =
445
+				$this->get_migration_script()->get_or_create_state(
446
+					stripslashes($old_attendee['state']),
447
+					$new_country_iso
448
+				);
449
+			$new_state_id = $new_state['STA_ID'];
450
+		} catch (EE_Error $exception) {
451
+			$new_state_id = 0;
452
+		}
453
+		$cols_n_values = [
454
+			'ATT_ID'       => $new_attendee_cpt_id,
455
+			'ATT_fname'    => stripslashes($old_attendee['fname']),
456
+			'ATT_lname'    => stripslashes($old_attendee['lname']),
457
+			'ATT_address'  => stripslashes($old_attendee['address']),
458
+			'ATT_address2' => stripslashes($old_attendee['address2']),
459
+			'ATT_city'     => stripslashes($old_attendee['city']),
460
+			'STA_ID'       => $new_state_id,
461
+			'CNT_ISO'      => $new_country_iso,
462
+			'ATT_zip'      => stripslashes($old_attendee['zip']),
463
+			'ATT_email'    => stripslashes($old_attendee['email']),
464
+			'ATT_phone'    => stripslashes($old_attendee['phone']),
465
+		];
466
+		$data_types    = [
467
+			'%d',// ATT_ID
468
+			'%s',// ATT_fname
469
+			'%s',// ATT_lname
470
+			'%s',// ATT_address
471
+			'%s',// ATT_address2
472
+			'%s',// ATT_city
473
+			'%d',// STA_ID
474
+			'%s',// CNT_ISO
475
+			'%s',// ATT_zip
476
+			'%s',// ATT_email
477
+			'%s',// ATT_phone
478
+		];
479
+		$success       = $wpdb->insert($this->_new_attendee_meta_table, $cols_n_values, $data_types);
480
+		if (! $success) {
481
+			$this->add_error(
482
+				$this->get_migration_script()->_create_error_message_for_db_insertion(
483
+					$this->_old_table,
484
+					$old_attendee,
485
+					$this->_new_attendee_meta_table,
486
+					$cols_n_values,
487
+					$data_types
488
+				)
489
+			);
490
+			return 0;
491
+		}
492
+		return $wpdb->insert_id;
493
+	}
494
+
495
+
496
+	/**
497
+	 * Note: we don't necessarily create a new transaction for each attendee row.
498
+	 * Only if the old attendee 'is_primary' is true; otherwise we find the old attendee row that
499
+	 * 'is_primary' and has the same 'txn_id', then we return ITS new transaction id
500
+	 *
501
+	 * @param array $old_attendee
502
+	 * @return int new transaction id
503
+	 * @throws EE_Error
504
+	 * @global wpdb $wpdb
505
+	 */
506
+	private function _insert_new_transaction(array $old_attendee): int
507
+	{
508
+		global $wpdb;
509
+
510
+		// first: let's check for an existing transaction for this old attendee
511
+		if (intval($old_attendee['is_primary'])) {// primary attendee, so create txn
512
+			$txn_id =
513
+				$this->get_migration_script()->get_mapping_new_pk(
514
+					$this->_old_table,
515
+					intval($old_attendee['id']),
516
+					$this->_new_transaction_table
517
+				);
518
+		} else { // non-primary attendee, so find its primary attendee's transaction
519
+			$primary_attendee_old_id =
520
+				$wpdb->get_var(
521
+					$wpdb->prepare(
522
+						"SELECT id FROM " . $this->_old_table . " WHERE is_primary=1 and registration_id=%s",
523
+						$old_attendee['registration_id']
524
+					)
525
+				);
526
+			if (! $primary_attendee_old_id) {
527
+				$primary_attendee        =
528
+					$this->_find_mer_primary_attendee_using_mer_tables($old_attendee['registration_id']);
529
+				$primary_attendee_old_id = is_array($primary_attendee) ? $primary_attendee['id'] : null;
530
+			}
531
+			$txn_id =
532
+				$this->get_migration_script()->get_mapping_new_pk(
533
+					$this->_old_table,
534
+					intval($primary_attendee_old_id),
535
+					$this->_new_transaction_table
536
+				);
537
+			if (! $txn_id) {
538
+				$this->add_error(
539
+					sprintf(
540
+						esc_html__(
541
+							"Could not find primary attendee's new transaction. Current attendee is: %s, we think the 3.1 primary attendee for it has id %d, but there's no 4.1 transaction for that primary attendee id.",
542
+							"event_espresso"
543
+						),
544
+						$this->_json_encode($old_attendee),
545
+						$primary_attendee_old_id
546
+					)
547
+				);
548
+				$txn_id = 0;
549
+			}
550
+		}
551
+		// if there isn't yet a transaction row for this, create one
552
+		// (so even if it was a non-primary attendee with no EE3 primary attendee,
553
+		// it ought to have SOME transaction, so we'll make one)
554
+		if (! $txn_id) {
555
+			// maps 3.1 payment stati onto 4.1 transaction stati
556
+			$txn_status_mapping = [
557
+				'Completed'        => 'TCM',
558
+				'Pending'          => 'TIN',
559
+				'Payment Declined' => 'TIN',
560
+				'Incomplete'       => 'TIN',
561
+				'Not Completed'    => 'TIN',
562
+				'Cancelled'        => 'TIN',
563
+				'Declined'         => 'TIN',
564
+			];
565
+			$STS_ID             = $txn_status_mapping[ $old_attendee['payment_status'] ] ?? 'TIN';
566
+			$cols_n_values      = [
567
+				'TXN_timestamp' => $this->get_migration_script()->convert_date_string_to_utc(
568
+					$this,
569
+					$old_attendee,
570
+					$old_attendee['date']
571
+				),
572
+				'TXN_total'     => floatval($old_attendee['total_cost']),
573
+				'TXN_paid'      => floatval($old_attendee['amount_pd']),
574
+				'STS_ID'        => $STS_ID,
575
+				'TXN_hash_salt' => $old_attendee['hashSalt'],
576
+			];
577
+			$data_types         = [
578
+				'%s',// TXN_timestamp
579
+				'%f',// TXN_total
580
+				'%f',// TXN_paid
581
+				'%s',// STS_ID
582
+				'%s',// TXN_hash_salt
583
+			];
584
+			$success            = $wpdb->insert($this->_new_transaction_table, $cols_n_values, $data_types);
585
+			if (! $success) {
586
+				$this->add_error(
587
+					$this->get_migration_script()->_create_error_message_for_db_insertion(
588
+						$this->_old_table,
589
+						$old_attendee,
590
+						$this->_new_transaction_table,
591
+						$cols_n_values,
592
+						$data_types
593
+					)
594
+				);
595
+				return 0;
596
+			}
597
+			$txn_id = $wpdb->insert_id;
598
+		}
599
+
600
+		return $txn_id;
601
+	}
602
+
603
+
604
+	/**
605
+	 * Detects if the MER tables exist
606
+	 *
607
+	 * @return boolean
608
+	 * @global wpdb $wpdb
609
+	 */
610
+	private function _mer_tables_exist(): ?bool
611
+	{
612
+		if ($this->_mer_tables_exist === null) {
613
+			global $wpdb;
614
+
615
+			if ($wpdb->get_var("SHOW TABLES LIKE '$this->_old_mer_table'") != $this->_old_mer_table) {
616
+				$this->_mer_tables_exist = false;
617
+			} else {
618
+				$this->_mer_tables_exist = true;
619
+			}
620
+		}
621
+		return $this->_mer_tables_exist;
622
+	}
623
+
624
+
625
+	/**
626
+	 * Gets the 4.1 registration's status given the 3.1 attendee row. We consider
627
+	 * whether the event required pre-approval or not,a dn the 4.1 payment status.
628
+	 *
629
+	 * @param array $old_attendee_row
630
+	 * @return string
631
+	 * @global wpdb $wpdb
632
+	 */
633
+	private function _get_reg_status_for_old_payment_status(array $old_attendee_row): string
634
+	{
635
+		// need event default reg status and if pre_approval was required
636
+		global $wpdb;
637
+		$event_required_pre_approval =
638
+			$wpdb->get_var(
639
+				$wpdb->prepare(
640
+					"SELECT require_pre_approval FROM " . $wpdb->prefix . "events_detail WHERE id = %d",
641
+					$old_attendee_row['event_id']
642
+				)
643
+			);
644
+		return $this->get_migration_script()->convert_3_1_payment_status_to_4_1_STS_ID(
645
+			$old_attendee_row['payment_status'],
646
+			intval($event_required_pre_approval) && intval($old_attendee_row['pre_approve'])
647
+		);
648
+	}
649
+
650
+
651
+	/**
652
+	 * Adds however many registrations are indicated by the old attendee's QUANTITY field,
653
+	 * and returns an array of their IDs
654
+	 *
655
+	 * @param array $old_attendee
656
+	 * @param int   $new_attendee_id
657
+	 * @param int   $new_txn_id
658
+	 * @return array of new registration ids
659
+	 * @throws EE_Error
660
+	 * @global wpdb $wpdb
661
+	 */
662
+	private function _insert_new_registrations(array $old_attendee, int $new_attendee_id, int $new_txn_id): array
663
+	{
664
+		global $wpdb;
665
+
666
+		$STS_ID       = $this->_get_reg_status_for_old_payment_status($old_attendee);
667
+		$new_event_id =
668
+			$this->get_migration_script()->get_mapping_new_pk(
669
+				$wpdb->prefix . 'events_detail',
670
+				$old_attendee['event_id'],
671
+				$wpdb->posts
672
+			);
673
+		if (! $new_event_id) {
674
+			$this->add_error(
675
+				sprintf(
676
+					esc_html__(
677
+						"Could not find NEW event CPT ID for old event '%d' on old attendee %s",
678
+						"event_espresso"
679
+					),
680
+					$old_attendee['event_id'],
681
+					$this->_json_encode($old_attendee)
682
+				)
683
+			);
684
+		}
685
+
686
+		$ticket_id = $this->_try_to_find_new_ticket_id($old_attendee, $new_event_id);
687
+		if (! $ticket_id) {
688
+			$ticket_id = $this->_insert_new_ticket_because_none_found($old_attendee, $new_event_id);
689
+			$this->add_error(
690
+				sprintf(
691
+					esc_html__(
692
+						'Could not find a ticket for old attendee with id %d for new event %d, so created a new ticket with id %d',
693
+						'event_espresso'
694
+					),
695
+					$old_attendee['id'],
696
+					$new_event_id,
697
+					$ticket_id
698
+				)
699
+			);
700
+		}
701
+		$regs_on_this_row = intval($old_attendee['quantity']);
702
+		$new_regs         = [];
703
+		// 4 cases we need to account for:
704
+		// 1 old attendee_details row with a quantity of X (no mer)
705
+		// Y old attendee_details rows with a quantity of 1 (no mer) joined by their common registration_id
706
+		// Y old attendee_details rows with a quantity of x (because of mer)
707
+		// Y old attendee_details rows with a quantity of 1 (because of mer) joined by wp_events_multi_event_registration_id_group
708
+		for ($count = 1; $count <= $regs_on_this_row; $count++) {
709
+			// sum regs on older rows
710
+			$regs_on_this_event_and_txn = $this->_sum_old_attendees_on_old_txn($old_attendee, true);
711
+			$cols_n_values              = [
712
+				'EVT_ID'           => $new_event_id,
713
+				'ATT_ID'           => $new_attendee_id,
714
+				'TXN_ID'           => $new_txn_id,
715
+				'TKT_ID'           => $ticket_id,
716
+				'STS_ID'           => $STS_ID,
717
+				'REG_date'         => $this->get_migration_script()->convert_date_string_to_utc(
718
+					$this,
719
+					$old_attendee,
720
+					$old_attendee['date']
721
+				),
722
+				'REG_final_price'  => $old_attendee['final_price'],
723
+				'REG_session'      => substr($old_attendee['attendee_session'], 0, 44),
724
+				'REG_code'         => sanitize_key($old_attendee['registration_id']),
725
+				'REG_url_link'     => sanitize_key($old_attendee['registration_id'] . '-' . $count),
726
+				'REG_count'        => $regs_on_this_event_and_txn + $count,
727
+				'REG_group_size'   => $this->_sum_old_attendees_on_old_txn($old_attendee),
728
+				'REG_att_is_going' => true,
729
+				'REG_deleted'      => false,
730
+			];
731
+			$data_types                 = [
732
+				'%d',// EVT_ID
733
+				'%d',// ATT_ID
734
+				'%d',// TXN_ID
735
+				'%d',// TKT_ID
736
+				'%s',// STS_ID
737
+				'%s',// REG_date
738
+				'%f',// REG_final_price
739
+				'%s',// REG_session
740
+				'%s',// REG_code
741
+				'%s',// REG_url_link
742
+				'%d',// REG_count
743
+				'%d',// REG_group_size
744
+				'%d',// REG_att_is_going
745
+				'%d',// REG_deleted
746
+			];
747
+			$success                    = $wpdb->insert($this->_new_reg_table, $cols_n_values, $data_types);
748
+			if (! $success) {
749
+				$this->add_error(
750
+					$this->get_migration_script()->_create_error_message_for_db_insertion(
751
+						$this->_old_table,
752
+						$old_attendee,
753
+						$this->_new_reg_table,
754
+						$cols_n_values,
755
+						$data_types
756
+					)
757
+				);
758
+				return [];
759
+			}
760
+			$cols_n_values['REG_ID'] = $wpdb->insert_id;
761
+			$new_regs[]              = $wpdb->insert_id;
762
+		}
763
+		$this->_add_regs_to_ticket_and_datetimes($ticket_id, count($new_regs), $STS_ID);
764
+		return $new_regs;
765
+	}
766
+
767
+
768
+	/**
769
+	 * Increments the sold values on the ticket and its related datetimes by the amount sold,
770
+	 * which should be done directly after adding the rows. Yes this means we're constantly incrementing
771
+	 * the sold amounts as we go, and is less efficient than a single big query,
772
+	 * but it's safer because we KNOW these regs have been added, rather than inferring
773
+	 * that they WILL be added (because the attendees stage runs nearly last during
774
+	 * the migration script)
775
+	 *
776
+	 * @param int    $new_ticket_id
777
+	 * @param int    $quantity_sold
778
+	 * @param string $STS_ID
779
+	 * @return boolean whether they were successfully updated or not
780
+	 */
781
+	protected function _add_regs_to_ticket_and_datetimes(int $new_ticket_id, int $quantity_sold, string $STS_ID): bool
782
+	{
783
+		if ($STS_ID != 'RAP') {
784
+			return true;
785
+		}
786
+		global $wpdb;
787
+		$success =
788
+			$wpdb->query(
789
+				$wpdb->prepare(
790
+					"UPDATE $this->_new_ticket_table SET TKT_sold=TKT_sold+%d WHERE TKT_ID=%d",
791
+					$quantity_sold,
792
+					$new_ticket_id
793
+				)
794
+			);
795
+		if ($success) {
796
+			// get the ticket's datetimes, and increment them too
797
+			$success_update_datetimes = $wpdb->query(
798
+				$wpdb->prepare(
799
+					"UPDATE $this->_new_ticket_table TKT
800 800
 				INNER JOIN $this->_new_ticket_datetime_table as DTK ON TKT.TKT_ID = DTK.TKT_ID
801 801
 				INNER JOIN $this->_new_datetime_table as DTT ON DTK.DTT_ID = DTT.DTT_ID
802 802
 				SET DTT.DTT_sold = DTT.DTT_sold + %d WHERE TKT.TKT_ID = %d",
803
-                    $quantity_sold,
804
-                    $new_ticket_id
805
-                )
806
-            );
807
-            if (! $success_update_datetimes) {
808
-                $this->add_error(
809
-                    sprintf(
810
-                        esc_html__(
811
-                            "Could not update datetimes related to ticket with ID %d's TKT_sold by %d because %s",
812
-                            "event_espresso"
813
-                        ),
814
-                        $new_ticket_id,
815
-                        $quantity_sold,
816
-                        $wpdb->last_error
817
-                    )
818
-                );
819
-            }
820
-        } else {
821
-            $this->add_error(
822
-                sprintf(
823
-                    esc_html__("Could not update ticket with ID %d's TKT_sold by %d because %s", "event_espresso"),
824
-                    $new_ticket_id,
825
-                    $quantity_sold,
826
-                    $wpdb->last_error
827
-                )
828
-            );
829
-        }
830
-        return true;
831
-    }
832
-
833
-
834
-    /**
835
-     * Makes a guess at which ticket is the one the attendee purchased.
836
-     * Obviously, the old attendee's event_id narrows it down quite a bit;
837
-     * then the old attendee's orig_price and event_time, and price_option can uniquely identify the ticket
838
-     * however, if we don't find an exact match, see if any of those conditions match;
839
-     * and lastly if none of that works, just use the first ticket for the event we find
840
-     *
841
-     * @param array $old_attendee
842
-     * @param       $new_event_id
843
-     * @return string|null
844
-     */
845
-    private function _try_to_find_new_ticket_id(array $old_attendee, $new_event_id): ?string
846
-    {
847
-        global $wpdb;
848
-        $tickets_table          = $this->_new_ticket_table;
849
-        $datetime_tickets_table = $this->_new_ticket_datetime_table;
850
-        $datetime_table         = $this->_new_datetime_table;
851
-
852
-        $old_att_price_option = $old_attendee['price_option'];
853
-        $old_att_price        = floatval($old_attendee['orig_price']);
854
-
855
-        $old_att_start_date = $old_attendee['start_date'];
856
-        $old_att_start_time = $this->get_migration_script()->convertTimeFromAMPM($old_attendee['event_time']);
857
-        $old_att_datetime   =
858
-            $this->get_migration_script()->convert_date_string_to_utc(
859
-                $this,
860
-                $old_attendee,
861
-                "$old_att_start_date $old_att_start_time:00"
862
-            );
863
-        // add all conditions to an array from which we can SHIFT conditions off in order to widen our search
864
-        // the most important condition should be last, as it will be array_shift'ed off last
865
-        $conditions           = [
866
-            $wpdb->prepare("$datetime_table.DTT_EVT_start = %s", $old_att_datetime),// times match?
867
-            $wpdb->prepare("$tickets_table.TKT_price = %f", $old_att_price),// prices match?
868
-            $wpdb->prepare("$tickets_table.TKT_name = %s", $old_att_price_option),// names match?
869
-            $wpdb->prepare("$datetime_table.EVT_ID = %d", $new_event_id),// events match?
870
-        ];
871
-        $select_and_join_part = "SELECT $tickets_table.TKT_ID FROM $tickets_table INNER JOIN
803
+					$quantity_sold,
804
+					$new_ticket_id
805
+				)
806
+			);
807
+			if (! $success_update_datetimes) {
808
+				$this->add_error(
809
+					sprintf(
810
+						esc_html__(
811
+							"Could not update datetimes related to ticket with ID %d's TKT_sold by %d because %s",
812
+							"event_espresso"
813
+						),
814
+						$new_ticket_id,
815
+						$quantity_sold,
816
+						$wpdb->last_error
817
+					)
818
+				);
819
+			}
820
+		} else {
821
+			$this->add_error(
822
+				sprintf(
823
+					esc_html__("Could not update ticket with ID %d's TKT_sold by %d because %s", "event_espresso"),
824
+					$new_ticket_id,
825
+					$quantity_sold,
826
+					$wpdb->last_error
827
+				)
828
+			);
829
+		}
830
+		return true;
831
+	}
832
+
833
+
834
+	/**
835
+	 * Makes a guess at which ticket is the one the attendee purchased.
836
+	 * Obviously, the old attendee's event_id narrows it down quite a bit;
837
+	 * then the old attendee's orig_price and event_time, and price_option can uniquely identify the ticket
838
+	 * however, if we don't find an exact match, see if any of those conditions match;
839
+	 * and lastly if none of that works, just use the first ticket for the event we find
840
+	 *
841
+	 * @param array $old_attendee
842
+	 * @param       $new_event_id
843
+	 * @return string|null
844
+	 */
845
+	private function _try_to_find_new_ticket_id(array $old_attendee, $new_event_id): ?string
846
+	{
847
+		global $wpdb;
848
+		$tickets_table          = $this->_new_ticket_table;
849
+		$datetime_tickets_table = $this->_new_ticket_datetime_table;
850
+		$datetime_table         = $this->_new_datetime_table;
851
+
852
+		$old_att_price_option = $old_attendee['price_option'];
853
+		$old_att_price        = floatval($old_attendee['orig_price']);
854
+
855
+		$old_att_start_date = $old_attendee['start_date'];
856
+		$old_att_start_time = $this->get_migration_script()->convertTimeFromAMPM($old_attendee['event_time']);
857
+		$old_att_datetime   =
858
+			$this->get_migration_script()->convert_date_string_to_utc(
859
+				$this,
860
+				$old_attendee,
861
+				"$old_att_start_date $old_att_start_time:00"
862
+			);
863
+		// add all conditions to an array from which we can SHIFT conditions off in order to widen our search
864
+		// the most important condition should be last, as it will be array_shift'ed off last
865
+		$conditions           = [
866
+			$wpdb->prepare("$datetime_table.DTT_EVT_start = %s", $old_att_datetime),// times match?
867
+			$wpdb->prepare("$tickets_table.TKT_price = %f", $old_att_price),// prices match?
868
+			$wpdb->prepare("$tickets_table.TKT_name = %s", $old_att_price_option),// names match?
869
+			$wpdb->prepare("$datetime_table.EVT_ID = %d", $new_event_id),// events match?
870
+		];
871
+		$select_and_join_part = "SELECT $tickets_table.TKT_ID FROM $tickets_table INNER JOIN
872 872
 			$datetime_tickets_table ON $tickets_table.TKT_ID = $datetime_tickets_table.TKT_ID INNER JOIN
873 873
 			$datetime_table ON $datetime_tickets_table.DTT_ID = $datetime_table.DTT_ID";
874
-        // start running queries, widening search each time by removing a condition
875
-        do {
876
-            $full_query      = $select_and_join_part . " WHERE " . implode(" AND ", $conditions) . " LIMIT 1";
877
-            $ticket_id_found = $wpdb->get_var($full_query);
878
-            array_shift($conditions);
879
-        } while (! $ticket_id_found && $conditions);
880
-        return $ticket_id_found;
881
-    }
882
-
883
-
884
-    /**
885
-     * If we couldn't find a 4.1 ticket for a 3.1 attendee row, this function creates one;
886
-     * and it also tries to find a datetime that works, and inserts a price, and associates
887
-     * the new ticket to that datetime and price.
888
-     *
889
-     * @return int ticket id
890
-     */
891
-    private function _insert_new_ticket_because_none_found($old_attendee, $new_event_id): int
892
-    {
893
-        global $wpdb;
894
-        $old_att_price_option = $old_attendee['price_option'];
895
-        $old_att_price        = floatval($old_attendee['orig_price']);
896
-
897
-        $old_att_start_date     = $old_attendee['start_date'];
898
-        $old_att_start_time     = $this->get_migration_script()->convertTimeFromAMPM(
899
-            $old_attendee['event_time']
900
-        );
901
-        $old_att_start_datetime =
902
-            $this->get_migration_script()->convert_date_string_to_utc(
903
-                $this,
904
-                $old_attendee,
905
-                "$old_att_start_date $old_att_start_time:00"
906
-            );
907
-        $old_att_end_datetime   = null;
908
-
909
-
910
-        // insert new datetime unless we find one
911
-        $datetime_id =
912
-            $wpdb->get_var(
913
-                $wpdb->prepare(
914
-                    "SELECT DTT_ID FROM " .
915
-                    $this->_new_datetime_table .
916
-                    " WHERE DTT_EVT_start=%s AND EVT_ID=%d LIMIT 1",
917
-                    $old_att_start_datetime,
918
-                    $new_event_id
919
-                ),
920
-                ARRAY_A
921
-            );
922
-        if (! $datetime_id) {
923
-            $old_att_end_date     = $old_attendee['start_date'];
924
-            $old_att_end_time     = $this->get_migration_script()->convertTimeFromAMPM(
925
-                $old_attendee['event_time']
926
-            );
927
-            $old_att_end_datetime = $this->get_migration_script()->convert_date_string_to_utc(
928
-                $this,
929
-                $old_attendee,
930
-                "$old_att_end_date $old_att_end_time:00"
931
-            );
932
-            $wpdb->insert(
933
-                $this->_new_datetime_table,
934
-                [
935
-                    'EVT_ID'        => $new_event_id,
936
-                    'DTT_EVT_start' => $old_att_start_datetime,
937
-                    'DTT_EVT_end'   => $old_att_end_datetime,
938
-                    'DTT_deleted'   => true,
939
-                ],
940
-                [
941
-                    '%d',// EVT_ID
942
-                    '%s',// DTT_EVT_start
943
-                    '%s',// DTT_EVT_end
944
-                    '%d',// DTT_deleted
945
-                ]
946
-            );
947
-            $datetime_id = $wpdb->insert_id;
948
-        }
949
-
950
-        // insert new ticket
951
-        $wpdb->insert(
952
-            $wpdb->prefix . 'esp_ticket',
953
-            [
954
-                'TKT_name'       => $old_att_price_option,
955
-                'TKT_qty'        => -1,
956
-                'TKT_price'      => $old_att_price,
957
-                'TKT_start_date' => $old_att_start_datetime,
958
-                // we really have no clue what the time should be, but at least it was available when they attended
959
-                'TKT_end_date'   => $old_att_end_datetime,
960
-
961
-            ],
962
-            [
963
-                '%s',// name
964
-                '%d',// qty
965
-                '%d',// price
966
-                '%s',// start_date
967
-                '%s',// end_date
968
-            ]
969
-        );
970
-        $ticket_id = $wpdb->insert_id;
971
-        // associate the ticket with the datetime we found earlier
972
-        $wpdb->insert(
973
-            $this->_new_datetime_ticket_table,
974
-            [
975
-                'DTT_ID' => $datetime_id,
976
-                'TKT_ID' => $ticket_id,
977
-            ],
978
-            [
979
-                '%d',// DTT_ID
980
-                '%d',// TKT_ID
981
-            ]
982
-        );
983
-        // insert new price
984
-        $wpdb->insert(
985
-            $this->_new_price_table,
986
-            [
987
-                'PRC_amount'  => $old_att_price,
988
-                'PRT_ID'      => EE_DMS_4_1_0_prices::price_type_base,
989
-                'PRC_name'    => $old_att_price_option,
990
-                'PRC_deleted' => true,
991
-            ],
992
-            [
993
-                '%d',// PRC_amount
994
-                '%d',// PRT_ID
995
-                '%s',// PRC_name
996
-                '%d',// PRC_deleted
997
-            ]
998
-        );
999
-        $price_id = $wpdb->insert_id;
1000
-        // associate the price to the ticket
1001
-        $wpdb->insert(
1002
-            $this->_new_ticket_price_table,
1003
-            [
1004
-                'TKT_ID' => $ticket_id,
1005
-                'PRC_ID' => $price_id,
1006
-            ],
1007
-            [
1008
-                '%d',// TKT_ID
1009
-                '%d',// PRC_ID
1010
-            ]
1011
-        );
1012
-        return $ticket_id;
1013
-    }
1014
-
1015
-
1016
-    /**
1017
-     * Counts all the registrations on this transaction. If $count_only_older is TRUE then returns the number added SO
1018
-     * FAR (ie, only considers attendee rows with an ID less than this one's), but if $count_only_older is FALSe
1019
-     * returns ALL
1020
-     *
1021
-     * @param array   $old_attendee_row
1022
-     * @param boolean $count_only_older true if you want the running count (ie, the total up to this row), and false if
1023
-     *                                  you want ALL
1024
-     * @return int
1025
-     * @global wpdb   $wpdb
1026
-     */
1027
-    private function _sum_old_attendees_on_old_txn(array $old_attendee_row, bool $count_only_older = false): int
1028
-    {
1029
-        global $wpdb;
1030
-        $count_only_older_sql = $count_only_older ? $wpdb->prepare(" AND id<%d", $old_attendee_row['id']) : '';
1031
-        $count                =
1032
-            intval(
1033
-                $wpdb->get_var(
1034
-                    $wpdb->prepare(
1035
-                        "SELECT SUM(quantity) FROM " .
1036
-                        $this->_old_table .
1037
-                        " WHERE registration_id=%s $count_only_older_sql",
1038
-                        $old_attendee_row['registration_id']
1039
-                    )
1040
-                )
1041
-            );
1042
-
1043
-        if ($this->_mer_tables_exist()) {
1044
-            // if MER exists, then it's a little tricky.
1045
-            // when users registered by adding items to the cart, and it was a
1046
-            // group registration requiring additional attendee INFO, then the attendee rows
1047
-            // DO NOT have the same registration_id (although they probably should have)
1048
-            // they are related just like MER attendee rows are related, through the MER group table
1049
-            // BUT we want to count all the MER attendee rows for the same registration
1050
-            $primary_attendee =
1051
-                $this->_find_mer_primary_attendee_using_mer_tables($old_attendee_row['registration_id']);
1052
-
1053
-            $count_using_mer_table =
1054
-                $wpdb->get_var(
1055
-                    $wpdb->prepare(
1056
-                        "SELECT SUM(quantity) FROM $this->_old_table att INNER JOIN $this->_old_mer_table mer ON att.registration_id = mer.registration_id WHERE att.event_id=%d AND mer.primary_registration_id = %s $count_only_older_sql",
1057
-                        $old_attendee_row['event_id'],
1058
-                        $primary_attendee['registration_id']
1059
-                    )
1060
-                );
1061
-            $count                 = max($count_using_mer_table, $count);
1062
-        }
1063
-        return $count;
1064
-    }
1065
-
1066
-
1067
-    private function _insert_new_payment($old_attendee, $new_txn_id): int
1068
-    {
1069
-        global $wpdb;
1070
-        // Only add a payment for primary attendees. Important note: partial payments were marked as "Pending".
1071
-        // Also, an offline payment had blank status (ie, "").
1072
-        $old_pay_stati_indicating_no_payment = ['', 'Incomplete', 'Not Completed'];
1073
-        // if this is for a primary 3.1 attendee which WASN'T free and has a completed, cancelled, or declined payment...
1074
-        if (
1075
-            intval($old_attendee['is_primary']) &&
1076
-            floatval($old_attendee['total_cost']) &&
1077
-            ! in_array($old_attendee['payment_status'], $old_pay_stati_indicating_no_payment)
1078
-        ) {
1079
-            $pay_status_mapping = [
1080
-                'Completed'        => 'PAP',
1081
-                'Payment Declined' => 'PDC',
1082
-                'Cancelled'        => 'PCN',
1083
-                'Declined'         => 'PDC',
1084
-            ];
1085
-
1086
-            $by_admin = $old_attendee['payment'] == 'Admin';
1087
-
1088
-            // IE, if we don't recognize the status, assume payment failed
1089
-            $STS_ID = $pay_status_mapping[ $old_attendee['payment_status'] ] ?? 'PFL';
1090
-
1091
-            $cols_n_values = [
1092
-                'TXN_ID'               => $new_txn_id,
1093
-                'STS_ID'               => $STS_ID,
1094
-                'PAY_timestamp'        => $this->get_migration_script()->convert_date_string_to_utc(
1095
-                    $this,
1096
-                    $old_attendee,
1097
-                    $old_attendee['date']
1098
-                ),
1099
-                'PAY_method'           => 'CART',
1100
-                'PAY_amount'           => $old_attendee['amount_pd'],
1101
-                // 'txn_type' was used both for the gateway's name, and for other payment notes.
1102
-                'PAY_gateway'          => $old_attendee['txn_type'],
1103
-                'PAY_extra_accntng'    => $old_attendee['txn_type'],
1104
-                'PAY_gateway_response' => '',
1105
-                'PAY_txn_id_chq_nmbr'  => substr($old_attendee['txn_id'], 0, 32),
1106
-                'PAY_via_admin'        => $by_admin,
1107
-                'PAY_details'          => $old_attendee['transaction_details'],
1108
-            ];
1109
-            $data_types    = [
1110
-                '%d',// TXN_Id
1111
-                '%s',// STS_ID
1112
-                '%s',// PAY_timestamp
1113
-                '%s',// PAY_method
1114
-                '%f',// PAY_amount
1115
-                '%s',// PAY_gateway
1116
-                '%s',// PAY_gateway_response
1117
-                '%s',// PAY_txn_id_chq_nmbr
1118
-                '%d',// PAY_via_admin
1119
-                '%s',// PAY_details
1120
-            ];
1121
-            $success       = $wpdb->insert($this->_new_payment_table, $cols_n_values, $data_types);
1122
-            if (! $success) {
1123
-                $this->add_error(
1124
-                    $this->get_migration_script()->_create_error_message_for_db_insertion(
1125
-                        $this->_old_table,
1126
-                        $old_attendee,
1127
-                        $this->_new_attendee_cpt_table,
1128
-                        $cols_n_values,
1129
-                        $data_types
1130
-                    )
1131
-                );
1132
-                return 0;
1133
-            }
1134
-            return $wpdb->insert_id;
1135
-        }
1136
-        return 0;
1137
-    }
1138
-
1139
-
1140
-    /**
1141
-     * If MER is active, if you want ot find the other registrations on that attendee row
1142
-     *
1143
-     * @param int   $old_registration_id
1144
-     * @return array|null
1145
-     * @global wpdb $wpdb
1146
-     */
1147
-    private function _find_mer_primary_attendee_using_mer_tables(int $old_registration_id): ?array
1148
-    {
1149
-        if (! $this->_mer_tables_exist()) {
1150
-            return null;
1151
-        }
1152
-        global $wpdb;
1153
-        return $wpdb->get_row(
1154
-            $wpdb->prepare(
1155
-                "SELECT * FROM $this->_old_mer_table AS mer INNER JOIN $this->_old_table AS att ON mer.primary_registration_id = att.registration_id WHERE mer.registration_id=%s LIMIT 1",
1156
-                $old_registration_id
1157
-            ),
1158
-            ARRAY_A
1159
-        );
1160
-    }
874
+		// start running queries, widening search each time by removing a condition
875
+		do {
876
+			$full_query      = $select_and_join_part . " WHERE " . implode(" AND ", $conditions) . " LIMIT 1";
877
+			$ticket_id_found = $wpdb->get_var($full_query);
878
+			array_shift($conditions);
879
+		} while (! $ticket_id_found && $conditions);
880
+		return $ticket_id_found;
881
+	}
882
+
883
+
884
+	/**
885
+	 * If we couldn't find a 4.1 ticket for a 3.1 attendee row, this function creates one;
886
+	 * and it also tries to find a datetime that works, and inserts a price, and associates
887
+	 * the new ticket to that datetime and price.
888
+	 *
889
+	 * @return int ticket id
890
+	 */
891
+	private function _insert_new_ticket_because_none_found($old_attendee, $new_event_id): int
892
+	{
893
+		global $wpdb;
894
+		$old_att_price_option = $old_attendee['price_option'];
895
+		$old_att_price        = floatval($old_attendee['orig_price']);
896
+
897
+		$old_att_start_date     = $old_attendee['start_date'];
898
+		$old_att_start_time     = $this->get_migration_script()->convertTimeFromAMPM(
899
+			$old_attendee['event_time']
900
+		);
901
+		$old_att_start_datetime =
902
+			$this->get_migration_script()->convert_date_string_to_utc(
903
+				$this,
904
+				$old_attendee,
905
+				"$old_att_start_date $old_att_start_time:00"
906
+			);
907
+		$old_att_end_datetime   = null;
908
+
909
+
910
+		// insert new datetime unless we find one
911
+		$datetime_id =
912
+			$wpdb->get_var(
913
+				$wpdb->prepare(
914
+					"SELECT DTT_ID FROM " .
915
+					$this->_new_datetime_table .
916
+					" WHERE DTT_EVT_start=%s AND EVT_ID=%d LIMIT 1",
917
+					$old_att_start_datetime,
918
+					$new_event_id
919
+				),
920
+				ARRAY_A
921
+			);
922
+		if (! $datetime_id) {
923
+			$old_att_end_date     = $old_attendee['start_date'];
924
+			$old_att_end_time     = $this->get_migration_script()->convertTimeFromAMPM(
925
+				$old_attendee['event_time']
926
+			);
927
+			$old_att_end_datetime = $this->get_migration_script()->convert_date_string_to_utc(
928
+				$this,
929
+				$old_attendee,
930
+				"$old_att_end_date $old_att_end_time:00"
931
+			);
932
+			$wpdb->insert(
933
+				$this->_new_datetime_table,
934
+				[
935
+					'EVT_ID'        => $new_event_id,
936
+					'DTT_EVT_start' => $old_att_start_datetime,
937
+					'DTT_EVT_end'   => $old_att_end_datetime,
938
+					'DTT_deleted'   => true,
939
+				],
940
+				[
941
+					'%d',// EVT_ID
942
+					'%s',// DTT_EVT_start
943
+					'%s',// DTT_EVT_end
944
+					'%d',// DTT_deleted
945
+				]
946
+			);
947
+			$datetime_id = $wpdb->insert_id;
948
+		}
949
+
950
+		// insert new ticket
951
+		$wpdb->insert(
952
+			$wpdb->prefix . 'esp_ticket',
953
+			[
954
+				'TKT_name'       => $old_att_price_option,
955
+				'TKT_qty'        => -1,
956
+				'TKT_price'      => $old_att_price,
957
+				'TKT_start_date' => $old_att_start_datetime,
958
+				// we really have no clue what the time should be, but at least it was available when they attended
959
+				'TKT_end_date'   => $old_att_end_datetime,
960
+
961
+			],
962
+			[
963
+				'%s',// name
964
+				'%d',// qty
965
+				'%d',// price
966
+				'%s',// start_date
967
+				'%s',// end_date
968
+			]
969
+		);
970
+		$ticket_id = $wpdb->insert_id;
971
+		// associate the ticket with the datetime we found earlier
972
+		$wpdb->insert(
973
+			$this->_new_datetime_ticket_table,
974
+			[
975
+				'DTT_ID' => $datetime_id,
976
+				'TKT_ID' => $ticket_id,
977
+			],
978
+			[
979
+				'%d',// DTT_ID
980
+				'%d',// TKT_ID
981
+			]
982
+		);
983
+		// insert new price
984
+		$wpdb->insert(
985
+			$this->_new_price_table,
986
+			[
987
+				'PRC_amount'  => $old_att_price,
988
+				'PRT_ID'      => EE_DMS_4_1_0_prices::price_type_base,
989
+				'PRC_name'    => $old_att_price_option,
990
+				'PRC_deleted' => true,
991
+			],
992
+			[
993
+				'%d',// PRC_amount
994
+				'%d',// PRT_ID
995
+				'%s',// PRC_name
996
+				'%d',// PRC_deleted
997
+			]
998
+		);
999
+		$price_id = $wpdb->insert_id;
1000
+		// associate the price to the ticket
1001
+		$wpdb->insert(
1002
+			$this->_new_ticket_price_table,
1003
+			[
1004
+				'TKT_ID' => $ticket_id,
1005
+				'PRC_ID' => $price_id,
1006
+			],
1007
+			[
1008
+				'%d',// TKT_ID
1009
+				'%d',// PRC_ID
1010
+			]
1011
+		);
1012
+		return $ticket_id;
1013
+	}
1014
+
1015
+
1016
+	/**
1017
+	 * Counts all the registrations on this transaction. If $count_only_older is TRUE then returns the number added SO
1018
+	 * FAR (ie, only considers attendee rows with an ID less than this one's), but if $count_only_older is FALSe
1019
+	 * returns ALL
1020
+	 *
1021
+	 * @param array   $old_attendee_row
1022
+	 * @param boolean $count_only_older true if you want the running count (ie, the total up to this row), and false if
1023
+	 *                                  you want ALL
1024
+	 * @return int
1025
+	 * @global wpdb   $wpdb
1026
+	 */
1027
+	private function _sum_old_attendees_on_old_txn(array $old_attendee_row, bool $count_only_older = false): int
1028
+	{
1029
+		global $wpdb;
1030
+		$count_only_older_sql = $count_only_older ? $wpdb->prepare(" AND id<%d", $old_attendee_row['id']) : '';
1031
+		$count                =
1032
+			intval(
1033
+				$wpdb->get_var(
1034
+					$wpdb->prepare(
1035
+						"SELECT SUM(quantity) FROM " .
1036
+						$this->_old_table .
1037
+						" WHERE registration_id=%s $count_only_older_sql",
1038
+						$old_attendee_row['registration_id']
1039
+					)
1040
+				)
1041
+			);
1042
+
1043
+		if ($this->_mer_tables_exist()) {
1044
+			// if MER exists, then it's a little tricky.
1045
+			// when users registered by adding items to the cart, and it was a
1046
+			// group registration requiring additional attendee INFO, then the attendee rows
1047
+			// DO NOT have the same registration_id (although they probably should have)
1048
+			// they are related just like MER attendee rows are related, through the MER group table
1049
+			// BUT we want to count all the MER attendee rows for the same registration
1050
+			$primary_attendee =
1051
+				$this->_find_mer_primary_attendee_using_mer_tables($old_attendee_row['registration_id']);
1052
+
1053
+			$count_using_mer_table =
1054
+				$wpdb->get_var(
1055
+					$wpdb->prepare(
1056
+						"SELECT SUM(quantity) FROM $this->_old_table att INNER JOIN $this->_old_mer_table mer ON att.registration_id = mer.registration_id WHERE att.event_id=%d AND mer.primary_registration_id = %s $count_only_older_sql",
1057
+						$old_attendee_row['event_id'],
1058
+						$primary_attendee['registration_id']
1059
+					)
1060
+				);
1061
+			$count                 = max($count_using_mer_table, $count);
1062
+		}
1063
+		return $count;
1064
+	}
1065
+
1066
+
1067
+	private function _insert_new_payment($old_attendee, $new_txn_id): int
1068
+	{
1069
+		global $wpdb;
1070
+		// Only add a payment for primary attendees. Important note: partial payments were marked as "Pending".
1071
+		// Also, an offline payment had blank status (ie, "").
1072
+		$old_pay_stati_indicating_no_payment = ['', 'Incomplete', 'Not Completed'];
1073
+		// if this is for a primary 3.1 attendee which WASN'T free and has a completed, cancelled, or declined payment...
1074
+		if (
1075
+			intval($old_attendee['is_primary']) &&
1076
+			floatval($old_attendee['total_cost']) &&
1077
+			! in_array($old_attendee['payment_status'], $old_pay_stati_indicating_no_payment)
1078
+		) {
1079
+			$pay_status_mapping = [
1080
+				'Completed'        => 'PAP',
1081
+				'Payment Declined' => 'PDC',
1082
+				'Cancelled'        => 'PCN',
1083
+				'Declined'         => 'PDC',
1084
+			];
1085
+
1086
+			$by_admin = $old_attendee['payment'] == 'Admin';
1087
+
1088
+			// IE, if we don't recognize the status, assume payment failed
1089
+			$STS_ID = $pay_status_mapping[ $old_attendee['payment_status'] ] ?? 'PFL';
1090
+
1091
+			$cols_n_values = [
1092
+				'TXN_ID'               => $new_txn_id,
1093
+				'STS_ID'               => $STS_ID,
1094
+				'PAY_timestamp'        => $this->get_migration_script()->convert_date_string_to_utc(
1095
+					$this,
1096
+					$old_attendee,
1097
+					$old_attendee['date']
1098
+				),
1099
+				'PAY_method'           => 'CART',
1100
+				'PAY_amount'           => $old_attendee['amount_pd'],
1101
+				// 'txn_type' was used both for the gateway's name, and for other payment notes.
1102
+				'PAY_gateway'          => $old_attendee['txn_type'],
1103
+				'PAY_extra_accntng'    => $old_attendee['txn_type'],
1104
+				'PAY_gateway_response' => '',
1105
+				'PAY_txn_id_chq_nmbr'  => substr($old_attendee['txn_id'], 0, 32),
1106
+				'PAY_via_admin'        => $by_admin,
1107
+				'PAY_details'          => $old_attendee['transaction_details'],
1108
+			];
1109
+			$data_types    = [
1110
+				'%d',// TXN_Id
1111
+				'%s',// STS_ID
1112
+				'%s',// PAY_timestamp
1113
+				'%s',// PAY_method
1114
+				'%f',// PAY_amount
1115
+				'%s',// PAY_gateway
1116
+				'%s',// PAY_gateway_response
1117
+				'%s',// PAY_txn_id_chq_nmbr
1118
+				'%d',// PAY_via_admin
1119
+				'%s',// PAY_details
1120
+			];
1121
+			$success       = $wpdb->insert($this->_new_payment_table, $cols_n_values, $data_types);
1122
+			if (! $success) {
1123
+				$this->add_error(
1124
+					$this->get_migration_script()->_create_error_message_for_db_insertion(
1125
+						$this->_old_table,
1126
+						$old_attendee,
1127
+						$this->_new_attendee_cpt_table,
1128
+						$cols_n_values,
1129
+						$data_types
1130
+					)
1131
+				);
1132
+				return 0;
1133
+			}
1134
+			return $wpdb->insert_id;
1135
+		}
1136
+		return 0;
1137
+	}
1138
+
1139
+
1140
+	/**
1141
+	 * If MER is active, if you want ot find the other registrations on that attendee row
1142
+	 *
1143
+	 * @param int   $old_registration_id
1144
+	 * @return array|null
1145
+	 * @global wpdb $wpdb
1146
+	 */
1147
+	private function _find_mer_primary_attendee_using_mer_tables(int $old_registration_id): ?array
1148
+	{
1149
+		if (! $this->_mer_tables_exist()) {
1150
+			return null;
1151
+		}
1152
+		global $wpdb;
1153
+		return $wpdb->get_row(
1154
+			$wpdb->prepare(
1155
+				"SELECT * FROM $this->_old_mer_table AS mer INNER JOIN $this->_old_table AS att ON mer.primary_registration_id = att.registration_id WHERE mer.registration_id=%s LIMIT 1",
1156
+				$old_registration_id
1157
+			),
1158
+			ARRAY_A
1159
+		);
1160
+	}
1161 1161
 }
Please login to merge, or discard this patch.
Spacing   +140 added lines, -140 removed lines patch added patch discarded remove patch
@@ -261,25 +261,25 @@  discard block
 block discarded – undo
261 261
     {
262 262
         global $wpdb;
263 263
         $this->_pretty_name = esc_html__("Attendees", "event_espresso");
264
-        $this->_old_table   = $wpdb->prefix . "events_attendee";
264
+        $this->_old_table   = $wpdb->prefix."events_attendee";
265 265
         // Only select the event status column from the event table.
266 266
         $this->select_expression = 'att.*, e.event_status';
267 267
         // Only select attendees for events that aren't deleted.
268 268
         $this->_extra_where_sql           = 'AS att
269
-            INNER JOIN ' . $wpdb->prefix . 'events_detail AS e ON att.event_id=e.id
269
+            INNER JOIN ' . $wpdb->prefix.'events_detail AS e ON att.event_id=e.id
270 270
             WHERE e.event_status!="D"';
271
-        $this->_old_mer_table             = $wpdb->prefix . "events_multi_event_registration_id_group";
271
+        $this->_old_mer_table             = $wpdb->prefix."events_multi_event_registration_id_group";
272 272
         $this->_new_attendee_cpt_table    = $wpdb->posts;
273
-        $this->_new_attendee_meta_table   = $wpdb->prefix . "esp_attendee_meta";
274
-        $this->_new_reg_table             = $wpdb->prefix . "esp_registration";
275
-        $this->_new_transaction_table     = $wpdb->prefix . "esp_transaction";
276
-        $this->_new_payment_table         = $wpdb->prefix . "esp_payment";
277
-        $this->_new_ticket_table          = $wpdb->prefix . "esp_ticket";
278
-        $this->_new_ticket_datetime_table = $wpdb->prefix . "esp_datetime_ticket";
279
-        $this->_new_datetime_table        = $wpdb->prefix . "esp_datetime";
280
-        $this->_new_datetime_ticket_table = $wpdb->prefix . "esp_datetime_ticket";
281
-        $this->_new_price_table           = $wpdb->prefix . "esp_price";
282
-        $this->_new_ticket_price_table    = $wpdb->prefix . "esp_ticket_price";
273
+        $this->_new_attendee_meta_table   = $wpdb->prefix."esp_attendee_meta";
274
+        $this->_new_reg_table             = $wpdb->prefix."esp_registration";
275
+        $this->_new_transaction_table     = $wpdb->prefix."esp_transaction";
276
+        $this->_new_payment_table         = $wpdb->prefix."esp_payment";
277
+        $this->_new_ticket_table          = $wpdb->prefix."esp_ticket";
278
+        $this->_new_ticket_datetime_table = $wpdb->prefix."esp_datetime_ticket";
279
+        $this->_new_datetime_table        = $wpdb->prefix."esp_datetime";
280
+        $this->_new_datetime_ticket_table = $wpdb->prefix."esp_datetime_ticket";
281
+        $this->_new_price_table           = $wpdb->prefix."esp_price";
282
+        $this->_new_ticket_price_table    = $wpdb->prefix."esp_ticket_price";
283 283
         parent::__construct();
284 284
     }
285 285
 
@@ -291,9 +291,9 @@  discard block
 block discarded – undo
291 291
     {
292 292
         // first check if there's already a new attendee with similar characteristics
293 293
         $new_att_id = $this->_find_attendee_cpt_matching($old_row);
294
-        if (! $new_att_id) {
294
+        if ( ! $new_att_id) {
295 295
             $new_att_id = $this->_insert_new_attendee_cpt($old_row);
296
-            if (! $new_att_id) {
296
+            if ( ! $new_att_id) {
297 297
                 // if we couldn't even make an attendee, abandon all hope
298 298
                 return;
299 299
             }
@@ -315,7 +315,7 @@  discard block
 block discarded – undo
315 315
         );
316 316
 
317 317
         $txn_id = $this->_insert_new_transaction($old_row);
318
-        if (! $txn_id) {
318
+        if ( ! $txn_id) {
319 319
             // if we couldn't make the transaction, also abandon all hope
320 320
             return;
321 321
         }
@@ -363,10 +363,10 @@  discard block
 block discarded – undo
363 363
         $existing_attendee_id =
364 364
             $wpdb->get_var(
365 365
                 $wpdb->prepare(
366
-                    "SELECT id FROM " .
367
-                    $this->_new_attendee_cpt_table .
368
-                    " AS cpt INNER JOIN " .
369
-                    $this->_new_attendee_meta_table .
366
+                    "SELECT id FROM ".
367
+                    $this->_new_attendee_cpt_table.
368
+                    " AS cpt INNER JOIN ".
369
+                    $this->_new_attendee_meta_table.
370 370
                     " AS meta ON cpt.ID = meta.ATT_ID WHERE meta.ATT_fname = %s AND meta.ATT_lname = %s AND meta.ATT_email = %s LIMIT 1",
371 371
                     $old_attendee['fname'],
372 372
                     $old_attendee['lname'],
@@ -381,39 +381,39 @@  discard block
 block discarded – undo
381 381
     {
382 382
         global $wpdb;
383 383
         $cols_n_values = [
384
-            'post_title'    => stripslashes($old_attendee['fname'] . " " . $old_attendee['lname']),// ATT_full_name
385
-            'post_content'  => '',// ATT_bio
386
-            'post_name'     => sanitize_title($old_attendee['fname'] . "-" . $old_attendee['lname']),// ATT_slug
384
+            'post_title'    => stripslashes($old_attendee['fname']." ".$old_attendee['lname']), // ATT_full_name
385
+            'post_content'  => '', // ATT_bio
386
+            'post_name'     => sanitize_title($old_attendee['fname']."-".$old_attendee['lname']), // ATT_slug
387 387
             'post_date'     => $this->get_migration_script()->convert_date_string_to_utc(
388 388
                 $this,
389 389
                 $old_attendee,
390 390
                 $old_attendee['date']
391
-            ),// ATT_created
392
-            'post_excerpt'  => '',// ATT_short_bio
391
+            ), // ATT_created
392
+            'post_excerpt'  => '', // ATT_short_bio
393 393
             'post_modified' => $this->get_migration_script()->convert_date_string_to_utc(
394 394
                 $this,
395 395
                 $old_attendee,
396 396
                 $old_attendee['date']
397
-            ),// ATT_modified
398
-            'post_author'   => 0,// ATT_author
399
-            'post_parent'   => 0,// ATT_parent
397
+            ), // ATT_modified
398
+            'post_author'   => 0, // ATT_author
399
+            'post_parent'   => 0, // ATT_parent
400 400
             'post_type'     => EspressoPostType::ATTENDEES,
401
-            'post_status'   => 'publish',// status
401
+            'post_status'   => 'publish', // status
402 402
         ];
403
-        $data_types    = [
404
-            '%s',// ATT_full_name
405
-            '%s',// ATT_bio
406
-            '%s',// ATT_slug
407
-            '%s',// ATT_created
408
-            '%s',// ATT_short_bio
409
-            '%s',// ATT_modified
410
-            '%d',// ATT_author
411
-            '%d',// ATT_parent
412
-            '%s',// post_type
413
-            '%s',// status
403
+        $data_types = [
404
+            '%s', // ATT_full_name
405
+            '%s', // ATT_bio
406
+            '%s', // ATT_slug
407
+            '%s', // ATT_created
408
+            '%s', // ATT_short_bio
409
+            '%s', // ATT_modified
410
+            '%d', // ATT_author
411
+            '%d', // ATT_parent
412
+            '%s', // post_type
413
+            '%s', // status
414 414
         ];
415
-        $success       = $wpdb->insert($this->_new_attendee_cpt_table, $cols_n_values, $data_types);
416
-        if (! $success) {
415
+        $success = $wpdb->insert($this->_new_attendee_cpt_table, $cols_n_values, $data_types);
416
+        if ( ! $success) {
417 417
             $this->add_error(
418 418
                 $this->get_migration_script()->_create_error_message_for_db_insertion(
419 419
                     $this->_old_table,
@@ -441,7 +441,7 @@  discard block
 block discarded – undo
441 441
             $new_country_iso = $this->get_migration_script()->get_default_country_iso();
442 442
         }
443 443
         try {
444
-            $new_state    =
444
+            $new_state =
445 445
                 $this->get_migration_script()->get_or_create_state(
446 446
                     stripslashes($old_attendee['state']),
447 447
                     $new_country_iso
@@ -463,21 +463,21 @@  discard block
 block discarded – undo
463 463
             'ATT_email'    => stripslashes($old_attendee['email']),
464 464
             'ATT_phone'    => stripslashes($old_attendee['phone']),
465 465
         ];
466
-        $data_types    = [
467
-            '%d',// ATT_ID
468
-            '%s',// ATT_fname
469
-            '%s',// ATT_lname
470
-            '%s',// ATT_address
471
-            '%s',// ATT_address2
472
-            '%s',// ATT_city
473
-            '%d',// STA_ID
474
-            '%s',// CNT_ISO
475
-            '%s',// ATT_zip
476
-            '%s',// ATT_email
477
-            '%s',// ATT_phone
466
+        $data_types = [
467
+            '%d', // ATT_ID
468
+            '%s', // ATT_fname
469
+            '%s', // ATT_lname
470
+            '%s', // ATT_address
471
+            '%s', // ATT_address2
472
+            '%s', // ATT_city
473
+            '%d', // STA_ID
474
+            '%s', // CNT_ISO
475
+            '%s', // ATT_zip
476
+            '%s', // ATT_email
477
+            '%s', // ATT_phone
478 478
         ];
479
-        $success       = $wpdb->insert($this->_new_attendee_meta_table, $cols_n_values, $data_types);
480
-        if (! $success) {
479
+        $success = $wpdb->insert($this->_new_attendee_meta_table, $cols_n_values, $data_types);
480
+        if ( ! $success) {
481 481
             $this->add_error(
482 482
                 $this->get_migration_script()->_create_error_message_for_db_insertion(
483 483
                     $this->_old_table,
@@ -519,11 +519,11 @@  discard block
 block discarded – undo
519 519
             $primary_attendee_old_id =
520 520
                 $wpdb->get_var(
521 521
                     $wpdb->prepare(
522
-                        "SELECT id FROM " . $this->_old_table . " WHERE is_primary=1 and registration_id=%s",
522
+                        "SELECT id FROM ".$this->_old_table." WHERE is_primary=1 and registration_id=%s",
523 523
                         $old_attendee['registration_id']
524 524
                     )
525 525
                 );
526
-            if (! $primary_attendee_old_id) {
526
+            if ( ! $primary_attendee_old_id) {
527 527
                 $primary_attendee        =
528 528
                     $this->_find_mer_primary_attendee_using_mer_tables($old_attendee['registration_id']);
529 529
                 $primary_attendee_old_id = is_array($primary_attendee) ? $primary_attendee['id'] : null;
@@ -534,7 +534,7 @@  discard block
 block discarded – undo
534 534
                     intval($primary_attendee_old_id),
535 535
                     $this->_new_transaction_table
536 536
                 );
537
-            if (! $txn_id) {
537
+            if ( ! $txn_id) {
538 538
                 $this->add_error(
539 539
                     sprintf(
540 540
                         esc_html__(
@@ -551,7 +551,7 @@  discard block
 block discarded – undo
551 551
         // if there isn't yet a transaction row for this, create one
552 552
         // (so even if it was a non-primary attendee with no EE3 primary attendee,
553 553
         // it ought to have SOME transaction, so we'll make one)
554
-        if (! $txn_id) {
554
+        if ( ! $txn_id) {
555 555
             // maps 3.1 payment stati onto 4.1 transaction stati
556 556
             $txn_status_mapping = [
557 557
                 'Completed'        => 'TCM',
@@ -562,7 +562,7 @@  discard block
 block discarded – undo
562 562
                 'Cancelled'        => 'TIN',
563 563
                 'Declined'         => 'TIN',
564 564
             ];
565
-            $STS_ID             = $txn_status_mapping[ $old_attendee['payment_status'] ] ?? 'TIN';
565
+            $STS_ID             = $txn_status_mapping[$old_attendee['payment_status']] ?? 'TIN';
566 566
             $cols_n_values      = [
567 567
                 'TXN_timestamp' => $this->get_migration_script()->convert_date_string_to_utc(
568 568
                     $this,
@@ -574,15 +574,15 @@  discard block
 block discarded – undo
574 574
                 'STS_ID'        => $STS_ID,
575 575
                 'TXN_hash_salt' => $old_attendee['hashSalt'],
576 576
             ];
577
-            $data_types         = [
578
-                '%s',// TXN_timestamp
579
-                '%f',// TXN_total
580
-                '%f',// TXN_paid
581
-                '%s',// STS_ID
582
-                '%s',// TXN_hash_salt
577
+            $data_types = [
578
+                '%s', // TXN_timestamp
579
+                '%f', // TXN_total
580
+                '%f', // TXN_paid
581
+                '%s', // STS_ID
582
+                '%s', // TXN_hash_salt
583 583
             ];
584
-            $success            = $wpdb->insert($this->_new_transaction_table, $cols_n_values, $data_types);
585
-            if (! $success) {
584
+            $success = $wpdb->insert($this->_new_transaction_table, $cols_n_values, $data_types);
585
+            if ( ! $success) {
586 586
                 $this->add_error(
587 587
                     $this->get_migration_script()->_create_error_message_for_db_insertion(
588 588
                         $this->_old_table,
@@ -637,7 +637,7 @@  discard block
 block discarded – undo
637 637
         $event_required_pre_approval =
638 638
             $wpdb->get_var(
639 639
                 $wpdb->prepare(
640
-                    "SELECT require_pre_approval FROM " . $wpdb->prefix . "events_detail WHERE id = %d",
640
+                    "SELECT require_pre_approval FROM ".$wpdb->prefix."events_detail WHERE id = %d",
641 641
                     $old_attendee_row['event_id']
642 642
                 )
643 643
             );
@@ -666,11 +666,11 @@  discard block
 block discarded – undo
666 666
         $STS_ID       = $this->_get_reg_status_for_old_payment_status($old_attendee);
667 667
         $new_event_id =
668 668
             $this->get_migration_script()->get_mapping_new_pk(
669
-                $wpdb->prefix . 'events_detail',
669
+                $wpdb->prefix.'events_detail',
670 670
                 $old_attendee['event_id'],
671 671
                 $wpdb->posts
672 672
             );
673
-        if (! $new_event_id) {
673
+        if ( ! $new_event_id) {
674 674
             $this->add_error(
675 675
                 sprintf(
676 676
                     esc_html__(
@@ -684,7 +684,7 @@  discard block
 block discarded – undo
684 684
         }
685 685
 
686 686
         $ticket_id = $this->_try_to_find_new_ticket_id($old_attendee, $new_event_id);
687
-        if (! $ticket_id) {
687
+        if ( ! $ticket_id) {
688 688
             $ticket_id = $this->_insert_new_ticket_because_none_found($old_attendee, $new_event_id);
689 689
             $this->add_error(
690 690
                 sprintf(
@@ -722,30 +722,30 @@  discard block
 block discarded – undo
722 722
                 'REG_final_price'  => $old_attendee['final_price'],
723 723
                 'REG_session'      => substr($old_attendee['attendee_session'], 0, 44),
724 724
                 'REG_code'         => sanitize_key($old_attendee['registration_id']),
725
-                'REG_url_link'     => sanitize_key($old_attendee['registration_id'] . '-' . $count),
725
+                'REG_url_link'     => sanitize_key($old_attendee['registration_id'].'-'.$count),
726 726
                 'REG_count'        => $regs_on_this_event_and_txn + $count,
727 727
                 'REG_group_size'   => $this->_sum_old_attendees_on_old_txn($old_attendee),
728 728
                 'REG_att_is_going' => true,
729 729
                 'REG_deleted'      => false,
730 730
             ];
731
-            $data_types                 = [
732
-                '%d',// EVT_ID
733
-                '%d',// ATT_ID
734
-                '%d',// TXN_ID
735
-                '%d',// TKT_ID
736
-                '%s',// STS_ID
737
-                '%s',// REG_date
738
-                '%f',// REG_final_price
739
-                '%s',// REG_session
740
-                '%s',// REG_code
741
-                '%s',// REG_url_link
742
-                '%d',// REG_count
743
-                '%d',// REG_group_size
744
-                '%d',// REG_att_is_going
745
-                '%d',// REG_deleted
731
+            $data_types = [
732
+                '%d', // EVT_ID
733
+                '%d', // ATT_ID
734
+                '%d', // TXN_ID
735
+                '%d', // TKT_ID
736
+                '%s', // STS_ID
737
+                '%s', // REG_date
738
+                '%f', // REG_final_price
739
+                '%s', // REG_session
740
+                '%s', // REG_code
741
+                '%s', // REG_url_link
742
+                '%d', // REG_count
743
+                '%d', // REG_group_size
744
+                '%d', // REG_att_is_going
745
+                '%d', // REG_deleted
746 746
             ];
747
-            $success                    = $wpdb->insert($this->_new_reg_table, $cols_n_values, $data_types);
748
-            if (! $success) {
747
+            $success = $wpdb->insert($this->_new_reg_table, $cols_n_values, $data_types);
748
+            if ( ! $success) {
749 749
                 $this->add_error(
750 750
                     $this->get_migration_script()->_create_error_message_for_db_insertion(
751 751
                         $this->_old_table,
@@ -804,7 +804,7 @@  discard block
 block discarded – undo
804 804
                     $new_ticket_id
805 805
                 )
806 806
             );
807
-            if (! $success_update_datetimes) {
807
+            if ( ! $success_update_datetimes) {
808 808
                 $this->add_error(
809 809
                     sprintf(
810 810
                         esc_html__(
@@ -862,21 +862,21 @@  discard block
 block discarded – undo
862 862
             );
863 863
         // add all conditions to an array from which we can SHIFT conditions off in order to widen our search
864 864
         // the most important condition should be last, as it will be array_shift'ed off last
865
-        $conditions           = [
866
-            $wpdb->prepare("$datetime_table.DTT_EVT_start = %s", $old_att_datetime),// times match?
867
-            $wpdb->prepare("$tickets_table.TKT_price = %f", $old_att_price),// prices match?
868
-            $wpdb->prepare("$tickets_table.TKT_name = %s", $old_att_price_option),// names match?
869
-            $wpdb->prepare("$datetime_table.EVT_ID = %d", $new_event_id),// events match?
865
+        $conditions = [
866
+            $wpdb->prepare("$datetime_table.DTT_EVT_start = %s", $old_att_datetime), // times match?
867
+            $wpdb->prepare("$tickets_table.TKT_price = %f", $old_att_price), // prices match?
868
+            $wpdb->prepare("$tickets_table.TKT_name = %s", $old_att_price_option), // names match?
869
+            $wpdb->prepare("$datetime_table.EVT_ID = %d", $new_event_id), // events match?
870 870
         ];
871 871
         $select_and_join_part = "SELECT $tickets_table.TKT_ID FROM $tickets_table INNER JOIN
872 872
 			$datetime_tickets_table ON $tickets_table.TKT_ID = $datetime_tickets_table.TKT_ID INNER JOIN
873 873
 			$datetime_table ON $datetime_tickets_table.DTT_ID = $datetime_table.DTT_ID";
874 874
         // start running queries, widening search each time by removing a condition
875 875
         do {
876
-            $full_query      = $select_and_join_part . " WHERE " . implode(" AND ", $conditions) . " LIMIT 1";
876
+            $full_query      = $select_and_join_part." WHERE ".implode(" AND ", $conditions)." LIMIT 1";
877 877
             $ticket_id_found = $wpdb->get_var($full_query);
878 878
             array_shift($conditions);
879
-        } while (! $ticket_id_found && $conditions);
879
+        }while ( ! $ticket_id_found && $conditions);
880 880
         return $ticket_id_found;
881 881
     }
882 882
 
@@ -904,22 +904,22 @@  discard block
 block discarded – undo
904 904
                 $old_attendee,
905 905
                 "$old_att_start_date $old_att_start_time:00"
906 906
             );
907
-        $old_att_end_datetime   = null;
907
+        $old_att_end_datetime = null;
908 908
 
909 909
 
910 910
         // insert new datetime unless we find one
911 911
         $datetime_id =
912 912
             $wpdb->get_var(
913 913
                 $wpdb->prepare(
914
-                    "SELECT DTT_ID FROM " .
915
-                    $this->_new_datetime_table .
914
+                    "SELECT DTT_ID FROM ".
915
+                    $this->_new_datetime_table.
916 916
                     " WHERE DTT_EVT_start=%s AND EVT_ID=%d LIMIT 1",
917 917
                     $old_att_start_datetime,
918 918
                     $new_event_id
919 919
                 ),
920 920
                 ARRAY_A
921 921
             );
922
-        if (! $datetime_id) {
922
+        if ( ! $datetime_id) {
923 923
             $old_att_end_date     = $old_attendee['start_date'];
924 924
             $old_att_end_time     = $this->get_migration_script()->convertTimeFromAMPM(
925 925
                 $old_attendee['event_time']
@@ -938,10 +938,10 @@  discard block
 block discarded – undo
938 938
                     'DTT_deleted'   => true,
939 939
                 ],
940 940
                 [
941
-                    '%d',// EVT_ID
942
-                    '%s',// DTT_EVT_start
943
-                    '%s',// DTT_EVT_end
944
-                    '%d',// DTT_deleted
941
+                    '%d', // EVT_ID
942
+                    '%s', // DTT_EVT_start
943
+                    '%s', // DTT_EVT_end
944
+                    '%d', // DTT_deleted
945 945
                 ]
946 946
             );
947 947
             $datetime_id = $wpdb->insert_id;
@@ -949,7 +949,7 @@  discard block
 block discarded – undo
949 949
 
950 950
         // insert new ticket
951 951
         $wpdb->insert(
952
-            $wpdb->prefix . 'esp_ticket',
952
+            $wpdb->prefix.'esp_ticket',
953 953
             [
954 954
                 'TKT_name'       => $old_att_price_option,
955 955
                 'TKT_qty'        => -1,
@@ -960,11 +960,11 @@  discard block
 block discarded – undo
960 960
 
961 961
             ],
962 962
             [
963
-                '%s',// name
964
-                '%d',// qty
965
-                '%d',// price
966
-                '%s',// start_date
967
-                '%s',// end_date
963
+                '%s', // name
964
+                '%d', // qty
965
+                '%d', // price
966
+                '%s', // start_date
967
+                '%s', // end_date
968 968
             ]
969 969
         );
970 970
         $ticket_id = $wpdb->insert_id;
@@ -976,8 +976,8 @@  discard block
 block discarded – undo
976 976
                 'TKT_ID' => $ticket_id,
977 977
             ],
978 978
             [
979
-                '%d',// DTT_ID
980
-                '%d',// TKT_ID
979
+                '%d', // DTT_ID
980
+                '%d', // TKT_ID
981 981
             ]
982 982
         );
983 983
         // insert new price
@@ -990,10 +990,10 @@  discard block
 block discarded – undo
990 990
                 'PRC_deleted' => true,
991 991
             ],
992 992
             [
993
-                '%d',// PRC_amount
994
-                '%d',// PRT_ID
995
-                '%s',// PRC_name
996
-                '%d',// PRC_deleted
993
+                '%d', // PRC_amount
994
+                '%d', // PRT_ID
995
+                '%s', // PRC_name
996
+                '%d', // PRC_deleted
997 997
             ]
998 998
         );
999 999
         $price_id = $wpdb->insert_id;
@@ -1005,8 +1005,8 @@  discard block
 block discarded – undo
1005 1005
                 'PRC_ID' => $price_id,
1006 1006
             ],
1007 1007
             [
1008
-                '%d',// TKT_ID
1009
-                '%d',// PRC_ID
1008
+                '%d', // TKT_ID
1009
+                '%d', // PRC_ID
1010 1010
             ]
1011 1011
         );
1012 1012
         return $ticket_id;
@@ -1032,8 +1032,8 @@  discard block
 block discarded – undo
1032 1032
             intval(
1033 1033
                 $wpdb->get_var(
1034 1034
                     $wpdb->prepare(
1035
-                        "SELECT SUM(quantity) FROM " .
1036
-                        $this->_old_table .
1035
+                        "SELECT SUM(quantity) FROM ".
1036
+                        $this->_old_table.
1037 1037
                         " WHERE registration_id=%s $count_only_older_sql",
1038 1038
                         $old_attendee_row['registration_id']
1039 1039
                     )
@@ -1058,7 +1058,7 @@  discard block
 block discarded – undo
1058 1058
                         $primary_attendee['registration_id']
1059 1059
                     )
1060 1060
                 );
1061
-            $count                 = max($count_using_mer_table, $count);
1061
+            $count = max($count_using_mer_table, $count);
1062 1062
         }
1063 1063
         return $count;
1064 1064
     }
@@ -1086,7 +1086,7 @@  discard block
 block discarded – undo
1086 1086
             $by_admin = $old_attendee['payment'] == 'Admin';
1087 1087
 
1088 1088
             // IE, if we don't recognize the status, assume payment failed
1089
-            $STS_ID = $pay_status_mapping[ $old_attendee['payment_status'] ] ?? 'PFL';
1089
+            $STS_ID = $pay_status_mapping[$old_attendee['payment_status']] ?? 'PFL';
1090 1090
 
1091 1091
             $cols_n_values = [
1092 1092
                 'TXN_ID'               => $new_txn_id,
@@ -1106,20 +1106,20 @@  discard block
 block discarded – undo
1106 1106
                 'PAY_via_admin'        => $by_admin,
1107 1107
                 'PAY_details'          => $old_attendee['transaction_details'],
1108 1108
             ];
1109
-            $data_types    = [
1110
-                '%d',// TXN_Id
1111
-                '%s',// STS_ID
1112
-                '%s',// PAY_timestamp
1113
-                '%s',// PAY_method
1114
-                '%f',// PAY_amount
1115
-                '%s',// PAY_gateway
1116
-                '%s',// PAY_gateway_response
1117
-                '%s',// PAY_txn_id_chq_nmbr
1118
-                '%d',// PAY_via_admin
1119
-                '%s',// PAY_details
1109
+            $data_types = [
1110
+                '%d', // TXN_Id
1111
+                '%s', // STS_ID
1112
+                '%s', // PAY_timestamp
1113
+                '%s', // PAY_method
1114
+                '%f', // PAY_amount
1115
+                '%s', // PAY_gateway
1116
+                '%s', // PAY_gateway_response
1117
+                '%s', // PAY_txn_id_chq_nmbr
1118
+                '%d', // PAY_via_admin
1119
+                '%s', // PAY_details
1120 1120
             ];
1121
-            $success       = $wpdb->insert($this->_new_payment_table, $cols_n_values, $data_types);
1122
-            if (! $success) {
1121
+            $success = $wpdb->insert($this->_new_payment_table, $cols_n_values, $data_types);
1122
+            if ( ! $success) {
1123 1123
                 $this->add_error(
1124 1124
                     $this->get_migration_script()->_create_error_message_for_db_insertion(
1125 1125
                         $this->_old_table,
@@ -1146,7 +1146,7 @@  discard block
 block discarded – undo
1146 1146
      */
1147 1147
     private function _find_mer_primary_attendee_using_mer_tables(int $old_registration_id): ?array
1148 1148
     {
1149
-        if (! $this->_mer_tables_exist()) {
1149
+        if ( ! $this->_mer_tables_exist()) {
1150 1150
             return null;
1151 1151
         }
1152 1152
         global $wpdb;
Please login to merge, or discard this patch.
core/data_migration_scripts/4_1_0_stages/EE_DMS_4_1_0_events.dmsstage.php 2 patches
Indentation   +817 added lines, -817 removed lines patch added patch discarded remove patch
@@ -177,825 +177,825 @@
 block discarded – undo
177 177
 class EE_DMS_4_1_0_events extends EE_Data_Migration_Script_Stage
178 178
 {
179 179
 
180
-    private string $_old_start_end_table;
181
-
182
-    private string $_new_meta_table;
183
-
184
-    private string $_new_datetime_table;
185
-
186
-
187
-    /**
188
-     * Just initializes the status of the migration
189
-     */
190
-    public function __construct()
191
-    {
192
-        global $wpdb;
193
-        $this->_old_table           = $wpdb->prefix . "events_detail";
194
-        $this->_old_start_end_table = $wpdb->prefix . "events_start_end";
195
-        $this->_new_table           = $wpdb->prefix . "posts";
196
-        $this->_new_meta_table      = $wpdb->prefix . "esp_event_meta";
197
-        $this->_new_datetime_table  = $wpdb->prefix . "esp_datetime";
198
-        $this->_pretty_name         = esc_html__("Events", "event_espresso");
199
-        parent::__construct();
200
-    }
201
-
202
-
203
-    /**
204
-     * Counts the records to migrate; the public version may cache it
205
-     *
206
-     * @return int
207
-     */
208
-    public function _count_records_to_migrate(): int
209
-    {
210
-        global $wpdb;
211
-        return (int) $wpdb->get_var("SELECT COUNT(*) FROM " . $this->_old_table . ' WHERE event_status !="D"');
212
-    }
213
-
214
-
215
-    /**
216
-     * IMPORTANT: if an error is encountered, or everything is finished, this stage should update its status property
217
-     * accordingly. Note: it should not alter the count of items migrated. That is done in the public function that
218
-     * calls this. IMPORTANT: The count of items migrated should ONLY be less than $num_items_to_migrate when it's the
219
-     * last migration step, otherwise it should always return $num_items_to_migrate. (Eg, if we're migrating attendees
220
-     * rows from the database, and $num_items_to_migrate is set to 50, then we SHOULD actually migrate 50 rows,but at
221
-     * very least we MUST report/return 50 items migrated)
222
-     *
223
-     * @param int $num_items_to_migrate
224
-     * @return int number of items ACTUALLY migrated
225
-     * @throws EE_Error
226
-     */
227
-    protected function _migration_step($num_items_to_migrate = 50)
228
-    {
229
-        global $wpdb;
230
-        // because the migration of each event can be a LOT more work, make each step smaller
231
-        $num_items_to_migrate     = max(1, $num_items_to_migrate / 5);
232
-        $events                   =
233
-            $wpdb->get_results(
234
-                $wpdb->prepare(
235
-                    "SELECT * FROM $this->_old_table WHERE event_status!='D' LIMIT %d,%d",
236
-                    $this->count_records_migrated(),
237
-                    $num_items_to_migrate
238
-                ),
239
-                ARRAY_A
240
-            );
241
-        $items_migrated_this_step = 0;
242
-
243
-        foreach ($events as $event_row) {
244
-            $guid = null;
245
-            // insert new 4.1 Attendee object using $wpdb
246
-            $post_id = $this->_insert_cpt($event_row);
247
-            if ($post_id) {
248
-                $this->get_migration_script()->set_mapping(
249
-                    $this->_old_table,
250
-                    $event_row['id'],
251
-                    $this->_new_table,
252
-                    $post_id
253
-                );
254
-                $meta_id = $this->_insert_event_meta($event_row, $post_id);
255
-                if ($meta_id) {
256
-                    $this->get_migration_script()->set_mapping(
257
-                        $this->_old_table,
258
-                        $event_row['id'],
259
-                        $this->_new_meta_table,
260
-                        $meta_id
261
-                    );
262
-                }
263
-                $this->_convert_start_end_times($event_row, $post_id);
264
-                $event_meta = maybe_unserialize($event_row['event_meta']);
265
-                $guid       = $event_meta['event_thumbnail_url'] ?? null;
266
-                if ($guid) {
267
-                    $this->get_migration_script()->convert_image_url_to_attachment_and_attach_to_post(
268
-                        $guid,
269
-                        $post_id,
270
-                        $this
271
-                    );
272
-                }
273
-                // maybe create a venue from info on the event?
274
-                $new_venue_id = $this->_maybe_create_venue($event_row);
275
-                if ($new_venue_id) {
276
-                    $this->_insert_new_venue_to_event($post_id, $new_venue_id);
277
-                }
278
-                $this->_add_post_metas($event_row, $post_id);
279
-            }
280
-            $items_migrated_this_step++;
281
-            if ($guid) {
282
-                // if we had to check for an image attachment
283
-                // then let's call it a day (avoid timing out, because this took a long time)
284
-                break;
285
-            }
286
-        }
287
-        if ($this->count_records_migrated() + $items_migrated_this_step >= $this->count_records_to_migrate()) {
288
-            $this->set_status(EE_Data_Migration_Manager::status_completed);
289
-        }
290
-        return $items_migrated_this_step;
291
-    }
292
-
293
-
294
-    /**
295
-     * Stores any extra 3.1 "event_meta" column things as post meta
296
-     *
297
-     * @param array $old_event
298
-     * @param int   $post_id
299
-     * @return void
300
-     */
301
-    private function _add_post_metas(array $old_event, int $post_id)
302
-    {
303
-        $event_meta = maybe_unserialize($old_event['event_meta']);
304
-        if (! $event_meta || ! is_array($event_meta)) {
305
-            return;
306
-        }
307
-        unset($event_meta['date_submitted']);              // factored into CPT
308
-        unset($event_meta['additional_attendee_reg_info']);// factored into event meta table
309
-        unset($event_meta['default_payment_status']);      // dido
310
-        unset($event_meta['event_thumbnail_url']);         // used to find post featured image
311
-        foreach ($event_meta as $meta_key => $meta_value) {
312
-            if ($meta_key) {// if the meta key is just an empty string, ignore it
313
-                $success = add_post_meta($post_id, $meta_key, $meta_value, true);
314
-                if (! $success) {
315
-                    $this->add_error(
316
-                        sprintf(
317
-                            esc_html__(
318
-                                "Could not add post meta for CPT with ID #%d. Meta key: '%s',meta value:'%d' for 3.1 event: %s",
319
-                                "event_espresso"
320
-                            ),
321
-                            $post_id,
322
-                            $meta_key,
323
-                            $meta_value,
324
-                            implode(",", $old_event)
325
-                        )
326
-                    );
327
-                }
328
-            }
329
-        }
330
-        if ($old_event['alt_email']) {
331
-            add_post_meta($post_id, 'alt_email', $old_event['alt_email']);
332
-        }
333
-        if ($old_event['recurrence_id']) {
334
-            add_post_meta($post_id, 'recurrence_id', $old_event['recurrence_id']);
335
-        }
336
-    }
337
-
338
-
339
-    /**
340
-     * Finds a unique slug for this event, given its name (we could have simply used
341
-     * the old unique_identifier column, but it added a long string of seemingly random characters onto the end
342
-     * and really wasn't that pretty for a slug, so we decided we'd make our own slug again)
343
-     *
344
-     * @param string $event_name      (the name of the event for reading by humans)
345
-     * @param string $old_identifier  the old EE3 identifier (a long unique string)
346
-     * @param string $new_post_status a post status
347
-     * @return string
348
-     */
349
-    private function _find_unique_slug(
350
-        string $event_name,
351
-        string $old_identifier = '',
352
-        string $new_post_status = 'publish'
353
-    ): string {
354
-        $original_name = $event_name ? sanitize_title($event_name) : $old_identifier;
355
-        return wp_unique_post_slug($original_name, 0, $new_post_status, EspressoPostType::EVENTS, 0);
356
-    }
357
-
358
-
359
-    /**
360
-     * returns whether there is a post that has this same slug (post_title)
361
-     *
362
-     * @param string $slug
363
-     * @return boolean
364
-     * @global wpdb  $wpdb
365
-     */
366
-    private function _other_post_exists_with_that_slug(string $slug): bool
367
-    {
368
-        global $wpdb;
369
-        $query =
370
-            $wpdb->prepare(
371
-                "SELECT COUNT(ID) FROM $this->_new_table WHERE event_status != 'D' AND post_name = %s",
372
-                $slug
373
-            );
374
-        $count = $wpdb->get_var($query);
375
-        return (bool) intval($count);
376
-    }
377
-
378
-
379
-    /**
380
-     * @param $old_event
381
-     * @return int
382
-     */
383
-    private function _insert_cpt($old_event): int
384
-    {
385
-        global $wpdb;
386
-        // convert 3.1 event status to 4.1 CPT status
387
-        // for reference, 3.1 event stati available for setting are:
388
-        //      $status = array(array('id' => 'A', 'text' => esc_html__('Public', 'event_espresso')), array('id' => 'S', 'text' => esc_html__('Waitlist', 'event_espresso')), array('id' => 'O', 'text' => esc_html__('Ongoing', 'event_espresso')), array('id' => 'R', 'text' => esc_html__('Draft', 'event_espresso')), array('id' => 'D', 'text' => esc_html__('Deleted', 'event_espresso')));
389
-        //      and the json api uses the following to convert from 3.1 to 4.0
390
-        //      'S'=>'secondary/waitlist',
391
-        //      'A'=>'active',
392
-        //      'X'=>'denied',
393
-        //      'IA'=>'inactive',
394
-        //      'O'=>'ongoing',
395
-        //      'P'=>'pending',
396
-        //      'R'=>'draft',
397
-        //      'D'=>'deleted');
398
-        //      4.1 Event Post stati are the normal post statis
399
-        //      (publish,future,draft,pending,private,trash,auto-draft,inherit)
400
-        //      and 3 custom ones: cancelled,postponed,sold_out
401
-        $status_conversions = [
402
-            'R'  => 'draft',
403
-            'X'  => 'draft',
404
-            // 4.1 doesn't have a "not approved for publishing" status. this is what posts are set to that aren't approved
405
-            'P'  => 'pending',
406
-            'IA' => 'draft',
407
-            // draft and in the past
408
-            // IA=inactive in 3.1: events were switched to this when they expired. in 4.1 that's just calculated
409
-            'O'  => 'publish',
410
-            // @todo: will be an event type later; if this is the status, set the end date WAAAY later; and add term for 'ongoing'
411
-            'A'  => 'publish',
412
-            'S'  => 'draft',
413
-            // @todo: is it ok to just mark secondary/waitlist events as DRAFTS?
414
-            'D'  => 'trash',
415
-        ];
416
-        $post_status        = $status_conversions[ $old_event['event_status'] ];
417
-        // check if we've sold out
418
-        if (intval($old_event['reg_limit']) <= self::count_registrations($old_event['id'])) {
419
-            $post_status = 'sold_out';
420
-        }
421
-        //      FYI postponed and cancelled don't exist in 3.1
422
-        $cols_n_values                      = [
423
-            'post_title'        => stripslashes($old_event['event_name']),// EVT_name
424
-            'post_content'      => stripslashes($old_event['event_desc']),// EVT_desc
425
-            'post_name'         => $this->_find_unique_slug(
426
-                $old_event['event_name'],
427
-                $old_event['event_identifier'],
428
-                $post_status
429
-            ),// EVT_slug
430
-            'post_date'         => $old_event['submitted'],// EVT_created NOT
431
-            'post_date_gmt'     => get_gmt_from_date($old_event['submitted']),
432
-            'post_excerpt'      => '',// EVT_short_desc
433
-            'post_modified'     => $old_event['submitted'],// EVT_modified
434
-            'post_modified_gmt' => get_gmt_from_date($old_event['submitted']),
435
-            'post_author'       => $old_event['wp_user'],// EVT_wp_user
436
-            'post_parent'       => 0,// parent maybe get this from some REM field?
437
-            'menu_order'        => 0,// EVT_order
438
-            'post_type'         => EspressoPostType::EVENTS,// post_type
439
-            'post_status'       => $post_status,// status
440
-        ];
441
-        $cols_n_values_with_no_invalid_text = [];
442
-        foreach ($cols_n_values as $col => $value) {
443
-            $value_sans_invalid_chars = $wpdb->strip_invalid_text_for_column($this->_new_table, $col, $value);
444
-            if (! is_wp_error($value_sans_invalid_chars)) {
445
-                $cols_n_values_with_no_invalid_text[ $col ] = $value_sans_invalid_chars;
446
-            } else {
447
-                // otherwise leave it as-is. It will blow everything up and stop the migration
448
-                $cols_n_values_with_no_invalid_text[ $col ] = $value;
449
-            }
450
-        }
451
-        $cols_n_values = $cols_n_values_with_no_invalid_text;
452
-        $data_types    = [
453
-            '%s',// EVT_name
454
-            '%s',// EVT_desc
455
-            '%s',// EVT_slug
456
-            '%s',// EVT_created
457
-            '%s',
458
-            '%s',// EVT_short_desc
459
-            '%s',// EVT_modified
460
-            '%s',
461
-            '%s',// EVT_wp_user
462
-            '%d',// post_parent
463
-            '%d',// EVT_order
464
-            '%s',// post_type
465
-            '%s',// status
466
-        ];
467
-        $success       = $wpdb->insert(
468
-            $this->_new_table,
469
-            $cols_n_values,
470
-            $data_types
471
-        );
472
-        if (! $success) {
473
-            $this->add_error(
474
-                $this->get_migration_script()->_create_error_message_for_db_insertion(
475
-                    $this->_old_table,
476
-                    $old_event,
477
-                    $this->_new_table,
478
-                    $cols_n_values,
479
-                    $data_types
480
-                )
481
-            );
482
-            return 0;
483
-        }
484
-        return $wpdb->insert_id;
485
-    }
486
-
487
-
488
-    /**
489
-     * Counts all the registrations for the event in the 3.1 DB. (takes into account attendee rows which represent
490
-     * various registrations)
491
-     *
492
-     * @param int   $event_id
493
-     * @return int
494
-     * @global wpdb $wpdb
495
-     */
496
-    public static function count_registrations(int $event_id): int
497
-    {
498
-        global $wpdb;
499
-        return (int) $wpdb->get_var(
500
-            $wpdb->prepare("SELECT sum(quantity) FROM {$wpdb->prefix}events_attendee WHERE event_id=%d", $event_id)
501
-        );
502
-    }
503
-
504
-
505
-    /**
506
-     * @param $old_event
507
-     * @param $new_cpt_id
508
-     * @return int
509
-     */
510
-    private function _insert_event_meta($old_event, $new_cpt_id): int
511
-    {
512
-        global $wpdb;
513
-        $event_meta = maybe_unserialize($old_event['event_meta']);
514
-        //      for reference, 3.1 'default_payment_status' are: $default_payment_status = array(
515
-        //  array('id' => "", 'text' => 'No Change'),
516
-        //  array('id' => 'Incomplete', 'text' => 'Incomplete'),
517
-        //  array('id' => 'Pending', 'text' => 'Pending'),
518
-        //  //array('id' => 'Completed', 'text' => 'Completed')
519
-        // );
520
-        $default_reg_status =
521
-            $this->get_migration_script()->convert_3_1_payment_status_to_4_1_STS_ID(
522
-                $event_meta['default_payment_status'] ?? '',
523
-                intval($old_event['require_pre_approval'])
524
-            );
525
-        $cols_n_values      = [
526
-            'EVT_ID'                          => $new_cpt_id,// EVT_ID_fk
527
-            'EVT_display_desc'                => 'Y' == $old_event['display_desc'],
528
-            'EVT_display_ticket_selector'     => 'Y' == $old_event['display_reg_form'],
529
-            'EVT_visible_on'                  => $this->get_migration_script()->convert_date_string_to_utc(
530
-                $this,
531
-                $old_event,
532
-                current_time(
533
-                    'mysql'
534
-                ),
535
-                $old_event['timezone_string']
536
-            ),// don't use the old 'visible_on', as it wasn't ever used
537
-            'EVT_additional_limit'            => $old_event['allow_multiple'] == 'N' ? 1
538
-                : $old_event['additional_limit'],
539
-            'EVT_default_registration_status' => $default_reg_status,
540
-            'EVT_member_only'                 => $old_event['member_only'],
541
-            'EVT_phone'                       => $old_event['phone'],
542
-            'EVT_allow_overflow'              => 'Y' == $old_event['allow_overflow'],
543
-            'EVT_timezone_string'             => $old_event['timezone_string'],
544
-            'EVT_external_URL'                => $old_event['externalURL'],
545
-            'EVT_donations'                   => false,// doesn't exist in 3.1
546
-
547
-        ];
548
-        $data_types         = [
549
-            '%s',// EVT_ID
550
-            '%d',// EVT_display_desc
551
-            '%d',// EVT_display_ticket_selector
552
-            '%s',// EVT_visible_on
553
-            '%d',// EVT_additional_limit
554
-            '%s',// EVT_default_registration_status
555
-            '%d',// EVT_member_only
556
-            '%s',// EVT_phone
557
-            '%d',// EVT_allow_overflow
558
-            '%s',// EVT_timezone_string
559
-            '%s',// EVT_external_URL
560
-            '%d',// EVT_donations
561
-        ];
562
-        $success            = $wpdb->insert(
563
-            $this->_new_meta_table,
564
-            $cols_n_values,
565
-            $data_types
566
-        );
567
-        if (! $success) {
568
-            $this->add_error(
569
-                $this->get_migration_script()->_create_error_message_for_db_insertion(
570
-                    $this->_old_table,
571
-                    $old_event,
572
-                    $this->_new_meta_table,
573
-                    $cols_n_values,
574
-                    $data_types
575
-                )
576
-            );
577
-            return 0;
578
-        }
579
-        return $wpdb->insert_id;
580
-    }
581
-
582
-
583
-    /**
584
-     * @param $old_event
585
-     * @return int
586
-     */
587
-    private function _maybe_create_venue($old_event): int
588
-    {
589
-        if (
590
-            $old_event['address'] ||
591
-            $old_event['address2'] ||
592
-            $old_event['city'] ||
593
-            $old_event['state'] ||
594
-            $old_event['zip'] ||
595
-            $old_event['venue_title'] ||
596
-            $old_event['venue_url'] ||
597
-            $old_event['venue_image'] ||
598
-            $old_event['venue_phone'] ||
599
-            $old_event['virtual_url'] ||
600
-            $old_event['virtual_phone']
601
-        ) {
602
-            $old_id = $this->_duplicate_venue_exists($old_event);
603
-            if ($old_id) {
604
-                return $old_id;
605
-            }
606
-            $new_id = $this->_insert_venue_into_posts($old_event);
607
-            if ($new_id) {
608
-                $this->_insert_venue_into_meta_table($new_id, $old_event);
609
-                $guid = $old_event['venue_image'] ?? null;
610
-                if($guid) {
611
-                    $this->get_migration_script()->convert_image_url_to_attachment_and_attach_to_post(
612
-                        $guid,
613
-                        $new_id,
614
-                        $this
615
-                    );
616
-                }
617
-            }
618
-            // we don't bother recording the conversion from old events to venues as that
619
-            // will complicate finding the conversion from old venues to new events
620
-            return $new_id;
621
-        }
622
-        return 0;
623
-    }
624
-
625
-
626
-    /**
627
-     * Assuming there is venue data on this event, check if there is a duplicate venue already in the system for it.
628
-     * If so, return it. Otherwise, return NULL.
629
-     *
630
-     * @param array $old_event
631
-     * @return int duplicate venue id
632
-     */
633
-    private function _duplicate_venue_exists(array $old_event): int
634
-    {
635
-        global $wpdb;
636
-        $conditions     = [
637
-            'VNU_address'       => $old_event ['address'],
638
-            'VNU_address2'      => $old_event['address2'],
639
-            'VNU_city'          => $old_event['city'],
640
-            'VNU_zip'           => $old_event['zip'],
641
-            'post_title'        => $this->_get_venue_title_for_event($old_event),
642
-            'VNU_phone'         => $old_event['venue_phone'],// VNU_phone
643
-            'VNU_url'           => $old_event['venue_url'],// VNU_url
644
-            'VNU_virtual_phone' => $old_event['virtual_phone'],// VNU_virtual_phone
645
-            'VNU_virtual_url'   => $old_event['virtual_url'],// VNU_virtual_url
646
-        ];
647
-        $sql_conditions = [];
648
-        foreach ($conditions as $column => $value) {
649
-            $sql_conditions [] = $wpdb->prepare("$column = %s", $value);
650
-        }
651
-        $query = "SELECT VNU_ID
180
+	private string $_old_start_end_table;
181
+
182
+	private string $_new_meta_table;
183
+
184
+	private string $_new_datetime_table;
185
+
186
+
187
+	/**
188
+	 * Just initializes the status of the migration
189
+	 */
190
+	public function __construct()
191
+	{
192
+		global $wpdb;
193
+		$this->_old_table           = $wpdb->prefix . "events_detail";
194
+		$this->_old_start_end_table = $wpdb->prefix . "events_start_end";
195
+		$this->_new_table           = $wpdb->prefix . "posts";
196
+		$this->_new_meta_table      = $wpdb->prefix . "esp_event_meta";
197
+		$this->_new_datetime_table  = $wpdb->prefix . "esp_datetime";
198
+		$this->_pretty_name         = esc_html__("Events", "event_espresso");
199
+		parent::__construct();
200
+	}
201
+
202
+
203
+	/**
204
+	 * Counts the records to migrate; the public version may cache it
205
+	 *
206
+	 * @return int
207
+	 */
208
+	public function _count_records_to_migrate(): int
209
+	{
210
+		global $wpdb;
211
+		return (int) $wpdb->get_var("SELECT COUNT(*) FROM " . $this->_old_table . ' WHERE event_status !="D"');
212
+	}
213
+
214
+
215
+	/**
216
+	 * IMPORTANT: if an error is encountered, or everything is finished, this stage should update its status property
217
+	 * accordingly. Note: it should not alter the count of items migrated. That is done in the public function that
218
+	 * calls this. IMPORTANT: The count of items migrated should ONLY be less than $num_items_to_migrate when it's the
219
+	 * last migration step, otherwise it should always return $num_items_to_migrate. (Eg, if we're migrating attendees
220
+	 * rows from the database, and $num_items_to_migrate is set to 50, then we SHOULD actually migrate 50 rows,but at
221
+	 * very least we MUST report/return 50 items migrated)
222
+	 *
223
+	 * @param int $num_items_to_migrate
224
+	 * @return int number of items ACTUALLY migrated
225
+	 * @throws EE_Error
226
+	 */
227
+	protected function _migration_step($num_items_to_migrate = 50)
228
+	{
229
+		global $wpdb;
230
+		// because the migration of each event can be a LOT more work, make each step smaller
231
+		$num_items_to_migrate     = max(1, $num_items_to_migrate / 5);
232
+		$events                   =
233
+			$wpdb->get_results(
234
+				$wpdb->prepare(
235
+					"SELECT * FROM $this->_old_table WHERE event_status!='D' LIMIT %d,%d",
236
+					$this->count_records_migrated(),
237
+					$num_items_to_migrate
238
+				),
239
+				ARRAY_A
240
+			);
241
+		$items_migrated_this_step = 0;
242
+
243
+		foreach ($events as $event_row) {
244
+			$guid = null;
245
+			// insert new 4.1 Attendee object using $wpdb
246
+			$post_id = $this->_insert_cpt($event_row);
247
+			if ($post_id) {
248
+				$this->get_migration_script()->set_mapping(
249
+					$this->_old_table,
250
+					$event_row['id'],
251
+					$this->_new_table,
252
+					$post_id
253
+				);
254
+				$meta_id = $this->_insert_event_meta($event_row, $post_id);
255
+				if ($meta_id) {
256
+					$this->get_migration_script()->set_mapping(
257
+						$this->_old_table,
258
+						$event_row['id'],
259
+						$this->_new_meta_table,
260
+						$meta_id
261
+					);
262
+				}
263
+				$this->_convert_start_end_times($event_row, $post_id);
264
+				$event_meta = maybe_unserialize($event_row['event_meta']);
265
+				$guid       = $event_meta['event_thumbnail_url'] ?? null;
266
+				if ($guid) {
267
+					$this->get_migration_script()->convert_image_url_to_attachment_and_attach_to_post(
268
+						$guid,
269
+						$post_id,
270
+						$this
271
+					);
272
+				}
273
+				// maybe create a venue from info on the event?
274
+				$new_venue_id = $this->_maybe_create_venue($event_row);
275
+				if ($new_venue_id) {
276
+					$this->_insert_new_venue_to_event($post_id, $new_venue_id);
277
+				}
278
+				$this->_add_post_metas($event_row, $post_id);
279
+			}
280
+			$items_migrated_this_step++;
281
+			if ($guid) {
282
+				// if we had to check for an image attachment
283
+				// then let's call it a day (avoid timing out, because this took a long time)
284
+				break;
285
+			}
286
+		}
287
+		if ($this->count_records_migrated() + $items_migrated_this_step >= $this->count_records_to_migrate()) {
288
+			$this->set_status(EE_Data_Migration_Manager::status_completed);
289
+		}
290
+		return $items_migrated_this_step;
291
+	}
292
+
293
+
294
+	/**
295
+	 * Stores any extra 3.1 "event_meta" column things as post meta
296
+	 *
297
+	 * @param array $old_event
298
+	 * @param int   $post_id
299
+	 * @return void
300
+	 */
301
+	private function _add_post_metas(array $old_event, int $post_id)
302
+	{
303
+		$event_meta = maybe_unserialize($old_event['event_meta']);
304
+		if (! $event_meta || ! is_array($event_meta)) {
305
+			return;
306
+		}
307
+		unset($event_meta['date_submitted']);              // factored into CPT
308
+		unset($event_meta['additional_attendee_reg_info']);// factored into event meta table
309
+		unset($event_meta['default_payment_status']);      // dido
310
+		unset($event_meta['event_thumbnail_url']);         // used to find post featured image
311
+		foreach ($event_meta as $meta_key => $meta_value) {
312
+			if ($meta_key) {// if the meta key is just an empty string, ignore it
313
+				$success = add_post_meta($post_id, $meta_key, $meta_value, true);
314
+				if (! $success) {
315
+					$this->add_error(
316
+						sprintf(
317
+							esc_html__(
318
+								"Could not add post meta for CPT with ID #%d. Meta key: '%s',meta value:'%d' for 3.1 event: %s",
319
+								"event_espresso"
320
+							),
321
+							$post_id,
322
+							$meta_key,
323
+							$meta_value,
324
+							implode(",", $old_event)
325
+						)
326
+					);
327
+				}
328
+			}
329
+		}
330
+		if ($old_event['alt_email']) {
331
+			add_post_meta($post_id, 'alt_email', $old_event['alt_email']);
332
+		}
333
+		if ($old_event['recurrence_id']) {
334
+			add_post_meta($post_id, 'recurrence_id', $old_event['recurrence_id']);
335
+		}
336
+	}
337
+
338
+
339
+	/**
340
+	 * Finds a unique slug for this event, given its name (we could have simply used
341
+	 * the old unique_identifier column, but it added a long string of seemingly random characters onto the end
342
+	 * and really wasn't that pretty for a slug, so we decided we'd make our own slug again)
343
+	 *
344
+	 * @param string $event_name      (the name of the event for reading by humans)
345
+	 * @param string $old_identifier  the old EE3 identifier (a long unique string)
346
+	 * @param string $new_post_status a post status
347
+	 * @return string
348
+	 */
349
+	private function _find_unique_slug(
350
+		string $event_name,
351
+		string $old_identifier = '',
352
+		string $new_post_status = 'publish'
353
+	): string {
354
+		$original_name = $event_name ? sanitize_title($event_name) : $old_identifier;
355
+		return wp_unique_post_slug($original_name, 0, $new_post_status, EspressoPostType::EVENTS, 0);
356
+	}
357
+
358
+
359
+	/**
360
+	 * returns whether there is a post that has this same slug (post_title)
361
+	 *
362
+	 * @param string $slug
363
+	 * @return boolean
364
+	 * @global wpdb  $wpdb
365
+	 */
366
+	private function _other_post_exists_with_that_slug(string $slug): bool
367
+	{
368
+		global $wpdb;
369
+		$query =
370
+			$wpdb->prepare(
371
+				"SELECT COUNT(ID) FROM $this->_new_table WHERE event_status != 'D' AND post_name = %s",
372
+				$slug
373
+			);
374
+		$count = $wpdb->get_var($query);
375
+		return (bool) intval($count);
376
+	}
377
+
378
+
379
+	/**
380
+	 * @param $old_event
381
+	 * @return int
382
+	 */
383
+	private function _insert_cpt($old_event): int
384
+	{
385
+		global $wpdb;
386
+		// convert 3.1 event status to 4.1 CPT status
387
+		// for reference, 3.1 event stati available for setting are:
388
+		//      $status = array(array('id' => 'A', 'text' => esc_html__('Public', 'event_espresso')), array('id' => 'S', 'text' => esc_html__('Waitlist', 'event_espresso')), array('id' => 'O', 'text' => esc_html__('Ongoing', 'event_espresso')), array('id' => 'R', 'text' => esc_html__('Draft', 'event_espresso')), array('id' => 'D', 'text' => esc_html__('Deleted', 'event_espresso')));
389
+		//      and the json api uses the following to convert from 3.1 to 4.0
390
+		//      'S'=>'secondary/waitlist',
391
+		//      'A'=>'active',
392
+		//      'X'=>'denied',
393
+		//      'IA'=>'inactive',
394
+		//      'O'=>'ongoing',
395
+		//      'P'=>'pending',
396
+		//      'R'=>'draft',
397
+		//      'D'=>'deleted');
398
+		//      4.1 Event Post stati are the normal post statis
399
+		//      (publish,future,draft,pending,private,trash,auto-draft,inherit)
400
+		//      and 3 custom ones: cancelled,postponed,sold_out
401
+		$status_conversions = [
402
+			'R'  => 'draft',
403
+			'X'  => 'draft',
404
+			// 4.1 doesn't have a "not approved for publishing" status. this is what posts are set to that aren't approved
405
+			'P'  => 'pending',
406
+			'IA' => 'draft',
407
+			// draft and in the past
408
+			// IA=inactive in 3.1: events were switched to this when they expired. in 4.1 that's just calculated
409
+			'O'  => 'publish',
410
+			// @todo: will be an event type later; if this is the status, set the end date WAAAY later; and add term for 'ongoing'
411
+			'A'  => 'publish',
412
+			'S'  => 'draft',
413
+			// @todo: is it ok to just mark secondary/waitlist events as DRAFTS?
414
+			'D'  => 'trash',
415
+		];
416
+		$post_status        = $status_conversions[ $old_event['event_status'] ];
417
+		// check if we've sold out
418
+		if (intval($old_event['reg_limit']) <= self::count_registrations($old_event['id'])) {
419
+			$post_status = 'sold_out';
420
+		}
421
+		//      FYI postponed and cancelled don't exist in 3.1
422
+		$cols_n_values                      = [
423
+			'post_title'        => stripslashes($old_event['event_name']),// EVT_name
424
+			'post_content'      => stripslashes($old_event['event_desc']),// EVT_desc
425
+			'post_name'         => $this->_find_unique_slug(
426
+				$old_event['event_name'],
427
+				$old_event['event_identifier'],
428
+				$post_status
429
+			),// EVT_slug
430
+			'post_date'         => $old_event['submitted'],// EVT_created NOT
431
+			'post_date_gmt'     => get_gmt_from_date($old_event['submitted']),
432
+			'post_excerpt'      => '',// EVT_short_desc
433
+			'post_modified'     => $old_event['submitted'],// EVT_modified
434
+			'post_modified_gmt' => get_gmt_from_date($old_event['submitted']),
435
+			'post_author'       => $old_event['wp_user'],// EVT_wp_user
436
+			'post_parent'       => 0,// parent maybe get this from some REM field?
437
+			'menu_order'        => 0,// EVT_order
438
+			'post_type'         => EspressoPostType::EVENTS,// post_type
439
+			'post_status'       => $post_status,// status
440
+		];
441
+		$cols_n_values_with_no_invalid_text = [];
442
+		foreach ($cols_n_values as $col => $value) {
443
+			$value_sans_invalid_chars = $wpdb->strip_invalid_text_for_column($this->_new_table, $col, $value);
444
+			if (! is_wp_error($value_sans_invalid_chars)) {
445
+				$cols_n_values_with_no_invalid_text[ $col ] = $value_sans_invalid_chars;
446
+			} else {
447
+				// otherwise leave it as-is. It will blow everything up and stop the migration
448
+				$cols_n_values_with_no_invalid_text[ $col ] = $value;
449
+			}
450
+		}
451
+		$cols_n_values = $cols_n_values_with_no_invalid_text;
452
+		$data_types    = [
453
+			'%s',// EVT_name
454
+			'%s',// EVT_desc
455
+			'%s',// EVT_slug
456
+			'%s',// EVT_created
457
+			'%s',
458
+			'%s',// EVT_short_desc
459
+			'%s',// EVT_modified
460
+			'%s',
461
+			'%s',// EVT_wp_user
462
+			'%d',// post_parent
463
+			'%d',// EVT_order
464
+			'%s',// post_type
465
+			'%s',// status
466
+		];
467
+		$success       = $wpdb->insert(
468
+			$this->_new_table,
469
+			$cols_n_values,
470
+			$data_types
471
+		);
472
+		if (! $success) {
473
+			$this->add_error(
474
+				$this->get_migration_script()->_create_error_message_for_db_insertion(
475
+					$this->_old_table,
476
+					$old_event,
477
+					$this->_new_table,
478
+					$cols_n_values,
479
+					$data_types
480
+				)
481
+			);
482
+			return 0;
483
+		}
484
+		return $wpdb->insert_id;
485
+	}
486
+
487
+
488
+	/**
489
+	 * Counts all the registrations for the event in the 3.1 DB. (takes into account attendee rows which represent
490
+	 * various registrations)
491
+	 *
492
+	 * @param int   $event_id
493
+	 * @return int
494
+	 * @global wpdb $wpdb
495
+	 */
496
+	public static function count_registrations(int $event_id): int
497
+	{
498
+		global $wpdb;
499
+		return (int) $wpdb->get_var(
500
+			$wpdb->prepare("SELECT sum(quantity) FROM {$wpdb->prefix}events_attendee WHERE event_id=%d", $event_id)
501
+		);
502
+	}
503
+
504
+
505
+	/**
506
+	 * @param $old_event
507
+	 * @param $new_cpt_id
508
+	 * @return int
509
+	 */
510
+	private function _insert_event_meta($old_event, $new_cpt_id): int
511
+	{
512
+		global $wpdb;
513
+		$event_meta = maybe_unserialize($old_event['event_meta']);
514
+		//      for reference, 3.1 'default_payment_status' are: $default_payment_status = array(
515
+		//  array('id' => "", 'text' => 'No Change'),
516
+		//  array('id' => 'Incomplete', 'text' => 'Incomplete'),
517
+		//  array('id' => 'Pending', 'text' => 'Pending'),
518
+		//  //array('id' => 'Completed', 'text' => 'Completed')
519
+		// );
520
+		$default_reg_status =
521
+			$this->get_migration_script()->convert_3_1_payment_status_to_4_1_STS_ID(
522
+				$event_meta['default_payment_status'] ?? '',
523
+				intval($old_event['require_pre_approval'])
524
+			);
525
+		$cols_n_values      = [
526
+			'EVT_ID'                          => $new_cpt_id,// EVT_ID_fk
527
+			'EVT_display_desc'                => 'Y' == $old_event['display_desc'],
528
+			'EVT_display_ticket_selector'     => 'Y' == $old_event['display_reg_form'],
529
+			'EVT_visible_on'                  => $this->get_migration_script()->convert_date_string_to_utc(
530
+				$this,
531
+				$old_event,
532
+				current_time(
533
+					'mysql'
534
+				),
535
+				$old_event['timezone_string']
536
+			),// don't use the old 'visible_on', as it wasn't ever used
537
+			'EVT_additional_limit'            => $old_event['allow_multiple'] == 'N' ? 1
538
+				: $old_event['additional_limit'],
539
+			'EVT_default_registration_status' => $default_reg_status,
540
+			'EVT_member_only'                 => $old_event['member_only'],
541
+			'EVT_phone'                       => $old_event['phone'],
542
+			'EVT_allow_overflow'              => 'Y' == $old_event['allow_overflow'],
543
+			'EVT_timezone_string'             => $old_event['timezone_string'],
544
+			'EVT_external_URL'                => $old_event['externalURL'],
545
+			'EVT_donations'                   => false,// doesn't exist in 3.1
546
+
547
+		];
548
+		$data_types         = [
549
+			'%s',// EVT_ID
550
+			'%d',// EVT_display_desc
551
+			'%d',// EVT_display_ticket_selector
552
+			'%s',// EVT_visible_on
553
+			'%d',// EVT_additional_limit
554
+			'%s',// EVT_default_registration_status
555
+			'%d',// EVT_member_only
556
+			'%s',// EVT_phone
557
+			'%d',// EVT_allow_overflow
558
+			'%s',// EVT_timezone_string
559
+			'%s',// EVT_external_URL
560
+			'%d',// EVT_donations
561
+		];
562
+		$success            = $wpdb->insert(
563
+			$this->_new_meta_table,
564
+			$cols_n_values,
565
+			$data_types
566
+		);
567
+		if (! $success) {
568
+			$this->add_error(
569
+				$this->get_migration_script()->_create_error_message_for_db_insertion(
570
+					$this->_old_table,
571
+					$old_event,
572
+					$this->_new_meta_table,
573
+					$cols_n_values,
574
+					$data_types
575
+				)
576
+			);
577
+			return 0;
578
+		}
579
+		return $wpdb->insert_id;
580
+	}
581
+
582
+
583
+	/**
584
+	 * @param $old_event
585
+	 * @return int
586
+	 */
587
+	private function _maybe_create_venue($old_event): int
588
+	{
589
+		if (
590
+			$old_event['address'] ||
591
+			$old_event['address2'] ||
592
+			$old_event['city'] ||
593
+			$old_event['state'] ||
594
+			$old_event['zip'] ||
595
+			$old_event['venue_title'] ||
596
+			$old_event['venue_url'] ||
597
+			$old_event['venue_image'] ||
598
+			$old_event['venue_phone'] ||
599
+			$old_event['virtual_url'] ||
600
+			$old_event['virtual_phone']
601
+		) {
602
+			$old_id = $this->_duplicate_venue_exists($old_event);
603
+			if ($old_id) {
604
+				return $old_id;
605
+			}
606
+			$new_id = $this->_insert_venue_into_posts($old_event);
607
+			if ($new_id) {
608
+				$this->_insert_venue_into_meta_table($new_id, $old_event);
609
+				$guid = $old_event['venue_image'] ?? null;
610
+				if($guid) {
611
+					$this->get_migration_script()->convert_image_url_to_attachment_and_attach_to_post(
612
+						$guid,
613
+						$new_id,
614
+						$this
615
+					);
616
+				}
617
+			}
618
+			// we don't bother recording the conversion from old events to venues as that
619
+			// will complicate finding the conversion from old venues to new events
620
+			return $new_id;
621
+		}
622
+		return 0;
623
+	}
624
+
625
+
626
+	/**
627
+	 * Assuming there is venue data on this event, check if there is a duplicate venue already in the system for it.
628
+	 * If so, return it. Otherwise, return NULL.
629
+	 *
630
+	 * @param array $old_event
631
+	 * @return int duplicate venue id
632
+	 */
633
+	private function _duplicate_venue_exists(array $old_event): int
634
+	{
635
+		global $wpdb;
636
+		$conditions     = [
637
+			'VNU_address'       => $old_event ['address'],
638
+			'VNU_address2'      => $old_event['address2'],
639
+			'VNU_city'          => $old_event['city'],
640
+			'VNU_zip'           => $old_event['zip'],
641
+			'post_title'        => $this->_get_venue_title_for_event($old_event),
642
+			'VNU_phone'         => $old_event['venue_phone'],// VNU_phone
643
+			'VNU_url'           => $old_event['venue_url'],// VNU_url
644
+			'VNU_virtual_phone' => $old_event['virtual_phone'],// VNU_virtual_phone
645
+			'VNU_virtual_url'   => $old_event['virtual_url'],// VNU_virtual_url
646
+		];
647
+		$sql_conditions = [];
648
+		foreach ($conditions as $column => $value) {
649
+			$sql_conditions [] = $wpdb->prepare("$column = %s", $value);
650
+		}
651
+		$query = "SELECT VNU_ID
652 652
 					FROM
653 653
 		$wpdb->posts as p INNER JOIN
654 654
 		{$wpdb->prefix}esp_venue_meta as v ON p.ID = v.VNU_ID
655 655
 			WHERE " . implode(" AND ", $sql_conditions) . " LIMIT 1";
656
-        return (int) $wpdb->get_var($query);
657
-    }
658
-
659
-
660
-    /**
661
-     * Gets teh venue's title or makes one up if there is none
662
-     *
663
-     * @param array $event_data_array keys are events_details columns and values are their values
664
-     * @return string
665
-     */
666
-    protected function _get_venue_title_for_event(array $event_data_array): string
667
-    {
668
-        return $event_data_array['venue_title']
669
-            ? stripslashes($event_data_array['venue_title'])
670
-            : stripslashes(
671
-                sprintf(
672
-                    esc_html__('Venue of %s', 'event_espresso'),
673
-                    $event_data_array['event_name']
674
-                )
675
-            );
676
-    }
677
-
678
-
679
-    /**
680
-     * Inserts the CPT
681
-     *
682
-     * @param array $old_event keys are cols, values are col values
683
-     * @return int
684
-     */
685
-    private function _insert_venue_into_posts(array $old_event): int
686
-    {
687
-        global $wpdb;
688
-        $insertion_array  = [
689
-            'post_title'        => $this->_get_venue_title_for_event($old_event),// VNU_name
690
-            'post_content'      => '',// VNU_desc
691
-            'post_name'         => $this->_find_unique_slug(
692
-                $old_event['venue_title'],
693
-                sanitize_title('venue-of-' . $old_event['event_name'])
694
-            ),// VNU_identifier
695
-            'post_date'         => current_time('mysql'),// VNU_created
696
-            'post_date_gmt'     => get_gmt_from_date(current_time('mysql')),
697
-            'post_excerpt'      => '',// VNU_short_desc arbitrary only 50 characters
698
-            'post_modified'     => current_time('mysql'),// VNU_modified
699
-            'post_modified_gmt' => get_gmt_from_date(current_time('mysql')),
700
-            'post_author'       => $old_event['wp_user'],// VNU_wp_user
701
-            'post_parent'       => 0,// parent
702
-            'menu_order'        => 0,// VNU_order
703
-            'post_type'         => EspressoPostType::VENUES,// post_type
704
-        ];
705
-        $data_types_array = [
706
-            '%s',// VNU_name
707
-            '%s',// VNU_desc
708
-            '%s',// VNU_identifier
709
-            '%s',// VNU_created
710
-            '%s',
711
-            '%s',// VNU_short_desc
712
-            '%s',// VNU_modified
713
-            '%s',
714
-            '%d',// VNU_wp_user
715
-            '%d',// parent
716
-            '%d',// VNU_order
717
-            '%s',// post_type
718
-        ];
719
-        $success          = $wpdb->insert(
720
-            $wpdb->posts,
721
-            $insertion_array,
722
-            $data_types_array
723
-        );
724
-        if (! $success) {
725
-            $this->add_error(
726
-                $this->get_migration_script()->_create_error_message_for_db_insertion(
727
-                    $this->_old_table,
728
-                    $old_event,
729
-                    $this->_new_table,
730
-                    $insertion_array,
731
-                    $data_types_array
732
-                )
733
-            );
734
-            return 0;
735
-        }
736
-        return $wpdb->insert_id;
737
-    }
738
-
739
-
740
-    /**
741
-     * Inserts into the venue_meta table
742
-     *
743
-     * @param int   $cpt_id
744
-     * @param array $old_event
745
-     * @return void
746
-     */
747
-    private function _insert_venue_into_meta_table(int $cpt_id, array $old_event): void
748
-    {
749
-        global $wpdb;
750
-
751
-        // assume the country is the same as the organization's old settings
752
-        $country_iso = $this->get_migration_script()->get_default_country_iso();
753
-        // find the state from the venue, or the organization, or just guess california
754
-        if (! $old_event['state']) {
755
-            $old_org_options = get_option('events_organization_settings');
756
-            $state_name      = stripslashes($old_org_options['organization_state']);
757
-        } else {
758
-            $state_name = $old_event['state'];
759
-        }
760
-        if (! $state_name) {
761
-            $state_name = 'CA';
762
-        }
763
-        // get a state ID with the same name, if possible
764
-        try {
765
-            $state    = $this->get_migration_script()->get_or_create_state($state_name, $country_iso);
766
-            $state_id = $state['STA_ID'];
767
-        } catch (EE_Error $e) {
768
-            $this->add_error($e->getMessage());
769
-            $state_id = 0;
770
-        }
771
-        // now insert into meta table
772
-        $insertion_array = [
773
-            'VNU_ID'              => $cpt_id,// VNU_ID_fk
774
-            'VNU_address'         => stripslashes($old_event['address']),// VNU_address
775
-            'VNU_address2'        => stripslashes($old_event['address2']),// VNU_address2
776
-            'VNU_city'            => stripslashes($old_event['city']),// VNU_city
777
-            'STA_ID'              => $state_id,// STA_ID
778
-            'CNT_ISO'             => $country_iso,// CNT_ISO
779
-            'VNU_zip'             => $old_event['zip'],// VNU_zip
780
-            'VNU_phone'           => $old_event['venue_phone'],// VNU_phone
781
-            'VNU_capacity'        => -1,// VNU_capacity
782
-            'VNU_url'             => $old_event['venue_url'],// VNU_url
783
-            'VNU_virtual_phone'   => $old_event['virtual_phone'],// VNU_virtual_phone
784
-            'VNU_virtual_url'     => $old_event['virtual_url'],// VNU_virtual_url
785
-            'VNU_google_map_link' => '',// VNU_google_map_link
786
-            'VNU_enable_for_gmap' => true, // VNU_enable_for_gmap
787
-        ];
788
-        $data_types      = [
789
-            '%d',// VNU_ID_fk
790
-            '%s',// VNU_address
791
-            '%s',// VNU_address2
792
-            '%s',// VNU_city
793
-            '%d',// STA_ID
794
-            '%s',// CNT_ISO
795
-            '%s',// VNU_zip
796
-            '%s',// VNU_phone
797
-            '%d',// VNU_capacity
798
-            '%s',// VNU_url
799
-            '%s',// VNU_virtual_phone
800
-            '%s',// VNU_virtual_url
801
-            '%s',// VNU_google_map_link
802
-            '%d',// VNU_enable_for_gmap
803
-        ];
804
-        $success         = $wpdb->insert($wpdb->prefix . "esp_venue_meta", $insertion_array, $data_types);
805
-        if (! $success) {
806
-            $this->add_error(
807
-                $this->get_migration_script()->_create_error_message_for_db_insertion(
808
-                    $this->_old_table,
809
-                    $old_event,
810
-                    $this->_new_meta_table,
811
-                    $insertion_array,
812
-                    $data_types
813
-                )
814
-            );
815
-        }
816
-    }
817
-
818
-
819
-    /**
820
-     * @param $new_event_id
821
-     * @param $new_venue_id
822
-     * @return void
823
-     */
824
-    private function _insert_new_venue_to_event($new_event_id, $new_venue_id): void
825
-    {
826
-        global $wpdb;
827
-        if (! $new_event_id) {
828
-            $this->add_error(
829
-                sprintf(esc_html__("Could not find 4.1 event id for 3.1 event #%d.", "event_espresso"), $new_event_id)
830
-            );
831
-            return;
832
-        }
833
-        if (! $new_venue_id) {
834
-            $this->add_error(
835
-                sprintf(esc_html__("Could not find 4.1 venue id for 3.1 venue #%d.", "event_espresso"), $new_venue_id)
836
-            );
837
-            return;
838
-        }
839
-        $cols_n_values = [
840
-            'EVT_ID'      => $new_event_id,
841
-            'VNU_ID'      => $new_venue_id,
842
-            'EVV_primary' => true,
843
-        ];
844
-        $data_types    = [
845
-            '%d',// EVT_ID
846
-            '%d',// VNU_ID
847
-            '%d',// EVT_primary
848
-        ];
849
-        $success       = $wpdb->insert($wpdb->prefix . "esp_event_venue", $cols_n_values, $data_types);
850
-        if (! $success) {
851
-            $this->add_error(
852
-                $this->get_migration_script()->_create_error_message_for_db_insertion(
853
-                    $this->_old_table,
854
-                    [],
855
-                    $this->_new_table,
856
-                    $cols_n_values,
857
-                    $data_types
858
-                )
859
-            );
860
-        }
861
-    }
862
-
863
-
864
-    /**
865
-     * Converts all the 3.1 start-end times for the event to 4.1 datetimes
866
-     *
867
-     * @param array $old_event  results of get_results(...,ARRAY_A)
868
-     * @param int   $new_cpt_id new post ID
869
-     * @return void (if there are errors though, adds them to the stage's error list
870
-     * @throws EE_Error
871
-     * @global wpdb $wpdb
872
-     */
873
-    private function _convert_start_end_times(array $old_event, int $new_cpt_id)
874
-    {
875
-        $start_end_times = $this->_get_old_start_end_times($old_event['id']);
876
-        foreach ($start_end_times as $start_end_time) {
877
-            $datetime_id = $this->_insert_new_datetime($start_end_time, $old_event, $new_cpt_id);
878
-            if ($datetime_id) {
879
-                $this->get_migration_script()->set_mapping(
880
-                    $this->_old_start_end_table,
881
-                    $start_end_time['id'],
882
-                    $this->_new_datetime_table,
883
-                    $datetime_id
884
-                );
885
-            }
886
-        }
887
-    }
888
-
889
-
890
-    /**
891
-     * Queries the 3.1 wp_events_start_end table to get all the start and end times for the event
892
-     *
893
-     * @param int   $old_event_id
894
-     * @return array
895
-     * @global wpdb $wpdb
896
-     */
897
-    private function _get_old_start_end_times(int $old_event_id): array
898
-    {
899
-        global $wpdb;
900
-        return $wpdb->get_results(
901
-            $wpdb->prepare("SELECT * FROM $this->_old_start_end_table WHERE event_id=%d", $old_event_id),
902
-            ARRAY_A
903
-        );
904
-    }
905
-
906
-
907
-    /**
908
-     * Inserts a 4.1 datetime given the 3.1 start_end db row and event_details row
909
-     *
910
-     * @param array $start_end_time_row
911
-     * @param array $old_event_row
912
-     * @param int   $new_cpt_id
913
-     * @return int ID of new datetime
914
-     */
915
-    private function _insert_new_datetime(array $start_end_time_row, array $old_event_row, int $new_cpt_id): int
916
-    {
917
-        global $wpdb;
918
-        $start_date         = $old_event_row['start_date'];
919
-        $start_time         = $this->get_migration_script()->convertTimeFromAMPM($start_end_time_row['start_time']);
920
-        $end_date           = $old_event_row['end_date'];
921
-        $end_time           = $this->get_migration_script()->convertTimeFromAMPM($start_end_time_row['end_time']);
922
-        $existing_datetimes = $this->_count_other_datetimes_exist_for_new_event($new_cpt_id);
923
-        $start_datetime_utc =
924
-            $this->get_migration_script()->convert_date_string_to_utc(
925
-                $this,
926
-                $start_end_time_row,
927
-                "$start_date $start_time:00",
928
-                $old_event_row['timezone_string']
929
-            );
930
-        $end_datetime_utc   =
931
-            $this->get_migration_script()->convert_date_string_to_utc(
932
-                $this,
933
-                $start_end_time_row,
934
-                "$end_date $end_time:00",
935
-                $old_event_row['timezone_string']
936
-            );
937
-        $cols_n_values      = [
938
-            'EVT_ID'        => $new_cpt_id,
939
-            // EVT_ID
940
-            'DTT_EVT_start' => $start_datetime_utc,
941
-            // DTT_EVT_start
942
-            'DTT_EVT_end'   => $end_datetime_utc,
943
-            // DTT_EVT_end
944
-            'DTT_reg_limit' => intval($start_end_time_row['reg_limit']) ? $start_end_time_row['reg_limit']
945
-                : $old_event_row['reg_limit'],
946
-            // DTT_reg_limit
947
-            'DTT_sold'      => 0,
948
-            // note: we will increment this as registrations are added during the migration
949
-            //          'DTT_is_primary'=> 0 == $existing_datetimes ,//DTT_is_primary... if count==0, then we'll call it the 'primary'
950
-            'DTT_order'     => $existing_datetimes,
951
-            // DTT_order, just give it the same order as the count of how many datetimes already exist
952
-            'DTT_parent'    => 0,
953
-            'DTT_deleted'   => false,
954
-        ];
955
-        $data_types         = [
956
-            '%d',// EVT_Id
957
-            '%s',// DTT_EVT_start
958
-            '%s',// DTT_EVT_end
959
-            '%d',// DTT_reg_limit
960
-            '%d',// DTT_sold
961
-            //          '%d',//DTT_is_primary
962
-            '%d',// DTT_order
963
-            '%d',// DTT_parent
964
-            '%d',// DTT_deleted
965
-        ];
966
-        $success            = $wpdb->insert($this->_new_datetime_table, $cols_n_values, $data_types);
967
-        if (! $success) {
968
-            $this->add_error(
969
-                $this->get_migration_script()->_create_error_message_for_db_insertion(
970
-                    $this->_old_start_end_table,
971
-                    array_merge($old_event_row, $start_end_time_row),
972
-                    $this->_new_datetime_table,
973
-                    $cols_n_values,
974
-                    $data_types
975
-                )
976
-            );
977
-            return 0;
978
-        }
979
-        return $wpdb->insert_id;
980
-    }
981
-
982
-
983
-    /**
984
-     * Checks if there's a 4.1 datetime for this event already. This is mostly only handy
985
-     * when deciding whether a datetime we're about ot insert should be the 'primary' or not
986
-     *
987
-     * @param int   $cpt_event_id
988
-     * @return int
989
-     * @global wpdb $wpdb
990
-     */
991
-    private function _count_other_datetimes_exist_for_new_event(int $cpt_event_id): int
992
-    {
993
-        global $wpdb;
994
-        return (int) $wpdb->get_var(
995
-            $wpdb->prepare(
996
-                "SELECT COUNT(*) FROM $this->_new_datetime_table WHERE EVT_ID=%d",
997
-                $cpt_event_id
998
-            )
999
-        );
1000
-    }
656
+		return (int) $wpdb->get_var($query);
657
+	}
658
+
659
+
660
+	/**
661
+	 * Gets teh venue's title or makes one up if there is none
662
+	 *
663
+	 * @param array $event_data_array keys are events_details columns and values are their values
664
+	 * @return string
665
+	 */
666
+	protected function _get_venue_title_for_event(array $event_data_array): string
667
+	{
668
+		return $event_data_array['venue_title']
669
+			? stripslashes($event_data_array['venue_title'])
670
+			: stripslashes(
671
+				sprintf(
672
+					esc_html__('Venue of %s', 'event_espresso'),
673
+					$event_data_array['event_name']
674
+				)
675
+			);
676
+	}
677
+
678
+
679
+	/**
680
+	 * Inserts the CPT
681
+	 *
682
+	 * @param array $old_event keys are cols, values are col values
683
+	 * @return int
684
+	 */
685
+	private function _insert_venue_into_posts(array $old_event): int
686
+	{
687
+		global $wpdb;
688
+		$insertion_array  = [
689
+			'post_title'        => $this->_get_venue_title_for_event($old_event),// VNU_name
690
+			'post_content'      => '',// VNU_desc
691
+			'post_name'         => $this->_find_unique_slug(
692
+				$old_event['venue_title'],
693
+				sanitize_title('venue-of-' . $old_event['event_name'])
694
+			),// VNU_identifier
695
+			'post_date'         => current_time('mysql'),// VNU_created
696
+			'post_date_gmt'     => get_gmt_from_date(current_time('mysql')),
697
+			'post_excerpt'      => '',// VNU_short_desc arbitrary only 50 characters
698
+			'post_modified'     => current_time('mysql'),// VNU_modified
699
+			'post_modified_gmt' => get_gmt_from_date(current_time('mysql')),
700
+			'post_author'       => $old_event['wp_user'],// VNU_wp_user
701
+			'post_parent'       => 0,// parent
702
+			'menu_order'        => 0,// VNU_order
703
+			'post_type'         => EspressoPostType::VENUES,// post_type
704
+		];
705
+		$data_types_array = [
706
+			'%s',// VNU_name
707
+			'%s',// VNU_desc
708
+			'%s',// VNU_identifier
709
+			'%s',// VNU_created
710
+			'%s',
711
+			'%s',// VNU_short_desc
712
+			'%s',// VNU_modified
713
+			'%s',
714
+			'%d',// VNU_wp_user
715
+			'%d',// parent
716
+			'%d',// VNU_order
717
+			'%s',// post_type
718
+		];
719
+		$success          = $wpdb->insert(
720
+			$wpdb->posts,
721
+			$insertion_array,
722
+			$data_types_array
723
+		);
724
+		if (! $success) {
725
+			$this->add_error(
726
+				$this->get_migration_script()->_create_error_message_for_db_insertion(
727
+					$this->_old_table,
728
+					$old_event,
729
+					$this->_new_table,
730
+					$insertion_array,
731
+					$data_types_array
732
+				)
733
+			);
734
+			return 0;
735
+		}
736
+		return $wpdb->insert_id;
737
+	}
738
+
739
+
740
+	/**
741
+	 * Inserts into the venue_meta table
742
+	 *
743
+	 * @param int   $cpt_id
744
+	 * @param array $old_event
745
+	 * @return void
746
+	 */
747
+	private function _insert_venue_into_meta_table(int $cpt_id, array $old_event): void
748
+	{
749
+		global $wpdb;
750
+
751
+		// assume the country is the same as the organization's old settings
752
+		$country_iso = $this->get_migration_script()->get_default_country_iso();
753
+		// find the state from the venue, or the organization, or just guess california
754
+		if (! $old_event['state']) {
755
+			$old_org_options = get_option('events_organization_settings');
756
+			$state_name      = stripslashes($old_org_options['organization_state']);
757
+		} else {
758
+			$state_name = $old_event['state'];
759
+		}
760
+		if (! $state_name) {
761
+			$state_name = 'CA';
762
+		}
763
+		// get a state ID with the same name, if possible
764
+		try {
765
+			$state    = $this->get_migration_script()->get_or_create_state($state_name, $country_iso);
766
+			$state_id = $state['STA_ID'];
767
+		} catch (EE_Error $e) {
768
+			$this->add_error($e->getMessage());
769
+			$state_id = 0;
770
+		}
771
+		// now insert into meta table
772
+		$insertion_array = [
773
+			'VNU_ID'              => $cpt_id,// VNU_ID_fk
774
+			'VNU_address'         => stripslashes($old_event['address']),// VNU_address
775
+			'VNU_address2'        => stripslashes($old_event['address2']),// VNU_address2
776
+			'VNU_city'            => stripslashes($old_event['city']),// VNU_city
777
+			'STA_ID'              => $state_id,// STA_ID
778
+			'CNT_ISO'             => $country_iso,// CNT_ISO
779
+			'VNU_zip'             => $old_event['zip'],// VNU_zip
780
+			'VNU_phone'           => $old_event['venue_phone'],// VNU_phone
781
+			'VNU_capacity'        => -1,// VNU_capacity
782
+			'VNU_url'             => $old_event['venue_url'],// VNU_url
783
+			'VNU_virtual_phone'   => $old_event['virtual_phone'],// VNU_virtual_phone
784
+			'VNU_virtual_url'     => $old_event['virtual_url'],// VNU_virtual_url
785
+			'VNU_google_map_link' => '',// VNU_google_map_link
786
+			'VNU_enable_for_gmap' => true, // VNU_enable_for_gmap
787
+		];
788
+		$data_types      = [
789
+			'%d',// VNU_ID_fk
790
+			'%s',// VNU_address
791
+			'%s',// VNU_address2
792
+			'%s',// VNU_city
793
+			'%d',// STA_ID
794
+			'%s',// CNT_ISO
795
+			'%s',// VNU_zip
796
+			'%s',// VNU_phone
797
+			'%d',// VNU_capacity
798
+			'%s',// VNU_url
799
+			'%s',// VNU_virtual_phone
800
+			'%s',// VNU_virtual_url
801
+			'%s',// VNU_google_map_link
802
+			'%d',// VNU_enable_for_gmap
803
+		];
804
+		$success         = $wpdb->insert($wpdb->prefix . "esp_venue_meta", $insertion_array, $data_types);
805
+		if (! $success) {
806
+			$this->add_error(
807
+				$this->get_migration_script()->_create_error_message_for_db_insertion(
808
+					$this->_old_table,
809
+					$old_event,
810
+					$this->_new_meta_table,
811
+					$insertion_array,
812
+					$data_types
813
+				)
814
+			);
815
+		}
816
+	}
817
+
818
+
819
+	/**
820
+	 * @param $new_event_id
821
+	 * @param $new_venue_id
822
+	 * @return void
823
+	 */
824
+	private function _insert_new_venue_to_event($new_event_id, $new_venue_id): void
825
+	{
826
+		global $wpdb;
827
+		if (! $new_event_id) {
828
+			$this->add_error(
829
+				sprintf(esc_html__("Could not find 4.1 event id for 3.1 event #%d.", "event_espresso"), $new_event_id)
830
+			);
831
+			return;
832
+		}
833
+		if (! $new_venue_id) {
834
+			$this->add_error(
835
+				sprintf(esc_html__("Could not find 4.1 venue id for 3.1 venue #%d.", "event_espresso"), $new_venue_id)
836
+			);
837
+			return;
838
+		}
839
+		$cols_n_values = [
840
+			'EVT_ID'      => $new_event_id,
841
+			'VNU_ID'      => $new_venue_id,
842
+			'EVV_primary' => true,
843
+		];
844
+		$data_types    = [
845
+			'%d',// EVT_ID
846
+			'%d',// VNU_ID
847
+			'%d',// EVT_primary
848
+		];
849
+		$success       = $wpdb->insert($wpdb->prefix . "esp_event_venue", $cols_n_values, $data_types);
850
+		if (! $success) {
851
+			$this->add_error(
852
+				$this->get_migration_script()->_create_error_message_for_db_insertion(
853
+					$this->_old_table,
854
+					[],
855
+					$this->_new_table,
856
+					$cols_n_values,
857
+					$data_types
858
+				)
859
+			);
860
+		}
861
+	}
862
+
863
+
864
+	/**
865
+	 * Converts all the 3.1 start-end times for the event to 4.1 datetimes
866
+	 *
867
+	 * @param array $old_event  results of get_results(...,ARRAY_A)
868
+	 * @param int   $new_cpt_id new post ID
869
+	 * @return void (if there are errors though, adds them to the stage's error list
870
+	 * @throws EE_Error
871
+	 * @global wpdb $wpdb
872
+	 */
873
+	private function _convert_start_end_times(array $old_event, int $new_cpt_id)
874
+	{
875
+		$start_end_times = $this->_get_old_start_end_times($old_event['id']);
876
+		foreach ($start_end_times as $start_end_time) {
877
+			$datetime_id = $this->_insert_new_datetime($start_end_time, $old_event, $new_cpt_id);
878
+			if ($datetime_id) {
879
+				$this->get_migration_script()->set_mapping(
880
+					$this->_old_start_end_table,
881
+					$start_end_time['id'],
882
+					$this->_new_datetime_table,
883
+					$datetime_id
884
+				);
885
+			}
886
+		}
887
+	}
888
+
889
+
890
+	/**
891
+	 * Queries the 3.1 wp_events_start_end table to get all the start and end times for the event
892
+	 *
893
+	 * @param int   $old_event_id
894
+	 * @return array
895
+	 * @global wpdb $wpdb
896
+	 */
897
+	private function _get_old_start_end_times(int $old_event_id): array
898
+	{
899
+		global $wpdb;
900
+		return $wpdb->get_results(
901
+			$wpdb->prepare("SELECT * FROM $this->_old_start_end_table WHERE event_id=%d", $old_event_id),
902
+			ARRAY_A
903
+		);
904
+	}
905
+
906
+
907
+	/**
908
+	 * Inserts a 4.1 datetime given the 3.1 start_end db row and event_details row
909
+	 *
910
+	 * @param array $start_end_time_row
911
+	 * @param array $old_event_row
912
+	 * @param int   $new_cpt_id
913
+	 * @return int ID of new datetime
914
+	 */
915
+	private function _insert_new_datetime(array $start_end_time_row, array $old_event_row, int $new_cpt_id): int
916
+	{
917
+		global $wpdb;
918
+		$start_date         = $old_event_row['start_date'];
919
+		$start_time         = $this->get_migration_script()->convertTimeFromAMPM($start_end_time_row['start_time']);
920
+		$end_date           = $old_event_row['end_date'];
921
+		$end_time           = $this->get_migration_script()->convertTimeFromAMPM($start_end_time_row['end_time']);
922
+		$existing_datetimes = $this->_count_other_datetimes_exist_for_new_event($new_cpt_id);
923
+		$start_datetime_utc =
924
+			$this->get_migration_script()->convert_date_string_to_utc(
925
+				$this,
926
+				$start_end_time_row,
927
+				"$start_date $start_time:00",
928
+				$old_event_row['timezone_string']
929
+			);
930
+		$end_datetime_utc   =
931
+			$this->get_migration_script()->convert_date_string_to_utc(
932
+				$this,
933
+				$start_end_time_row,
934
+				"$end_date $end_time:00",
935
+				$old_event_row['timezone_string']
936
+			);
937
+		$cols_n_values      = [
938
+			'EVT_ID'        => $new_cpt_id,
939
+			// EVT_ID
940
+			'DTT_EVT_start' => $start_datetime_utc,
941
+			// DTT_EVT_start
942
+			'DTT_EVT_end'   => $end_datetime_utc,
943
+			// DTT_EVT_end
944
+			'DTT_reg_limit' => intval($start_end_time_row['reg_limit']) ? $start_end_time_row['reg_limit']
945
+				: $old_event_row['reg_limit'],
946
+			// DTT_reg_limit
947
+			'DTT_sold'      => 0,
948
+			// note: we will increment this as registrations are added during the migration
949
+			//          'DTT_is_primary'=> 0 == $existing_datetimes ,//DTT_is_primary... if count==0, then we'll call it the 'primary'
950
+			'DTT_order'     => $existing_datetimes,
951
+			// DTT_order, just give it the same order as the count of how many datetimes already exist
952
+			'DTT_parent'    => 0,
953
+			'DTT_deleted'   => false,
954
+		];
955
+		$data_types         = [
956
+			'%d',// EVT_Id
957
+			'%s',// DTT_EVT_start
958
+			'%s',// DTT_EVT_end
959
+			'%d',// DTT_reg_limit
960
+			'%d',// DTT_sold
961
+			//          '%d',//DTT_is_primary
962
+			'%d',// DTT_order
963
+			'%d',// DTT_parent
964
+			'%d',// DTT_deleted
965
+		];
966
+		$success            = $wpdb->insert($this->_new_datetime_table, $cols_n_values, $data_types);
967
+		if (! $success) {
968
+			$this->add_error(
969
+				$this->get_migration_script()->_create_error_message_for_db_insertion(
970
+					$this->_old_start_end_table,
971
+					array_merge($old_event_row, $start_end_time_row),
972
+					$this->_new_datetime_table,
973
+					$cols_n_values,
974
+					$data_types
975
+				)
976
+			);
977
+			return 0;
978
+		}
979
+		return $wpdb->insert_id;
980
+	}
981
+
982
+
983
+	/**
984
+	 * Checks if there's a 4.1 datetime for this event already. This is mostly only handy
985
+	 * when deciding whether a datetime we're about ot insert should be the 'primary' or not
986
+	 *
987
+	 * @param int   $cpt_event_id
988
+	 * @return int
989
+	 * @global wpdb $wpdb
990
+	 */
991
+	private function _count_other_datetimes_exist_for_new_event(int $cpt_event_id): int
992
+	{
993
+		global $wpdb;
994
+		return (int) $wpdb->get_var(
995
+			$wpdb->prepare(
996
+				"SELECT COUNT(*) FROM $this->_new_datetime_table WHERE EVT_ID=%d",
997
+				$cpt_event_id
998
+			)
999
+		);
1000
+	}
1001 1001
 }
Please login to merge, or discard this patch.
Spacing   +144 added lines, -144 removed lines patch added patch discarded remove patch
@@ -190,11 +190,11 @@  discard block
 block discarded – undo
190 190
     public function __construct()
191 191
     {
192 192
         global $wpdb;
193
-        $this->_old_table           = $wpdb->prefix . "events_detail";
194
-        $this->_old_start_end_table = $wpdb->prefix . "events_start_end";
195
-        $this->_new_table           = $wpdb->prefix . "posts";
196
-        $this->_new_meta_table      = $wpdb->prefix . "esp_event_meta";
197
-        $this->_new_datetime_table  = $wpdb->prefix . "esp_datetime";
193
+        $this->_old_table           = $wpdb->prefix."events_detail";
194
+        $this->_old_start_end_table = $wpdb->prefix."events_start_end";
195
+        $this->_new_table           = $wpdb->prefix."posts";
196
+        $this->_new_meta_table      = $wpdb->prefix."esp_event_meta";
197
+        $this->_new_datetime_table  = $wpdb->prefix."esp_datetime";
198 198
         $this->_pretty_name         = esc_html__("Events", "event_espresso");
199 199
         parent::__construct();
200 200
     }
@@ -208,7 +208,7 @@  discard block
 block discarded – undo
208 208
     public function _count_records_to_migrate(): int
209 209
     {
210 210
         global $wpdb;
211
-        return (int) $wpdb->get_var("SELECT COUNT(*) FROM " . $this->_old_table . ' WHERE event_status !="D"');
211
+        return (int) $wpdb->get_var("SELECT COUNT(*) FROM ".$this->_old_table.' WHERE event_status !="D"');
212 212
     }
213 213
 
214 214
 
@@ -301,17 +301,17 @@  discard block
 block discarded – undo
301 301
     private function _add_post_metas(array $old_event, int $post_id)
302 302
     {
303 303
         $event_meta = maybe_unserialize($old_event['event_meta']);
304
-        if (! $event_meta || ! is_array($event_meta)) {
304
+        if ( ! $event_meta || ! is_array($event_meta)) {
305 305
             return;
306 306
         }
307
-        unset($event_meta['date_submitted']);              // factored into CPT
308
-        unset($event_meta['additional_attendee_reg_info']);// factored into event meta table
309
-        unset($event_meta['default_payment_status']);      // dido
310
-        unset($event_meta['event_thumbnail_url']);         // used to find post featured image
307
+        unset($event_meta['date_submitted']); // factored into CPT
308
+        unset($event_meta['additional_attendee_reg_info']); // factored into event meta table
309
+        unset($event_meta['default_payment_status']); // dido
310
+        unset($event_meta['event_thumbnail_url']); // used to find post featured image
311 311
         foreach ($event_meta as $meta_key => $meta_value) {
312 312
             if ($meta_key) {// if the meta key is just an empty string, ignore it
313 313
                 $success = add_post_meta($post_id, $meta_key, $meta_value, true);
314
-                if (! $success) {
314
+                if ( ! $success) {
315 315
                     $this->add_error(
316 316
                         sprintf(
317 317
                             esc_html__(
@@ -413,63 +413,63 @@  discard block
 block discarded – undo
413 413
             // @todo: is it ok to just mark secondary/waitlist events as DRAFTS?
414 414
             'D'  => 'trash',
415 415
         ];
416
-        $post_status        = $status_conversions[ $old_event['event_status'] ];
416
+        $post_status = $status_conversions[$old_event['event_status']];
417 417
         // check if we've sold out
418 418
         if (intval($old_event['reg_limit']) <= self::count_registrations($old_event['id'])) {
419 419
             $post_status = 'sold_out';
420 420
         }
421 421
         //      FYI postponed and cancelled don't exist in 3.1
422
-        $cols_n_values                      = [
423
-            'post_title'        => stripslashes($old_event['event_name']),// EVT_name
424
-            'post_content'      => stripslashes($old_event['event_desc']),// EVT_desc
422
+        $cols_n_values = [
423
+            'post_title'        => stripslashes($old_event['event_name']), // EVT_name
424
+            'post_content'      => stripslashes($old_event['event_desc']), // EVT_desc
425 425
             'post_name'         => $this->_find_unique_slug(
426 426
                 $old_event['event_name'],
427 427
                 $old_event['event_identifier'],
428 428
                 $post_status
429
-            ),// EVT_slug
430
-            'post_date'         => $old_event['submitted'],// EVT_created NOT
429
+            ), // EVT_slug
430
+            'post_date'         => $old_event['submitted'], // EVT_created NOT
431 431
             'post_date_gmt'     => get_gmt_from_date($old_event['submitted']),
432
-            'post_excerpt'      => '',// EVT_short_desc
433
-            'post_modified'     => $old_event['submitted'],// EVT_modified
432
+            'post_excerpt'      => '', // EVT_short_desc
433
+            'post_modified'     => $old_event['submitted'], // EVT_modified
434 434
             'post_modified_gmt' => get_gmt_from_date($old_event['submitted']),
435
-            'post_author'       => $old_event['wp_user'],// EVT_wp_user
436
-            'post_parent'       => 0,// parent maybe get this from some REM field?
437
-            'menu_order'        => 0,// EVT_order
438
-            'post_type'         => EspressoPostType::EVENTS,// post_type
439
-            'post_status'       => $post_status,// status
435
+            'post_author'       => $old_event['wp_user'], // EVT_wp_user
436
+            'post_parent'       => 0, // parent maybe get this from some REM field?
437
+            'menu_order'        => 0, // EVT_order
438
+            'post_type'         => EspressoPostType::EVENTS, // post_type
439
+            'post_status'       => $post_status, // status
440 440
         ];
441 441
         $cols_n_values_with_no_invalid_text = [];
442 442
         foreach ($cols_n_values as $col => $value) {
443 443
             $value_sans_invalid_chars = $wpdb->strip_invalid_text_for_column($this->_new_table, $col, $value);
444
-            if (! is_wp_error($value_sans_invalid_chars)) {
445
-                $cols_n_values_with_no_invalid_text[ $col ] = $value_sans_invalid_chars;
444
+            if ( ! is_wp_error($value_sans_invalid_chars)) {
445
+                $cols_n_values_with_no_invalid_text[$col] = $value_sans_invalid_chars;
446 446
             } else {
447 447
                 // otherwise leave it as-is. It will blow everything up and stop the migration
448
-                $cols_n_values_with_no_invalid_text[ $col ] = $value;
448
+                $cols_n_values_with_no_invalid_text[$col] = $value;
449 449
             }
450 450
         }
451 451
         $cols_n_values = $cols_n_values_with_no_invalid_text;
452 452
         $data_types    = [
453
-            '%s',// EVT_name
454
-            '%s',// EVT_desc
455
-            '%s',// EVT_slug
456
-            '%s',// EVT_created
453
+            '%s', // EVT_name
454
+            '%s', // EVT_desc
455
+            '%s', // EVT_slug
456
+            '%s', // EVT_created
457 457
             '%s',
458
-            '%s',// EVT_short_desc
459
-            '%s',// EVT_modified
458
+            '%s', // EVT_short_desc
459
+            '%s', // EVT_modified
460 460
             '%s',
461
-            '%s',// EVT_wp_user
462
-            '%d',// post_parent
463
-            '%d',// EVT_order
464
-            '%s',// post_type
465
-            '%s',// status
461
+            '%s', // EVT_wp_user
462
+            '%d', // post_parent
463
+            '%d', // EVT_order
464
+            '%s', // post_type
465
+            '%s', // status
466 466
         ];
467
-        $success       = $wpdb->insert(
467
+        $success = $wpdb->insert(
468 468
             $this->_new_table,
469 469
             $cols_n_values,
470 470
             $data_types
471 471
         );
472
-        if (! $success) {
472
+        if ( ! $success) {
473 473
             $this->add_error(
474 474
                 $this->get_migration_script()->_create_error_message_for_db_insertion(
475 475
                     $this->_old_table,
@@ -522,8 +522,8 @@  discard block
 block discarded – undo
522 522
                 $event_meta['default_payment_status'] ?? '',
523 523
                 intval($old_event['require_pre_approval'])
524 524
             );
525
-        $cols_n_values      = [
526
-            'EVT_ID'                          => $new_cpt_id,// EVT_ID_fk
525
+        $cols_n_values = [
526
+            'EVT_ID'                          => $new_cpt_id, // EVT_ID_fk
527 527
             'EVT_display_desc'                => 'Y' == $old_event['display_desc'],
528 528
             'EVT_display_ticket_selector'     => 'Y' == $old_event['display_reg_form'],
529 529
             'EVT_visible_on'                  => $this->get_migration_script()->convert_date_string_to_utc(
@@ -533,7 +533,7 @@  discard block
 block discarded – undo
533 533
                     'mysql'
534 534
                 ),
535 535
                 $old_event['timezone_string']
536
-            ),// don't use the old 'visible_on', as it wasn't ever used
536
+            ), // don't use the old 'visible_on', as it wasn't ever used
537 537
             'EVT_additional_limit'            => $old_event['allow_multiple'] == 'N' ? 1
538 538
                 : $old_event['additional_limit'],
539 539
             'EVT_default_registration_status' => $default_reg_status,
@@ -542,29 +542,29 @@  discard block
 block discarded – undo
542 542
             'EVT_allow_overflow'              => 'Y' == $old_event['allow_overflow'],
543 543
             'EVT_timezone_string'             => $old_event['timezone_string'],
544 544
             'EVT_external_URL'                => $old_event['externalURL'],
545
-            'EVT_donations'                   => false,// doesn't exist in 3.1
545
+            'EVT_donations'                   => false, // doesn't exist in 3.1
546 546
 
547 547
         ];
548
-        $data_types         = [
549
-            '%s',// EVT_ID
550
-            '%d',// EVT_display_desc
551
-            '%d',// EVT_display_ticket_selector
552
-            '%s',// EVT_visible_on
553
-            '%d',// EVT_additional_limit
554
-            '%s',// EVT_default_registration_status
555
-            '%d',// EVT_member_only
556
-            '%s',// EVT_phone
557
-            '%d',// EVT_allow_overflow
558
-            '%s',// EVT_timezone_string
559
-            '%s',// EVT_external_URL
560
-            '%d',// EVT_donations
548
+        $data_types = [
549
+            '%s', // EVT_ID
550
+            '%d', // EVT_display_desc
551
+            '%d', // EVT_display_ticket_selector
552
+            '%s', // EVT_visible_on
553
+            '%d', // EVT_additional_limit
554
+            '%s', // EVT_default_registration_status
555
+            '%d', // EVT_member_only
556
+            '%s', // EVT_phone
557
+            '%d', // EVT_allow_overflow
558
+            '%s', // EVT_timezone_string
559
+            '%s', // EVT_external_URL
560
+            '%d', // EVT_donations
561 561
         ];
562
-        $success            = $wpdb->insert(
562
+        $success = $wpdb->insert(
563 563
             $this->_new_meta_table,
564 564
             $cols_n_values,
565 565
             $data_types
566 566
         );
567
-        if (! $success) {
567
+        if ( ! $success) {
568 568
             $this->add_error(
569 569
                 $this->get_migration_script()->_create_error_message_for_db_insertion(
570 570
                     $this->_old_table,
@@ -607,7 +607,7 @@  discard block
 block discarded – undo
607 607
             if ($new_id) {
608 608
                 $this->_insert_venue_into_meta_table($new_id, $old_event);
609 609
                 $guid = $old_event['venue_image'] ?? null;
610
-                if($guid) {
610
+                if ($guid) {
611 611
                     $this->get_migration_script()->convert_image_url_to_attachment_and_attach_to_post(
612 612
                         $guid,
613 613
                         $new_id,
@@ -633,16 +633,16 @@  discard block
 block discarded – undo
633 633
     private function _duplicate_venue_exists(array $old_event): int
634 634
     {
635 635
         global $wpdb;
636
-        $conditions     = [
636
+        $conditions = [
637 637
             'VNU_address'       => $old_event ['address'],
638 638
             'VNU_address2'      => $old_event['address2'],
639 639
             'VNU_city'          => $old_event['city'],
640 640
             'VNU_zip'           => $old_event['zip'],
641 641
             'post_title'        => $this->_get_venue_title_for_event($old_event),
642
-            'VNU_phone'         => $old_event['venue_phone'],// VNU_phone
643
-            'VNU_url'           => $old_event['venue_url'],// VNU_url
644
-            'VNU_virtual_phone' => $old_event['virtual_phone'],// VNU_virtual_phone
645
-            'VNU_virtual_url'   => $old_event['virtual_url'],// VNU_virtual_url
642
+            'VNU_phone'         => $old_event['venue_phone'], // VNU_phone
643
+            'VNU_url'           => $old_event['venue_url'], // VNU_url
644
+            'VNU_virtual_phone' => $old_event['virtual_phone'], // VNU_virtual_phone
645
+            'VNU_virtual_url'   => $old_event['virtual_url'], // VNU_virtual_url
646 646
         ];
647 647
         $sql_conditions = [];
648 648
         foreach ($conditions as $column => $value) {
@@ -652,7 +652,7 @@  discard block
 block discarded – undo
652 652
 					FROM
653 653
 		$wpdb->posts as p INNER JOIN
654 654
 		{$wpdb->prefix}esp_venue_meta as v ON p.ID = v.VNU_ID
655
-			WHERE " . implode(" AND ", $sql_conditions) . " LIMIT 1";
655
+			WHERE ".implode(" AND ", $sql_conditions)." LIMIT 1";
656 656
         return (int) $wpdb->get_var($query);
657 657
     }
658 658
 
@@ -685,43 +685,43 @@  discard block
 block discarded – undo
685 685
     private function _insert_venue_into_posts(array $old_event): int
686 686
     {
687 687
         global $wpdb;
688
-        $insertion_array  = [
689
-            'post_title'        => $this->_get_venue_title_for_event($old_event),// VNU_name
690
-            'post_content'      => '',// VNU_desc
688
+        $insertion_array = [
689
+            'post_title'        => $this->_get_venue_title_for_event($old_event), // VNU_name
690
+            'post_content'      => '', // VNU_desc
691 691
             'post_name'         => $this->_find_unique_slug(
692 692
                 $old_event['venue_title'],
693
-                sanitize_title('venue-of-' . $old_event['event_name'])
694
-            ),// VNU_identifier
695
-            'post_date'         => current_time('mysql'),// VNU_created
693
+                sanitize_title('venue-of-'.$old_event['event_name'])
694
+            ), // VNU_identifier
695
+            'post_date'         => current_time('mysql'), // VNU_created
696 696
             'post_date_gmt'     => get_gmt_from_date(current_time('mysql')),
697
-            'post_excerpt'      => '',// VNU_short_desc arbitrary only 50 characters
698
-            'post_modified'     => current_time('mysql'),// VNU_modified
697
+            'post_excerpt'      => '', // VNU_short_desc arbitrary only 50 characters
698
+            'post_modified'     => current_time('mysql'), // VNU_modified
699 699
             'post_modified_gmt' => get_gmt_from_date(current_time('mysql')),
700
-            'post_author'       => $old_event['wp_user'],// VNU_wp_user
701
-            'post_parent'       => 0,// parent
702
-            'menu_order'        => 0,// VNU_order
703
-            'post_type'         => EspressoPostType::VENUES,// post_type
700
+            'post_author'       => $old_event['wp_user'], // VNU_wp_user
701
+            'post_parent'       => 0, // parent
702
+            'menu_order'        => 0, // VNU_order
703
+            'post_type'         => EspressoPostType::VENUES, // post_type
704 704
         ];
705 705
         $data_types_array = [
706
-            '%s',// VNU_name
707
-            '%s',// VNU_desc
708
-            '%s',// VNU_identifier
709
-            '%s',// VNU_created
706
+            '%s', // VNU_name
707
+            '%s', // VNU_desc
708
+            '%s', // VNU_identifier
709
+            '%s', // VNU_created
710 710
             '%s',
711
-            '%s',// VNU_short_desc
712
-            '%s',// VNU_modified
711
+            '%s', // VNU_short_desc
712
+            '%s', // VNU_modified
713 713
             '%s',
714
-            '%d',// VNU_wp_user
715
-            '%d',// parent
716
-            '%d',// VNU_order
717
-            '%s',// post_type
714
+            '%d', // VNU_wp_user
715
+            '%d', // parent
716
+            '%d', // VNU_order
717
+            '%s', // post_type
718 718
         ];
719
-        $success          = $wpdb->insert(
719
+        $success = $wpdb->insert(
720 720
             $wpdb->posts,
721 721
             $insertion_array,
722 722
             $data_types_array
723 723
         );
724
-        if (! $success) {
724
+        if ( ! $success) {
725 725
             $this->add_error(
726 726
                 $this->get_migration_script()->_create_error_message_for_db_insertion(
727 727
                     $this->_old_table,
@@ -751,13 +751,13 @@  discard block
 block discarded – undo
751 751
         // assume the country is the same as the organization's old settings
752 752
         $country_iso = $this->get_migration_script()->get_default_country_iso();
753 753
         // find the state from the venue, or the organization, or just guess california
754
-        if (! $old_event['state']) {
754
+        if ( ! $old_event['state']) {
755 755
             $old_org_options = get_option('events_organization_settings');
756 756
             $state_name      = stripslashes($old_org_options['organization_state']);
757 757
         } else {
758 758
             $state_name = $old_event['state'];
759 759
         }
760
-        if (! $state_name) {
760
+        if ( ! $state_name) {
761 761
             $state_name = 'CA';
762 762
         }
763 763
         // get a state ID with the same name, if possible
@@ -770,39 +770,39 @@  discard block
 block discarded – undo
770 770
         }
771 771
         // now insert into meta table
772 772
         $insertion_array = [
773
-            'VNU_ID'              => $cpt_id,// VNU_ID_fk
774
-            'VNU_address'         => stripslashes($old_event['address']),// VNU_address
775
-            'VNU_address2'        => stripslashes($old_event['address2']),// VNU_address2
776
-            'VNU_city'            => stripslashes($old_event['city']),// VNU_city
777
-            'STA_ID'              => $state_id,// STA_ID
778
-            'CNT_ISO'             => $country_iso,// CNT_ISO
779
-            'VNU_zip'             => $old_event['zip'],// VNU_zip
780
-            'VNU_phone'           => $old_event['venue_phone'],// VNU_phone
781
-            'VNU_capacity'        => -1,// VNU_capacity
782
-            'VNU_url'             => $old_event['venue_url'],// VNU_url
783
-            'VNU_virtual_phone'   => $old_event['virtual_phone'],// VNU_virtual_phone
784
-            'VNU_virtual_url'     => $old_event['virtual_url'],// VNU_virtual_url
785
-            'VNU_google_map_link' => '',// VNU_google_map_link
773
+            'VNU_ID'              => $cpt_id, // VNU_ID_fk
774
+            'VNU_address'         => stripslashes($old_event['address']), // VNU_address
775
+            'VNU_address2'        => stripslashes($old_event['address2']), // VNU_address2
776
+            'VNU_city'            => stripslashes($old_event['city']), // VNU_city
777
+            'STA_ID'              => $state_id, // STA_ID
778
+            'CNT_ISO'             => $country_iso, // CNT_ISO
779
+            'VNU_zip'             => $old_event['zip'], // VNU_zip
780
+            'VNU_phone'           => $old_event['venue_phone'], // VNU_phone
781
+            'VNU_capacity'        => -1, // VNU_capacity
782
+            'VNU_url'             => $old_event['venue_url'], // VNU_url
783
+            'VNU_virtual_phone'   => $old_event['virtual_phone'], // VNU_virtual_phone
784
+            'VNU_virtual_url'     => $old_event['virtual_url'], // VNU_virtual_url
785
+            'VNU_google_map_link' => '', // VNU_google_map_link
786 786
             'VNU_enable_for_gmap' => true, // VNU_enable_for_gmap
787 787
         ];
788
-        $data_types      = [
789
-            '%d',// VNU_ID_fk
790
-            '%s',// VNU_address
791
-            '%s',// VNU_address2
792
-            '%s',// VNU_city
793
-            '%d',// STA_ID
794
-            '%s',// CNT_ISO
795
-            '%s',// VNU_zip
796
-            '%s',// VNU_phone
797
-            '%d',// VNU_capacity
798
-            '%s',// VNU_url
799
-            '%s',// VNU_virtual_phone
800
-            '%s',// VNU_virtual_url
801
-            '%s',// VNU_google_map_link
802
-            '%d',// VNU_enable_for_gmap
788
+        $data_types = [
789
+            '%d', // VNU_ID_fk
790
+            '%s', // VNU_address
791
+            '%s', // VNU_address2
792
+            '%s', // VNU_city
793
+            '%d', // STA_ID
794
+            '%s', // CNT_ISO
795
+            '%s', // VNU_zip
796
+            '%s', // VNU_phone
797
+            '%d', // VNU_capacity
798
+            '%s', // VNU_url
799
+            '%s', // VNU_virtual_phone
800
+            '%s', // VNU_virtual_url
801
+            '%s', // VNU_google_map_link
802
+            '%d', // VNU_enable_for_gmap
803 803
         ];
804
-        $success         = $wpdb->insert($wpdb->prefix . "esp_venue_meta", $insertion_array, $data_types);
805
-        if (! $success) {
804
+        $success = $wpdb->insert($wpdb->prefix."esp_venue_meta", $insertion_array, $data_types);
805
+        if ( ! $success) {
806 806
             $this->add_error(
807 807
                 $this->get_migration_script()->_create_error_message_for_db_insertion(
808 808
                     $this->_old_table,
@@ -824,13 +824,13 @@  discard block
 block discarded – undo
824 824
     private function _insert_new_venue_to_event($new_event_id, $new_venue_id): void
825 825
     {
826 826
         global $wpdb;
827
-        if (! $new_event_id) {
827
+        if ( ! $new_event_id) {
828 828
             $this->add_error(
829 829
                 sprintf(esc_html__("Could not find 4.1 event id for 3.1 event #%d.", "event_espresso"), $new_event_id)
830 830
             );
831 831
             return;
832 832
         }
833
-        if (! $new_venue_id) {
833
+        if ( ! $new_venue_id) {
834 834
             $this->add_error(
835 835
                 sprintf(esc_html__("Could not find 4.1 venue id for 3.1 venue #%d.", "event_espresso"), $new_venue_id)
836 836
             );
@@ -841,13 +841,13 @@  discard block
 block discarded – undo
841 841
             'VNU_ID'      => $new_venue_id,
842 842
             'EVV_primary' => true,
843 843
         ];
844
-        $data_types    = [
845
-            '%d',// EVT_ID
846
-            '%d',// VNU_ID
847
-            '%d',// EVT_primary
844
+        $data_types = [
845
+            '%d', // EVT_ID
846
+            '%d', // VNU_ID
847
+            '%d', // EVT_primary
848 848
         ];
849
-        $success       = $wpdb->insert($wpdb->prefix . "esp_event_venue", $cols_n_values, $data_types);
850
-        if (! $success) {
849
+        $success = $wpdb->insert($wpdb->prefix."esp_event_venue", $cols_n_values, $data_types);
850
+        if ( ! $success) {
851 851
             $this->add_error(
852 852
                 $this->get_migration_script()->_create_error_message_for_db_insertion(
853 853
                     $this->_old_table,
@@ -927,14 +927,14 @@  discard block
 block discarded – undo
927 927
                 "$start_date $start_time:00",
928 928
                 $old_event_row['timezone_string']
929 929
             );
930
-        $end_datetime_utc   =
930
+        $end_datetime_utc =
931 931
             $this->get_migration_script()->convert_date_string_to_utc(
932 932
                 $this,
933 933
                 $start_end_time_row,
934 934
                 "$end_date $end_time:00",
935 935
                 $old_event_row['timezone_string']
936 936
             );
937
-        $cols_n_values      = [
937
+        $cols_n_values = [
938 938
             'EVT_ID'        => $new_cpt_id,
939 939
             // EVT_ID
940 940
             'DTT_EVT_start' => $start_datetime_utc,
@@ -952,19 +952,19 @@  discard block
 block discarded – undo
952 952
             'DTT_parent'    => 0,
953 953
             'DTT_deleted'   => false,
954 954
         ];
955
-        $data_types         = [
956
-            '%d',// EVT_Id
957
-            '%s',// DTT_EVT_start
958
-            '%s',// DTT_EVT_end
959
-            '%d',// DTT_reg_limit
960
-            '%d',// DTT_sold
955
+        $data_types = [
956
+            '%d', // EVT_Id
957
+            '%s', // DTT_EVT_start
958
+            '%s', // DTT_EVT_end
959
+            '%d', // DTT_reg_limit
960
+            '%d', // DTT_sold
961 961
             //          '%d',//DTT_is_primary
962
-            '%d',// DTT_order
963
-            '%d',// DTT_parent
964
-            '%d',// DTT_deleted
962
+            '%d', // DTT_order
963
+            '%d', // DTT_parent
964
+            '%d', // DTT_deleted
965 965
         ];
966
-        $success            = $wpdb->insert($this->_new_datetime_table, $cols_n_values, $data_types);
967
-        if (! $success) {
966
+        $success = $wpdb->insert($this->_new_datetime_table, $cols_n_values, $data_types);
967
+        if ( ! $success) {
968 968
             $this->add_error(
969 969
                 $this->get_migration_script()->_create_error_message_for_db_insertion(
970 970
                     $this->_old_start_end_table,
Please login to merge, or discard this patch.