Completed
Branch master (6bdc59)
by
unknown
25:30 queued 20:54
created
espresso.php 1 patch
Indentation   +107 added lines, -107 removed lines patch added patch discarded remove patch
@@ -37,138 +37,138 @@
 block discarded – undo
37 37
  * @since           4.0
38 38
  */
39 39
 if (function_exists('espresso_version')) {
40
-    if (! function_exists('espresso_duplicate_plugin_error')) {
41
-        /**
42
-         *    espresso_duplicate_plugin_error
43
-         *    displays if more than one version of EE is activated at the same time.
44
-         */
45
-        function espresso_duplicate_plugin_error()
46
-        {
47
-            ?>
40
+	if (! function_exists('espresso_duplicate_plugin_error')) {
41
+		/**
42
+		 *    espresso_duplicate_plugin_error
43
+		 *    displays if more than one version of EE is activated at the same time.
44
+		 */
45
+		function espresso_duplicate_plugin_error()
46
+		{
47
+			?>
48 48
 <div class="error">
49 49
 	<p>
50 50
 		<?php
51
-                    echo esc_html__(
52
-                        'Can not run multiple versions of Event Espresso! One version has been automatically deactivated. Please verify that you have the correct version you want still active.',
53
-                        'event_espresso'
54
-                    ); ?>
51
+					echo esc_html__(
52
+						'Can not run multiple versions of Event Espresso! One version has been automatically deactivated. Please verify that you have the correct version you want still active.',
53
+						'event_espresso'
54
+					); ?>
55 55
 	</p>
56 56
 </div>
57 57
 <?php
58
-            espresso_deactivate_plugin(plugin_basename(__FILE__));
59
-        }
60
-    }
61
-    add_action('admin_notices', 'espresso_duplicate_plugin_error', 1);
58
+			espresso_deactivate_plugin(plugin_basename(__FILE__));
59
+		}
60
+	}
61
+	add_action('admin_notices', 'espresso_duplicate_plugin_error', 1);
62 62
 } else {
63
-    define('EE_MIN_PHP_VER_REQUIRED', '7.4.0');
64
-    if (! version_compare(PHP_VERSION, EE_MIN_PHP_VER_REQUIRED, '>=')) {
65
-        /**
66
-         * espresso_minimum_php_version_error
67
-         *
68
-         * @return void
69
-         */
70
-        function espresso_minimum_php_version_error()
71
-        {
72
-            ?>
63
+	define('EE_MIN_PHP_VER_REQUIRED', '7.4.0');
64
+	if (! version_compare(PHP_VERSION, EE_MIN_PHP_VER_REQUIRED, '>=')) {
65
+		/**
66
+		 * espresso_minimum_php_version_error
67
+		 *
68
+		 * @return void
69
+		 */
70
+		function espresso_minimum_php_version_error()
71
+		{
72
+			?>
73 73
 <div class="error">
74 74
 	<p>
75 75
 		<?php
76
-                    printf(
77
-                        esc_html__(
78
-                            'We\'re sorry, but Event Espresso requires PHP version %1$s or greater in order to operate. You are currently running version %2$s.%3$sIn order to update your version of PHP, you will need to contact your current hosting provider.%3$sFor information on stable PHP versions, please go to %4$s.',
79
-                            'event_espresso'
80
-                        ),
81
-                        EE_MIN_PHP_VER_REQUIRED,
82
-                        PHP_VERSION,
83
-                        '<br/>',
84
-                        '<a href="https://www.php.net/downloads.php">https://php.net/downloads.php</a>'
85
-                    );
86
-                    ?>
76
+					printf(
77
+						esc_html__(
78
+							'We\'re sorry, but Event Espresso requires PHP version %1$s or greater in order to operate. You are currently running version %2$s.%3$sIn order to update your version of PHP, you will need to contact your current hosting provider.%3$sFor information on stable PHP versions, please go to %4$s.',
79
+							'event_espresso'
80
+						),
81
+						EE_MIN_PHP_VER_REQUIRED,
82
+						PHP_VERSION,
83
+						'<br/>',
84
+						'<a href="https://www.php.net/downloads.php">https://php.net/downloads.php</a>'
85
+					);
86
+					?>
87 87
 	</p>
88 88
 </div>
89 89
 <?php
90
-            espresso_deactivate_plugin(plugin_basename(__FILE__));
91
-        }
90
+			espresso_deactivate_plugin(plugin_basename(__FILE__));
91
+		}
92 92
 
93
-        add_action('admin_notices', 'espresso_minimum_php_version_error', 1);
94
-    } else {
95
-        define('EVENT_ESPRESSO_MAIN_FILE', __FILE__);
93
+		add_action('admin_notices', 'espresso_minimum_php_version_error', 1);
94
+	} else {
95
+		define('EVENT_ESPRESSO_MAIN_FILE', __FILE__);
96 96
 
97
-        require_once __DIR__ . '/vendor/autoload.php';
97
+		require_once __DIR__ . '/vendor/autoload.php';
98 98
 
99
-        /**
100
-         * espresso_version
101
-         * Returns the plugin version
102
-         *
103
-         * @return string
104
-         */
105
-        function espresso_version(): string
106
-        {
107
-            return apply_filters('FHEE__espresso__espresso_version', '5.0.20.rc.001');
108
-        }
99
+		/**
100
+		 * espresso_version
101
+		 * Returns the plugin version
102
+		 *
103
+		 * @return string
104
+		 */
105
+		function espresso_version(): string
106
+		{
107
+			return apply_filters('FHEE__espresso__espresso_version', '5.0.20.rc.001');
108
+		}
109 109
 
110
-        /**
111
-         * espresso_plugin_activation
112
-         * adds a wp-option to indicate that EE has been activated via the WP admin plugins page
113
-         */
114
-        function espresso_plugin_activation()
115
-        {
116
-            update_option('ee_espresso_activation', true);
117
-            update_option('event-espresso-core_allow_tracking', 'no');
118
-            update_option('event-espresso-core_tracking_notice', 'hide');
119
-            // Run WP GraphQL activation callback
120
-            espressoLoadWpGraphQL();
121
-            graphql_activation_callback();
122
-        }
110
+		/**
111
+		 * espresso_plugin_activation
112
+		 * adds a wp-option to indicate that EE has been activated via the WP admin plugins page
113
+		 */
114
+		function espresso_plugin_activation()
115
+		{
116
+			update_option('ee_espresso_activation', true);
117
+			update_option('event-espresso-core_allow_tracking', 'no');
118
+			update_option('event-espresso-core_tracking_notice', 'hide');
119
+			// Run WP GraphQL activation callback
120
+			espressoLoadWpGraphQL();
121
+			graphql_activation_callback();
122
+		}
123 123
 
124
-        register_activation_hook(EVENT_ESPRESSO_MAIN_FILE, 'espresso_plugin_activation');
124
+		register_activation_hook(EVENT_ESPRESSO_MAIN_FILE, 'espresso_plugin_activation');
125 125
 
126
-        /**
127
-         * espresso_plugin_deactivation
128
-         */
129
-        function espresso_plugin_deactivation()
130
-        {
131
-            // Run WP GraphQL deactivation callback
132
-            espressoLoadWpGraphQL();
133
-            graphql_deactivation_callback();
134
-            delete_option('event-espresso-core_allow_tracking');
135
-            delete_option('event-espresso-core_tracking_notice');
136
-        }
137
-        register_deactivation_hook(EVENT_ESPRESSO_MAIN_FILE, 'espresso_plugin_deactivation');
126
+		/**
127
+		 * espresso_plugin_deactivation
128
+		 */
129
+		function espresso_plugin_deactivation()
130
+		{
131
+			// Run WP GraphQL deactivation callback
132
+			espressoLoadWpGraphQL();
133
+			graphql_deactivation_callback();
134
+			delete_option('event-espresso-core_allow_tracking');
135
+			delete_option('event-espresso-core_tracking_notice');
136
+		}
137
+		register_deactivation_hook(EVENT_ESPRESSO_MAIN_FILE, 'espresso_plugin_deactivation');
138 138
 
139
-        require_once __DIR__ . '/core/bootstrap_espresso.php';
140
-        bootstrap_espresso();
141
-    }
139
+		require_once __DIR__ . '/core/bootstrap_espresso.php';
140
+		bootstrap_espresso();
141
+	}
142 142
 }
143 143
 
144 144
 if (! function_exists('espresso_deactivate_plugin')) {
145
-    /**
146
-     *    deactivate_plugin
147
-     * usage:  espresso_deactivate_plugin( plugin_basename( __FILE__ ));
148
-     *
149
-     * @access public
150
-     * @param string $plugin_basename - the results of plugin_basename( __FILE__ ) for the plugin's main file
151
-     * @return    void
152
-     */
153
-    function espresso_deactivate_plugin(string $plugin_basename = '')
154
-    {
155
-        if (! function_exists('deactivate_plugins')) {
156
-            require_once ABSPATH . 'wp-admin/includes/plugin.php';
157
-        }
158
-        unset($_GET['activate'], $_REQUEST['activate']);
159
-        deactivate_plugins($plugin_basename);
160
-    }
145
+	/**
146
+	 *    deactivate_plugin
147
+	 * usage:  espresso_deactivate_plugin( plugin_basename( __FILE__ ));
148
+	 *
149
+	 * @access public
150
+	 * @param string $plugin_basename - the results of plugin_basename( __FILE__ ) for the plugin's main file
151
+	 * @return    void
152
+	 */
153
+	function espresso_deactivate_plugin(string $plugin_basename = '')
154
+	{
155
+		if (! function_exists('deactivate_plugins')) {
156
+			require_once ABSPATH . 'wp-admin/includes/plugin.php';
157
+		}
158
+		unset($_GET['activate'], $_REQUEST['activate']);
159
+		deactivate_plugins($plugin_basename);
160
+	}
161 161
 }
162 162
 
163 163
 
164 164
 if (! function_exists('espressoLoadWpGraphQL')) {
165
-    function espressoLoadWpGraphQL()
166
-    {
167
-        if (
168
-            ! class_exists('WPGraphQL')
169
-            && is_readable(__DIR__ . '/vendor/wp-graphql/wp-graphql/wp-graphql.php')
170
-        ) {
171
-            require_once __DIR__ . '/vendor/wp-graphql/wp-graphql/wp-graphql.php';
172
-        }
173
-    }
165
+	function espressoLoadWpGraphQL()
166
+	{
167
+		if (
168
+			! class_exists('WPGraphQL')
169
+			&& is_readable(__DIR__ . '/vendor/wp-graphql/wp-graphql/wp-graphql.php')
170
+		) {
171
+			require_once __DIR__ . '/vendor/wp-graphql/wp-graphql/wp-graphql.php';
172
+		}
173
+	}
174 174
 }
Please login to merge, or discard this patch.
core/helpers/EEH_Event_View.helper.php 2 patches
Indentation   +639 added lines, -639 removed lines patch added patch discarded remove patch
@@ -11,374 +11,374 @@  discard block
 block discarded – undo
11 11
  */
12 12
 class EEH_Event_View extends EEH_Base
13 13
 {
14
-    private static ?EE_Event $_event = null;
15
-
16
-
17
-    /**
18
-     * get_event
19
-     * attempts to retrieve an EE_Event object any way it can
20
-     *
21
-     * @param int|WP_Post $EVT_ID
22
-     * @return EE_Event|null
23
-     * @throws EE_Error
24
-     * @throws ReflectionException
25
-     */
26
-    public static function get_event($EVT_ID = 0): ?EE_Event
27
-    {
28
-        // international newspaper?
29
-        global $post;
30
-        $EVT_ID = $EVT_ID instanceof WP_Post && $EVT_ID->post_type === EspressoPostType::EVENTS
31
-            ? $EVT_ID->ID
32
-            : absint($EVT_ID);
33
-        // do we already have the Event  you are looking for?
34
-        if (EEH_Event_View::$_event instanceof EE_Event && $EVT_ID && EEH_Event_View::$_event->ID() === $EVT_ID) {
35
-            return EEH_Event_View::$_event;
36
-        }
37
-        // reset property so that the new event is cached.
38
-        EEH_Event_View::$_event = null;
39
-        if (! $EVT_ID && $post instanceof EE_Event) {
40
-            EEH_Event_View::$_event = $post;
41
-            return EEH_Event_View::$_event;
42
-        }
43
-        // if the post type is for an event and it has a cached event and we don't have a different incoming $EVT_ID
44
-        // then let's just use that cached event on the $post object.
45
-        if (
46
-            $post instanceof WP_Post
47
-            && $post->post_type === EspressoPostType::EVENTS
48
-            && isset($post->EE_Event)
49
-            && (
50
-                $EVT_ID === 0
51
-                || $EVT_ID === $post->ID
52
-            )
53
-        ) {
54
-            EEH_Event_View::$_event = $post->EE_Event;
55
-            return EEH_Event_View::$_event;
56
-        }
57
-        // If the event we have isn't an event but we do have an EVT_ID, let's try getting the event using the ID.
58
-        if (! EEH_Event_View::$_event instanceof EE_Event && $EVT_ID) {
59
-            EEH_Event_View::$_event = EEM_Event::instance()->get_one_by_ID($EVT_ID);
60
-        }
61
-        // no? ok let's try getting the event using the ID.
62
-        if (! EEH_Event_View::$_event instanceof EE_Event && $post->ID) {
63
-            $maybe_an_event = EEM_Event::instance()->get_one_by_ID($post->ID);
64
-            EEH_Event_View::$_event = $maybe_an_event instanceof EE_Event ? $maybe_an_event : EEH_Event_View::$_event;
65
-        }
66
-        // ensure Event object is added to WP post object
67
-        if (
68
-            $post instanceof WP_Post
69
-            && $post->post_type === EspressoPostType::EVENTS
70
-            && EEH_Event_View::$_event instanceof EE_Event
71
-        ) {
72
-            $post->EE_Event = EEH_Event_View::$_event;
73
-        }
74
-        return EEH_Event_View::$_event;
75
-    }
76
-
77
-
78
-    /**
79
-     *    display_ticket_selector
80
-     *
81
-     * @param int $EVT_ID
82
-     * @return    boolean
83
-     * @throws EE_Error
84
-     * @throws EE_Error
85
-     * @throws ReflectionException
86
-     */
87
-    public static function display_ticket_selector($EVT_ID = 0)
88
-    {
89
-        $event = EEH_Event_View::get_event($EVT_ID);
90
-        return $event instanceof EE_Event && $event->display_ticket_selector();
91
-    }
92
-
93
-
94
-    /**
95
-     *    event_status
96
-     *
97
-     * @param int $EVT_ID
98
-     * @return    string
99
-     * @throws EE_Error
100
-     * @throws EE_Error
101
-     * @throws ReflectionException
102
-     */
103
-    public static function event_status($EVT_ID = 0)
104
-    {
105
-        $event = EEH_Event_View::get_event($EVT_ID);
106
-        return $event instanceof EE_Event ? $event->pretty_active_status(false) : '';
107
-    }
108
-
109
-
110
-    /**
111
-     *  event_active_status
112
-     *
113
-     * @param int $EVT_ID
114
-     * @return     string
115
-     * @throws EE_Error
116
-     * @throws EE_Error
117
-     * @throws ReflectionException
118
-     */
119
-    public static function event_active_status($EVT_ID = 0, $echo = true)
120
-    {
121
-        $event = EEH_Event_View::get_event($EVT_ID);
122
-        return $event instanceof EE_Event ? $event->pretty_active_status($echo) : 'inactive';
123
-    }
124
-
125
-
126
-    /**
127
-     *  event_has_content_or_excerpt
128
-     *
129
-     * @param int $EVT_ID
130
-     * @return     bool
131
-     * @throws EE_Error
132
-     * @throws EE_Error
133
-     * @throws ReflectionException
134
-     */
135
-    public static function event_has_content_or_excerpt($EVT_ID = 0)
136
-    {
137
-        $event                  = EEH_Event_View::get_event($EVT_ID);
138
-        $has_content_or_excerpt = false;
139
-        if ($event instanceof EE_Event) {
140
-            $has_content_or_excerpt = $event->description() != '' || $event->short_description(null, null, true) != '';
141
-        }
142
-        if (
143
-            is_archive()
144
-            && ! (espresso_display_full_description_in_event_list()
145
-                  || espresso_display_excerpt_in_event_list())
146
-        ) {
147
-            $has_content_or_excerpt = false;
148
-        }
149
-        return $has_content_or_excerpt;
150
-    }
151
-
152
-
153
-    /**
154
-     *    event_active_status
155
-     *
156
-     * @param null $num_words
157
-     * @param null $more
158
-     * @return    string
159
-     */
160
-    public static function event_content_or_excerpt($num_words = null, $more = null)
161
-    {
162
-        global $post;
163
-        ob_start();
164
-        if ((is_single()) || (is_archive() && espresso_display_full_description_in_event_list())) {
165
-            // admin has chosen "full description"
166
-            // for the "Event Espresso - Events > Templates > Display Description" option
167
-            the_content();
168
-        } elseif ((is_archive() && espresso_display_excerpt_in_event_list())) {
169
-            if (has_excerpt($post->ID)) {
170
-                // admin has chosen "excerpt (short desc)"
171
-                // for the "Event Espresso - Events > Templates > Display Description" option
172
-                // AND an excerpt actually exists
173
-                the_excerpt();
174
-            } else {
175
-                // admin has chosen "excerpt (short desc)"
176
-                // for the "Event Espresso - Events > Templates > Display Description" option
177
-                // but NO excerpt actually exists, so we need to create one
178
-                if (! empty($num_words)) {
179
-                    if (empty($more)) {
180
-                        $more_link_text = esc_html__('(more&hellip;)', 'event_espresso');
181
-                        $more           = ' <a href="' . get_permalink() . '"';
182
-                        $more           .= ' class="more-link"';
183
-                        $more           .= EED_Events_Archive::link_target();
184
-                        $more           .= '>' . $more_link_text . '</a>';
185
-                        $more           = apply_filters('the_content_more_link', $more, $more_link_text);
186
-                    }
187
-                    $content = str_replace('NOMORELINK', '', get_the_content('NOMORELINK'));
188
-
189
-                    $content = wp_trim_words($content, $num_words, ' ') . $more;
190
-                } else {
191
-                    $content = get_the_content();
192
-                }
193
-                global $allowedtags;
194
-                // make sure links are allowed
195
-                $allowedtags['a'] = isset($allowedtags['a'])
196
-                    ? $allowedtags['a']
197
-                    : [];
198
-                // as well as target attribute
199
-                $allowedtags['a']['target'] = isset($allowedtags['a']['target'])
200
-                    ? $allowedtags['a']['target']
201
-                    : false;
202
-                // but get previous value so we can reset it
203
-                $prev_value                 = $allowedtags['a']['target'];
204
-                $allowedtags['a']['target'] = true;
205
-                $content                    = wp_kses($content, $allowedtags);
206
-                $content                    = strip_shortcodes($content);
207
-                echo apply_filters('the_content', $content);
208
-                $allowedtags['a']['target'] = $prev_value;
209
-            }
210
-        } else {
211
-            // admin has chosen "none"
212
-            // for the "Event Espresso - Events > Templates > Display Description" option
213
-            echo apply_filters('the_content', '');
214
-        }
215
-        return ob_get_clean();
216
-    }
217
-
218
-
219
-    /**
220
-     *  event_tickets_available
221
-     *
222
-     * @param int $EVT_ID
223
-     * @return     EE_Ticket[]
224
-     * @throws EE_Error
225
-     * @throws ReflectionException
226
-     */
227
-    public static function event_tickets_available($EVT_ID = 0)
228
-    {
229
-        $event                          = EEH_Event_View::get_event($EVT_ID);
230
-        $tickets_available_for_purchase = [];
231
-        if ($event instanceof EE_Event) {
232
-            $datetimes = EEH_Event_View::get_all_date_obj($EVT_ID, false);
233
-            foreach ($datetimes as $datetime) {
234
-                $tickets_available_for_purchase =
235
-                    array_merge($tickets_available_for_purchase, $datetime->ticket_types_available_for_purchase());
236
-            }
237
-        }
238
-        return $tickets_available_for_purchase;
239
-    }
240
-
241
-
242
-    /**
243
-     *    the_event_date
244
-     *
245
-     * @param int  $EVT_ID
246
-     * @param bool $hide_uncategorized
247
-     * @return    string
248
-     * @throws EE_Error
249
-     * @throws ReflectionException
250
-     */
251
-    public static function event_categories($EVT_ID = 0, $hide_uncategorized = true)
252
-    {
253
-        $category_links = [];
254
-        $event          = EEH_Event_View::get_event($EVT_ID);
255
-        if ($event instanceof EE_Event) {
256
-            $event_categories = get_the_terms($event->ID(), 'espresso_event_categories');
257
-            if ($event_categories) {
258
-                // loop thru terms and create links
259
-                foreach ($event_categories as $term) {
260
-                    $url = get_term_link($term, 'espresso_venue_categories');
261
-                    if (
262
-                        ! is_wp_error($url)
263
-                        && (
264
-                            (
265
-                                $hide_uncategorized
266
-                                && strtolower($term->name) != esc_html__('uncategorized', 'event_espresso')
267
-                            )
268
-                            || ! $hide_uncategorized
269
-                        )
270
-                    ) {
271
-                        $category_links[] = '<a href="' . esc_url_raw($url) . '" '
272
-                                            . 'rel="tag" ' . EED_Events_Archive::link_target() . '>'
273
-                                            . esc_html($term->name)
274
-                                            . '</a>';
275
-                    }
276
-                }
277
-            }
278
-        }
279
-        return implode(', ', $category_links);
280
-    }
281
-
282
-
283
-    /**
284
-     *    the_event_date - first date by date order
285
-     *
286
-     * @param string $date_format
287
-     * @param string $time_format
288
-     * @param int    $EVT_ID
289
-     * @return    string
290
-     * @throws EE_Error
291
-     * @throws ReflectionException
292
-     */
293
-    public static function the_event_date($date_format = 'D M jS', $time_format = 'g:i a', $EVT_ID = 0)
294
-    {
295
-        $datetime = EEH_Event_View::get_primary_date_obj($EVT_ID);
296
-        $format   = ! empty($date_format) && ! empty($time_format)
297
-                ? $date_format . ' ' . $time_format
298
-                : $date_format . $time_format;
299
-        return $datetime instanceof EE_Datetime ? $datetime->get_i18n_datetime('DTT_EVT_start', $format) : '';
300
-    }
301
-
302
-
303
-    /**
304
-     *    the_event_end_date - last date by date order
305
-     *
306
-     * @param string $date_format
307
-     * @param string $time_format
308
-     * @param int    $EVT_ID
309
-     * @return    string
310
-     * @throws EE_Error
311
-     * @throws ReflectionException
312
-     */
313
-    public static function the_event_end_date($date_format = 'D M jS', $time_format = 'g:i a', $EVT_ID = 0)
314
-    {
315
-        $datetime = EEH_Event_View::get_last_date_obj($EVT_ID);
316
-        $format   =
317
-            ! empty($date_format) && ! empty($time_format)
318
-                ? $date_format . ' ' . $time_format
319
-                : $date_format
320
-                  . $time_format;
321
-        return $datetime instanceof EE_Datetime ? $datetime->get_i18n_datetime('DTT_EVT_end', $format) : '';
322
-    }
323
-
324
-
325
-    /**
326
-     *    the_earliest_event_date - first date chronologically
327
-     *
328
-     * @param string $date_format
329
-     * @param string $time_format
330
-     * @param int    $EVT_ID
331
-     * @return    string
332
-     * @throws EE_Error
333
-     * @throws ReflectionException
334
-     */
335
-    public static function the_earliest_event_date($date_format = 'D M jS', $time_format = 'g:i a', $EVT_ID = 0)
336
-    {
337
-        $datetime = EEH_Event_View::get_earliest_date_obj($EVT_ID);
338
-        $format   =
339
-            ! empty($date_format) && ! empty($time_format)
340
-                ? $date_format . ' ' . $time_format
341
-                : $date_format
342
-                  . $time_format;
343
-        return $datetime instanceof EE_Datetime ? $datetime->get_i18n_datetime('DTT_EVT_start', $format) : '';
344
-    }
345
-
346
-
347
-    /**
348
-     *    the_latest_event_date - latest date chronologically
349
-     *
350
-     * @param string $date_format
351
-     * @param string $time_format
352
-     * @param int    $EVT_ID
353
-     * @return    string
354
-     * @throws EE_Error
355
-     * @throws ReflectionException
356
-     */
357
-    public static function the_latest_event_date($date_format = 'D M jS', $time_format = 'g:i a', $EVT_ID = 0)
358
-    {
359
-        $datetime = EEH_Event_View::get_latest_date_obj($EVT_ID);
360
-        $format   =
361
-            ! empty($date_format) && ! empty($time_format)
362
-                ? $date_format . ' ' . $time_format
363
-                : $date_format
364
-                  . $time_format;
365
-        return $datetime instanceof EE_Datetime ? $datetime->get_i18n_datetime('DTT_EVT_end', $format) : '';
366
-    }
367
-
368
-
369
-    /**
370
-     *    event_date_as_calendar_page
371
-     *
372
-     * @param int $EVT_ID
373
-     * @return    void
374
-     * @throws EE_Error
375
-     * @throws ReflectionException
376
-     */
377
-    public static function event_date_as_calendar_page($EVT_ID = 0)
378
-    {
379
-        $datetime = EEH_Event_View::get_primary_date_obj($EVT_ID);
380
-        if ($datetime instanceof EE_Datetime) {
381
-            ?>
14
+	private static ?EE_Event $_event = null;
15
+
16
+
17
+	/**
18
+	 * get_event
19
+	 * attempts to retrieve an EE_Event object any way it can
20
+	 *
21
+	 * @param int|WP_Post $EVT_ID
22
+	 * @return EE_Event|null
23
+	 * @throws EE_Error
24
+	 * @throws ReflectionException
25
+	 */
26
+	public static function get_event($EVT_ID = 0): ?EE_Event
27
+	{
28
+		// international newspaper?
29
+		global $post;
30
+		$EVT_ID = $EVT_ID instanceof WP_Post && $EVT_ID->post_type === EspressoPostType::EVENTS
31
+			? $EVT_ID->ID
32
+			: absint($EVT_ID);
33
+		// do we already have the Event  you are looking for?
34
+		if (EEH_Event_View::$_event instanceof EE_Event && $EVT_ID && EEH_Event_View::$_event->ID() === $EVT_ID) {
35
+			return EEH_Event_View::$_event;
36
+		}
37
+		// reset property so that the new event is cached.
38
+		EEH_Event_View::$_event = null;
39
+		if (! $EVT_ID && $post instanceof EE_Event) {
40
+			EEH_Event_View::$_event = $post;
41
+			return EEH_Event_View::$_event;
42
+		}
43
+		// if the post type is for an event and it has a cached event and we don't have a different incoming $EVT_ID
44
+		// then let's just use that cached event on the $post object.
45
+		if (
46
+			$post instanceof WP_Post
47
+			&& $post->post_type === EspressoPostType::EVENTS
48
+			&& isset($post->EE_Event)
49
+			&& (
50
+				$EVT_ID === 0
51
+				|| $EVT_ID === $post->ID
52
+			)
53
+		) {
54
+			EEH_Event_View::$_event = $post->EE_Event;
55
+			return EEH_Event_View::$_event;
56
+		}
57
+		// If the event we have isn't an event but we do have an EVT_ID, let's try getting the event using the ID.
58
+		if (! EEH_Event_View::$_event instanceof EE_Event && $EVT_ID) {
59
+			EEH_Event_View::$_event = EEM_Event::instance()->get_one_by_ID($EVT_ID);
60
+		}
61
+		// no? ok let's try getting the event using the ID.
62
+		if (! EEH_Event_View::$_event instanceof EE_Event && $post->ID) {
63
+			$maybe_an_event = EEM_Event::instance()->get_one_by_ID($post->ID);
64
+			EEH_Event_View::$_event = $maybe_an_event instanceof EE_Event ? $maybe_an_event : EEH_Event_View::$_event;
65
+		}
66
+		// ensure Event object is added to WP post object
67
+		if (
68
+			$post instanceof WP_Post
69
+			&& $post->post_type === EspressoPostType::EVENTS
70
+			&& EEH_Event_View::$_event instanceof EE_Event
71
+		) {
72
+			$post->EE_Event = EEH_Event_View::$_event;
73
+		}
74
+		return EEH_Event_View::$_event;
75
+	}
76
+
77
+
78
+	/**
79
+	 *    display_ticket_selector
80
+	 *
81
+	 * @param int $EVT_ID
82
+	 * @return    boolean
83
+	 * @throws EE_Error
84
+	 * @throws EE_Error
85
+	 * @throws ReflectionException
86
+	 */
87
+	public static function display_ticket_selector($EVT_ID = 0)
88
+	{
89
+		$event = EEH_Event_View::get_event($EVT_ID);
90
+		return $event instanceof EE_Event && $event->display_ticket_selector();
91
+	}
92
+
93
+
94
+	/**
95
+	 *    event_status
96
+	 *
97
+	 * @param int $EVT_ID
98
+	 * @return    string
99
+	 * @throws EE_Error
100
+	 * @throws EE_Error
101
+	 * @throws ReflectionException
102
+	 */
103
+	public static function event_status($EVT_ID = 0)
104
+	{
105
+		$event = EEH_Event_View::get_event($EVT_ID);
106
+		return $event instanceof EE_Event ? $event->pretty_active_status(false) : '';
107
+	}
108
+
109
+
110
+	/**
111
+	 *  event_active_status
112
+	 *
113
+	 * @param int $EVT_ID
114
+	 * @return     string
115
+	 * @throws EE_Error
116
+	 * @throws EE_Error
117
+	 * @throws ReflectionException
118
+	 */
119
+	public static function event_active_status($EVT_ID = 0, $echo = true)
120
+	{
121
+		$event = EEH_Event_View::get_event($EVT_ID);
122
+		return $event instanceof EE_Event ? $event->pretty_active_status($echo) : 'inactive';
123
+	}
124
+
125
+
126
+	/**
127
+	 *  event_has_content_or_excerpt
128
+	 *
129
+	 * @param int $EVT_ID
130
+	 * @return     bool
131
+	 * @throws EE_Error
132
+	 * @throws EE_Error
133
+	 * @throws ReflectionException
134
+	 */
135
+	public static function event_has_content_or_excerpt($EVT_ID = 0)
136
+	{
137
+		$event                  = EEH_Event_View::get_event($EVT_ID);
138
+		$has_content_or_excerpt = false;
139
+		if ($event instanceof EE_Event) {
140
+			$has_content_or_excerpt = $event->description() != '' || $event->short_description(null, null, true) != '';
141
+		}
142
+		if (
143
+			is_archive()
144
+			&& ! (espresso_display_full_description_in_event_list()
145
+				  || espresso_display_excerpt_in_event_list())
146
+		) {
147
+			$has_content_or_excerpt = false;
148
+		}
149
+		return $has_content_or_excerpt;
150
+	}
151
+
152
+
153
+	/**
154
+	 *    event_active_status
155
+	 *
156
+	 * @param null $num_words
157
+	 * @param null $more
158
+	 * @return    string
159
+	 */
160
+	public static function event_content_or_excerpt($num_words = null, $more = null)
161
+	{
162
+		global $post;
163
+		ob_start();
164
+		if ((is_single()) || (is_archive() && espresso_display_full_description_in_event_list())) {
165
+			// admin has chosen "full description"
166
+			// for the "Event Espresso - Events > Templates > Display Description" option
167
+			the_content();
168
+		} elseif ((is_archive() && espresso_display_excerpt_in_event_list())) {
169
+			if (has_excerpt($post->ID)) {
170
+				// admin has chosen "excerpt (short desc)"
171
+				// for the "Event Espresso - Events > Templates > Display Description" option
172
+				// AND an excerpt actually exists
173
+				the_excerpt();
174
+			} else {
175
+				// admin has chosen "excerpt (short desc)"
176
+				// for the "Event Espresso - Events > Templates > Display Description" option
177
+				// but NO excerpt actually exists, so we need to create one
178
+				if (! empty($num_words)) {
179
+					if (empty($more)) {
180
+						$more_link_text = esc_html__('(more&hellip;)', 'event_espresso');
181
+						$more           = ' <a href="' . get_permalink() . '"';
182
+						$more           .= ' class="more-link"';
183
+						$more           .= EED_Events_Archive::link_target();
184
+						$more           .= '>' . $more_link_text . '</a>';
185
+						$more           = apply_filters('the_content_more_link', $more, $more_link_text);
186
+					}
187
+					$content = str_replace('NOMORELINK', '', get_the_content('NOMORELINK'));
188
+
189
+					$content = wp_trim_words($content, $num_words, ' ') . $more;
190
+				} else {
191
+					$content = get_the_content();
192
+				}
193
+				global $allowedtags;
194
+				// make sure links are allowed
195
+				$allowedtags['a'] = isset($allowedtags['a'])
196
+					? $allowedtags['a']
197
+					: [];
198
+				// as well as target attribute
199
+				$allowedtags['a']['target'] = isset($allowedtags['a']['target'])
200
+					? $allowedtags['a']['target']
201
+					: false;
202
+				// but get previous value so we can reset it
203
+				$prev_value                 = $allowedtags['a']['target'];
204
+				$allowedtags['a']['target'] = true;
205
+				$content                    = wp_kses($content, $allowedtags);
206
+				$content                    = strip_shortcodes($content);
207
+				echo apply_filters('the_content', $content);
208
+				$allowedtags['a']['target'] = $prev_value;
209
+			}
210
+		} else {
211
+			// admin has chosen "none"
212
+			// for the "Event Espresso - Events > Templates > Display Description" option
213
+			echo apply_filters('the_content', '');
214
+		}
215
+		return ob_get_clean();
216
+	}
217
+
218
+
219
+	/**
220
+	 *  event_tickets_available
221
+	 *
222
+	 * @param int $EVT_ID
223
+	 * @return     EE_Ticket[]
224
+	 * @throws EE_Error
225
+	 * @throws ReflectionException
226
+	 */
227
+	public static function event_tickets_available($EVT_ID = 0)
228
+	{
229
+		$event                          = EEH_Event_View::get_event($EVT_ID);
230
+		$tickets_available_for_purchase = [];
231
+		if ($event instanceof EE_Event) {
232
+			$datetimes = EEH_Event_View::get_all_date_obj($EVT_ID, false);
233
+			foreach ($datetimes as $datetime) {
234
+				$tickets_available_for_purchase =
235
+					array_merge($tickets_available_for_purchase, $datetime->ticket_types_available_for_purchase());
236
+			}
237
+		}
238
+		return $tickets_available_for_purchase;
239
+	}
240
+
241
+
242
+	/**
243
+	 *    the_event_date
244
+	 *
245
+	 * @param int  $EVT_ID
246
+	 * @param bool $hide_uncategorized
247
+	 * @return    string
248
+	 * @throws EE_Error
249
+	 * @throws ReflectionException
250
+	 */
251
+	public static function event_categories($EVT_ID = 0, $hide_uncategorized = true)
252
+	{
253
+		$category_links = [];
254
+		$event          = EEH_Event_View::get_event($EVT_ID);
255
+		if ($event instanceof EE_Event) {
256
+			$event_categories = get_the_terms($event->ID(), 'espresso_event_categories');
257
+			if ($event_categories) {
258
+				// loop thru terms and create links
259
+				foreach ($event_categories as $term) {
260
+					$url = get_term_link($term, 'espresso_venue_categories');
261
+					if (
262
+						! is_wp_error($url)
263
+						&& (
264
+							(
265
+								$hide_uncategorized
266
+								&& strtolower($term->name) != esc_html__('uncategorized', 'event_espresso')
267
+							)
268
+							|| ! $hide_uncategorized
269
+						)
270
+					) {
271
+						$category_links[] = '<a href="' . esc_url_raw($url) . '" '
272
+											. 'rel="tag" ' . EED_Events_Archive::link_target() . '>'
273
+											. esc_html($term->name)
274
+											. '</a>';
275
+					}
276
+				}
277
+			}
278
+		}
279
+		return implode(', ', $category_links);
280
+	}
281
+
282
+
283
+	/**
284
+	 *    the_event_date - first date by date order
285
+	 *
286
+	 * @param string $date_format
287
+	 * @param string $time_format
288
+	 * @param int    $EVT_ID
289
+	 * @return    string
290
+	 * @throws EE_Error
291
+	 * @throws ReflectionException
292
+	 */
293
+	public static function the_event_date($date_format = 'D M jS', $time_format = 'g:i a', $EVT_ID = 0)
294
+	{
295
+		$datetime = EEH_Event_View::get_primary_date_obj($EVT_ID);
296
+		$format   = ! empty($date_format) && ! empty($time_format)
297
+				? $date_format . ' ' . $time_format
298
+				: $date_format . $time_format;
299
+		return $datetime instanceof EE_Datetime ? $datetime->get_i18n_datetime('DTT_EVT_start', $format) : '';
300
+	}
301
+
302
+
303
+	/**
304
+	 *    the_event_end_date - last date by date order
305
+	 *
306
+	 * @param string $date_format
307
+	 * @param string $time_format
308
+	 * @param int    $EVT_ID
309
+	 * @return    string
310
+	 * @throws EE_Error
311
+	 * @throws ReflectionException
312
+	 */
313
+	public static function the_event_end_date($date_format = 'D M jS', $time_format = 'g:i a', $EVT_ID = 0)
314
+	{
315
+		$datetime = EEH_Event_View::get_last_date_obj($EVT_ID);
316
+		$format   =
317
+			! empty($date_format) && ! empty($time_format)
318
+				? $date_format . ' ' . $time_format
319
+				: $date_format
320
+				  . $time_format;
321
+		return $datetime instanceof EE_Datetime ? $datetime->get_i18n_datetime('DTT_EVT_end', $format) : '';
322
+	}
323
+
324
+
325
+	/**
326
+	 *    the_earliest_event_date - first date chronologically
327
+	 *
328
+	 * @param string $date_format
329
+	 * @param string $time_format
330
+	 * @param int    $EVT_ID
331
+	 * @return    string
332
+	 * @throws EE_Error
333
+	 * @throws ReflectionException
334
+	 */
335
+	public static function the_earliest_event_date($date_format = 'D M jS', $time_format = 'g:i a', $EVT_ID = 0)
336
+	{
337
+		$datetime = EEH_Event_View::get_earliest_date_obj($EVT_ID);
338
+		$format   =
339
+			! empty($date_format) && ! empty($time_format)
340
+				? $date_format . ' ' . $time_format
341
+				: $date_format
342
+				  . $time_format;
343
+		return $datetime instanceof EE_Datetime ? $datetime->get_i18n_datetime('DTT_EVT_start', $format) : '';
344
+	}
345
+
346
+
347
+	/**
348
+	 *    the_latest_event_date - latest date chronologically
349
+	 *
350
+	 * @param string $date_format
351
+	 * @param string $time_format
352
+	 * @param int    $EVT_ID
353
+	 * @return    string
354
+	 * @throws EE_Error
355
+	 * @throws ReflectionException
356
+	 */
357
+	public static function the_latest_event_date($date_format = 'D M jS', $time_format = 'g:i a', $EVT_ID = 0)
358
+	{
359
+		$datetime = EEH_Event_View::get_latest_date_obj($EVT_ID);
360
+		$format   =
361
+			! empty($date_format) && ! empty($time_format)
362
+				? $date_format . ' ' . $time_format
363
+				: $date_format
364
+				  . $time_format;
365
+		return $datetime instanceof EE_Datetime ? $datetime->get_i18n_datetime('DTT_EVT_end', $format) : '';
366
+	}
367
+
368
+
369
+	/**
370
+	 *    event_date_as_calendar_page
371
+	 *
372
+	 * @param int $EVT_ID
373
+	 * @return    void
374
+	 * @throws EE_Error
375
+	 * @throws ReflectionException
376
+	 */
377
+	public static function event_date_as_calendar_page($EVT_ID = 0)
378
+	{
379
+		$datetime = EEH_Event_View::get_primary_date_obj($EVT_ID);
380
+		if ($datetime instanceof EE_Datetime) {
381
+			?>
382 382
             <div class="event-date-calendar-page-dv">
383 383
                 <div class="event-date-calendar-page-month-dv">
384 384
                     <?php echo esc_html($datetime->get_i18n_datetime('DTT_EVT_start', 'M')); ?>
@@ -388,275 +388,275 @@  discard block
 block discarded – undo
388 388
                 </div>
389 389
             </div>
390 390
             <?php
391
-        }
392
-    }
393
-
394
-
395
-    /**
396
-     *    get_primary_date_obj - orders date by DTT_order
397
-     *
398
-     * @param int $EVT_ID
399
-     * @return EE_Datetime
400
-     * @throws EE_Error
401
-     * @throws ReflectionException
402
-     */
403
-    public static function get_primary_date_obj($EVT_ID = 0)
404
-    {
405
-        $event = EEH_Event_View::get_event($EVT_ID);
406
-        if ($event instanceof EE_Event) {
407
-            $datetimes = $event->get_many_related(
408
-                'Datetime',
409
-                [
410
-                    'limit'    => 1,
411
-                    'order_by' => ['DTT_order' => 'ASC'],
412
-                ]
413
-            );
414
-            return reset($datetimes);
415
-        }
416
-        return null;
417
-    }
418
-
419
-
420
-    /**
421
-     *    get_last_date_obj - orders date by DTT_order
422
-     *
423
-     * @param int $EVT_ID
424
-     * @return EE_Datetime
425
-     * @throws EE_Error
426
-     * @throws ReflectionException
427
-     */
428
-    public static function get_last_date_obj($EVT_ID = 0)
429
-    {
430
-        $event = EEH_Event_View::get_event($EVT_ID);
431
-        if ($event instanceof EE_Event) {
432
-            $datetimes = $event->get_many_related(
433
-                'Datetime',
434
-                [
435
-                    'limit'    => 1,
436
-                    'order_by' => ['DTT_order' => 'DESC'],
437
-                ]
438
-            );
439
-            return end($datetimes);
440
-        }
441
-        return null;
442
-    }
443
-
444
-
445
-    /**
446
-     *    get_earliest_date_obj - orders date chronologically
447
-     *
448
-     * @param int $EVT_ID
449
-     * @return EE_Datetime
450
-     * @throws EE_Error
451
-     * @throws ReflectionException
452
-     */
453
-    public static function get_earliest_date_obj($EVT_ID = 0)
454
-    {
455
-        $event = EEH_Event_View::get_event($EVT_ID);
456
-        if ($event instanceof EE_Event) {
457
-            $datetimes = $event->get_many_related(
458
-                'Datetime',
459
-                [
460
-                    'limit'    => 1,
461
-                    'order_by' => ['DTT_EVT_start' => 'ASC'],
462
-                ]
463
-            );
464
-            return reset($datetimes);
465
-        }
466
-        return null;
467
-    }
468
-
469
-
470
-    /**
471
-     *    get_latest_date_obj - orders date chronologically
472
-     *
473
-     * @param int $EVT_ID
474
-     * @return EE_Datetime
475
-     * @throws EE_Error
476
-     * @throws ReflectionException
477
-     */
478
-    public static function get_latest_date_obj($EVT_ID = 0)
479
-    {
480
-        $event = EEH_Event_View::get_event($EVT_ID);
481
-        if ($event instanceof EE_Event) {
482
-            $datetimes = $event->get_many_related(
483
-                'Datetime',
484
-                [
485
-                    'limit'    => 1,
486
-                    'order_by' => ['DTT_EVT_start' => 'DESC'],
487
-                ]
488
-            );
489
-            return end($datetimes);
490
-        }
491
-        return null;
492
-    }
493
-
494
-
495
-    /**
496
-     *    get_next_upcoming_date_obj - return the next upcoming datetime
497
-     *
498
-     * @param int $EVT_ID
499
-     * @return    EE_Datetime|null
500
-     * @throws EE_Error
501
-     * @throws EE_Error
502
-     */
503
-    public static function get_next_upcoming_date_obj($EVT_ID = 0)
504
-    {
505
-        $datetime = EEM_Datetime::instance()->get_one(
506
-            [
507
-                [
508
-                    'Event.EVT_ID'  => $EVT_ID,
509
-                    'DTT_EVT_start' => ['>=', current_time('mysql', true)],
510
-                ],
511
-                'order_by' => ['DTT_EVT_start' => 'asc'],
512
-            ]
513
-        );
514
-        return $datetime instanceof EE_Datetime ? $datetime : null;
515
-    }
516
-
517
-
518
-    /**
519
-     *    get_next_upcoming_date_obj - return the next upcoming datetime
520
-     *
521
-     * @param int $DTT_ID
522
-     * @return EE_Datetime|null
523
-     * @throws EE_Error
524
-     * @throws ReflectionException
525
-     */
526
-    public static function get_date_obj(int $DTT_ID = 0): ?EE_Datetime
527
-    {
528
-        $datetime = EEM_Datetime::instance()->get_one_by_ID($DTT_ID);
529
-        return $datetime instanceof EE_Datetime ? $datetime : null;
530
-    }
531
-
532
-
533
-    /**
534
-     *    get_all_date_obj
535
-     *
536
-     * @param int  $EVT_ID
537
-     * @param null $include_expired
538
-     * @param bool $include_deleted
539
-     * @param null $limit
540
-     * @return EE_Datetime[]
541
-     * @throws EE_Error
542
-     * @throws EE_Error
543
-     * @throws ReflectionException
544
-     */
545
-    public static function get_all_date_obj(
546
-        $EVT_ID = 0,
547
-        $include_expired = null,
548
-        $include_deleted = false,
549
-        $limit = null
550
-    ) {
551
-        $event = EEH_Event_View::get_event($EVT_ID);
552
-        if ($include_expired === null) {
553
-            if ($event instanceof EE_Event && $event->is_expired()) {
554
-                $include_expired = true;
555
-            } else {
556
-                $include_expired = false;
557
-            }
558
-        }
559
-
560
-        if ($event instanceof EE_Event) {
561
-            return $event->datetimes_ordered($include_expired, $include_deleted, $limit);
562
-        }
563
-        return [];
564
-    }
565
-
566
-
567
-    /**
568
-     *    event_link_url
569
-     *
570
-     * @param int $EVT_ID
571
-     * @return string
572
-     * @throws EE_Error
573
-     * @throws ReflectionException
574
-     */
575
-    public static function event_link_url($EVT_ID = 0)
576
-    {
577
-        $event = EEH_Event_View::get_event($EVT_ID);
578
-        if ($event instanceof EE_Event) {
579
-            $url = $event->external_url() !== null && $event->external_url() !== ''
580
-                ? $event->external_url()
581
-                : get_permalink($event->ID());
582
-            $url = preg_match("~^(?:f|ht)tps?://~i", $url) ? $url : 'https://' . $url;
583
-            return esc_url_raw($url);
584
-        }
585
-        return '';
586
-    }
587
-
588
-
589
-    /**
590
-     *    event_phone
591
-     *
592
-     * @param int $EVT_ID
593
-     * @return    string
594
-     * @throws EE_Error
595
-     * @throws EE_Error
596
-     * @throws ReflectionException
597
-     */
598
-    public static function event_phone($EVT_ID = 0)
599
-    {
600
-        $event = EEH_Event_View::get_event($EVT_ID);
601
-        if ($event instanceof EE_Event) {
602
-            return EEH_Schema::telephone($event->phone());
603
-        }
604
-        return null;
605
-    }
606
-
607
-
608
-    /**
609
-     *    edit_event_link
610
-     *
611
-     * @param int    $EVT_ID
612
-     * @param string $link
613
-     * @param string $before
614
-     * @param string $after
615
-     * @return    string
616
-     * @throws EE_Error
617
-     * @throws ReflectionException
618
-     */
619
-    public static function edit_event_link($EVT_ID = 0, $link = '', $before = '', $after = '')
620
-    {
621
-        $event = EEH_Event_View::get_event($EVT_ID);
622
-        if ($event instanceof EE_Event) {
623
-            // can the user edit this post ?
624
-            if (current_user_can('edit_post', $event->ID())) {
625
-                // set link text
626
-                $link_text = ! empty($link) ? $link : esc_html__('edit this event', 'event_espresso');
627
-                // generate nonce
628
-                $nonce = wp_create_nonce('edit_nonce');
629
-                // generate url to event editor for this event
630
-                $url =
631
-                    add_query_arg(
632
-                        [
633
-                            'page'       => 'espresso_events',
634
-                            'action'     => 'edit',
635
-                            'post'       => $event->ID(),
636
-                            'edit_nonce' => $nonce,
637
-                        ],
638
-                        admin_url()
639
-                    );
640
-                // get edit CPT text
641
-                $post_type_obj = get_post_type_object(EspressoPostType::EVENTS);
642
-                // build final link html
643
-                $link = '<a class="post-edit-link" href="' . $url . '" ';
644
-                $link .= ' title="' . esc_attr($post_type_obj->labels->edit_item) . '"';
645
-                $link .= EED_Events_Archive::link_target();
646
-                $link .= '>' . $link_text . '</a>';
647
-                // put it all together
648
-                return $before . apply_filters('edit_post_link', $link, $event->ID()) . $after;
649
-            }
650
-        }
651
-        return '';
652
-    }
653
-
654
-
655
-    /**
656
-     * @return string
657
-     */
658
-    public static function event_archive_url()
659
-    {
660
-        return get_post_type_archive_link(EspressoPostType::EVENTS);
661
-    }
391
+		}
392
+	}
393
+
394
+
395
+	/**
396
+	 *    get_primary_date_obj - orders date by DTT_order
397
+	 *
398
+	 * @param int $EVT_ID
399
+	 * @return EE_Datetime
400
+	 * @throws EE_Error
401
+	 * @throws ReflectionException
402
+	 */
403
+	public static function get_primary_date_obj($EVT_ID = 0)
404
+	{
405
+		$event = EEH_Event_View::get_event($EVT_ID);
406
+		if ($event instanceof EE_Event) {
407
+			$datetimes = $event->get_many_related(
408
+				'Datetime',
409
+				[
410
+					'limit'    => 1,
411
+					'order_by' => ['DTT_order' => 'ASC'],
412
+				]
413
+			);
414
+			return reset($datetimes);
415
+		}
416
+		return null;
417
+	}
418
+
419
+
420
+	/**
421
+	 *    get_last_date_obj - orders date by DTT_order
422
+	 *
423
+	 * @param int $EVT_ID
424
+	 * @return EE_Datetime
425
+	 * @throws EE_Error
426
+	 * @throws ReflectionException
427
+	 */
428
+	public static function get_last_date_obj($EVT_ID = 0)
429
+	{
430
+		$event = EEH_Event_View::get_event($EVT_ID);
431
+		if ($event instanceof EE_Event) {
432
+			$datetimes = $event->get_many_related(
433
+				'Datetime',
434
+				[
435
+					'limit'    => 1,
436
+					'order_by' => ['DTT_order' => 'DESC'],
437
+				]
438
+			);
439
+			return end($datetimes);
440
+		}
441
+		return null;
442
+	}
443
+
444
+
445
+	/**
446
+	 *    get_earliest_date_obj - orders date chronologically
447
+	 *
448
+	 * @param int $EVT_ID
449
+	 * @return EE_Datetime
450
+	 * @throws EE_Error
451
+	 * @throws ReflectionException
452
+	 */
453
+	public static function get_earliest_date_obj($EVT_ID = 0)
454
+	{
455
+		$event = EEH_Event_View::get_event($EVT_ID);
456
+		if ($event instanceof EE_Event) {
457
+			$datetimes = $event->get_many_related(
458
+				'Datetime',
459
+				[
460
+					'limit'    => 1,
461
+					'order_by' => ['DTT_EVT_start' => 'ASC'],
462
+				]
463
+			);
464
+			return reset($datetimes);
465
+		}
466
+		return null;
467
+	}
468
+
469
+
470
+	/**
471
+	 *    get_latest_date_obj - orders date chronologically
472
+	 *
473
+	 * @param int $EVT_ID
474
+	 * @return EE_Datetime
475
+	 * @throws EE_Error
476
+	 * @throws ReflectionException
477
+	 */
478
+	public static function get_latest_date_obj($EVT_ID = 0)
479
+	{
480
+		$event = EEH_Event_View::get_event($EVT_ID);
481
+		if ($event instanceof EE_Event) {
482
+			$datetimes = $event->get_many_related(
483
+				'Datetime',
484
+				[
485
+					'limit'    => 1,
486
+					'order_by' => ['DTT_EVT_start' => 'DESC'],
487
+				]
488
+			);
489
+			return end($datetimes);
490
+		}
491
+		return null;
492
+	}
493
+
494
+
495
+	/**
496
+	 *    get_next_upcoming_date_obj - return the next upcoming datetime
497
+	 *
498
+	 * @param int $EVT_ID
499
+	 * @return    EE_Datetime|null
500
+	 * @throws EE_Error
501
+	 * @throws EE_Error
502
+	 */
503
+	public static function get_next_upcoming_date_obj($EVT_ID = 0)
504
+	{
505
+		$datetime = EEM_Datetime::instance()->get_one(
506
+			[
507
+				[
508
+					'Event.EVT_ID'  => $EVT_ID,
509
+					'DTT_EVT_start' => ['>=', current_time('mysql', true)],
510
+				],
511
+				'order_by' => ['DTT_EVT_start' => 'asc'],
512
+			]
513
+		);
514
+		return $datetime instanceof EE_Datetime ? $datetime : null;
515
+	}
516
+
517
+
518
+	/**
519
+	 *    get_next_upcoming_date_obj - return the next upcoming datetime
520
+	 *
521
+	 * @param int $DTT_ID
522
+	 * @return EE_Datetime|null
523
+	 * @throws EE_Error
524
+	 * @throws ReflectionException
525
+	 */
526
+	public static function get_date_obj(int $DTT_ID = 0): ?EE_Datetime
527
+	{
528
+		$datetime = EEM_Datetime::instance()->get_one_by_ID($DTT_ID);
529
+		return $datetime instanceof EE_Datetime ? $datetime : null;
530
+	}
531
+
532
+
533
+	/**
534
+	 *    get_all_date_obj
535
+	 *
536
+	 * @param int  $EVT_ID
537
+	 * @param null $include_expired
538
+	 * @param bool $include_deleted
539
+	 * @param null $limit
540
+	 * @return EE_Datetime[]
541
+	 * @throws EE_Error
542
+	 * @throws EE_Error
543
+	 * @throws ReflectionException
544
+	 */
545
+	public static function get_all_date_obj(
546
+		$EVT_ID = 0,
547
+		$include_expired = null,
548
+		$include_deleted = false,
549
+		$limit = null
550
+	) {
551
+		$event = EEH_Event_View::get_event($EVT_ID);
552
+		if ($include_expired === null) {
553
+			if ($event instanceof EE_Event && $event->is_expired()) {
554
+				$include_expired = true;
555
+			} else {
556
+				$include_expired = false;
557
+			}
558
+		}
559
+
560
+		if ($event instanceof EE_Event) {
561
+			return $event->datetimes_ordered($include_expired, $include_deleted, $limit);
562
+		}
563
+		return [];
564
+	}
565
+
566
+
567
+	/**
568
+	 *    event_link_url
569
+	 *
570
+	 * @param int $EVT_ID
571
+	 * @return string
572
+	 * @throws EE_Error
573
+	 * @throws ReflectionException
574
+	 */
575
+	public static function event_link_url($EVT_ID = 0)
576
+	{
577
+		$event = EEH_Event_View::get_event($EVT_ID);
578
+		if ($event instanceof EE_Event) {
579
+			$url = $event->external_url() !== null && $event->external_url() !== ''
580
+				? $event->external_url()
581
+				: get_permalink($event->ID());
582
+			$url = preg_match("~^(?:f|ht)tps?://~i", $url) ? $url : 'https://' . $url;
583
+			return esc_url_raw($url);
584
+		}
585
+		return '';
586
+	}
587
+
588
+
589
+	/**
590
+	 *    event_phone
591
+	 *
592
+	 * @param int $EVT_ID
593
+	 * @return    string
594
+	 * @throws EE_Error
595
+	 * @throws EE_Error
596
+	 * @throws ReflectionException
597
+	 */
598
+	public static function event_phone($EVT_ID = 0)
599
+	{
600
+		$event = EEH_Event_View::get_event($EVT_ID);
601
+		if ($event instanceof EE_Event) {
602
+			return EEH_Schema::telephone($event->phone());
603
+		}
604
+		return null;
605
+	}
606
+
607
+
608
+	/**
609
+	 *    edit_event_link
610
+	 *
611
+	 * @param int    $EVT_ID
612
+	 * @param string $link
613
+	 * @param string $before
614
+	 * @param string $after
615
+	 * @return    string
616
+	 * @throws EE_Error
617
+	 * @throws ReflectionException
618
+	 */
619
+	public static function edit_event_link($EVT_ID = 0, $link = '', $before = '', $after = '')
620
+	{
621
+		$event = EEH_Event_View::get_event($EVT_ID);
622
+		if ($event instanceof EE_Event) {
623
+			// can the user edit this post ?
624
+			if (current_user_can('edit_post', $event->ID())) {
625
+				// set link text
626
+				$link_text = ! empty($link) ? $link : esc_html__('edit this event', 'event_espresso');
627
+				// generate nonce
628
+				$nonce = wp_create_nonce('edit_nonce');
629
+				// generate url to event editor for this event
630
+				$url =
631
+					add_query_arg(
632
+						[
633
+							'page'       => 'espresso_events',
634
+							'action'     => 'edit',
635
+							'post'       => $event->ID(),
636
+							'edit_nonce' => $nonce,
637
+						],
638
+						admin_url()
639
+					);
640
+				// get edit CPT text
641
+				$post_type_obj = get_post_type_object(EspressoPostType::EVENTS);
642
+				// build final link html
643
+				$link = '<a class="post-edit-link" href="' . $url . '" ';
644
+				$link .= ' title="' . esc_attr($post_type_obj->labels->edit_item) . '"';
645
+				$link .= EED_Events_Archive::link_target();
646
+				$link .= '>' . $link_text . '</a>';
647
+				// put it all together
648
+				return $before . apply_filters('edit_post_link', $link, $event->ID()) . $after;
649
+			}
650
+		}
651
+		return '';
652
+	}
653
+
654
+
655
+	/**
656
+	 * @return string
657
+	 */
658
+	public static function event_archive_url()
659
+	{
660
+		return get_post_type_archive_link(EspressoPostType::EVENTS);
661
+	}
662 662
 }
Please login to merge, or discard this patch.
Spacing   +20 added lines, -20 removed lines patch added patch discarded remove patch
@@ -36,7 +36,7 @@  discard block
 block discarded – undo
36 36
         }
37 37
         // reset property so that the new event is cached.
38 38
         EEH_Event_View::$_event = null;
39
-        if (! $EVT_ID && $post instanceof EE_Event) {
39
+        if ( ! $EVT_ID && $post instanceof EE_Event) {
40 40
             EEH_Event_View::$_event = $post;
41 41
             return EEH_Event_View::$_event;
42 42
         }
@@ -55,11 +55,11 @@  discard block
 block discarded – undo
55 55
             return EEH_Event_View::$_event;
56 56
         }
57 57
         // If the event we have isn't an event but we do have an EVT_ID, let's try getting the event using the ID.
58
-        if (! EEH_Event_View::$_event instanceof EE_Event && $EVT_ID) {
58
+        if ( ! EEH_Event_View::$_event instanceof EE_Event && $EVT_ID) {
59 59
             EEH_Event_View::$_event = EEM_Event::instance()->get_one_by_ID($EVT_ID);
60 60
         }
61 61
         // no? ok let's try getting the event using the ID.
62
-        if (! EEH_Event_View::$_event instanceof EE_Event && $post->ID) {
62
+        if ( ! EEH_Event_View::$_event instanceof EE_Event && $post->ID) {
63 63
             $maybe_an_event = EEM_Event::instance()->get_one_by_ID($post->ID);
64 64
             EEH_Event_View::$_event = $maybe_an_event instanceof EE_Event ? $maybe_an_event : EEH_Event_View::$_event;
65 65
         }
@@ -175,18 +175,18 @@  discard block
 block discarded – undo
175 175
                 // admin has chosen "excerpt (short desc)"
176 176
                 // for the "Event Espresso - Events > Templates > Display Description" option
177 177
                 // but NO excerpt actually exists, so we need to create one
178
-                if (! empty($num_words)) {
178
+                if ( ! empty($num_words)) {
179 179
                     if (empty($more)) {
180 180
                         $more_link_text = esc_html__('(more&hellip;)', 'event_espresso');
181
-                        $more           = ' <a href="' . get_permalink() . '"';
181
+                        $more           = ' <a href="'.get_permalink().'"';
182 182
                         $more           .= ' class="more-link"';
183 183
                         $more           .= EED_Events_Archive::link_target();
184
-                        $more           .= '>' . $more_link_text . '</a>';
185
-                        $more           = apply_filters('the_content_more_link', $more, $more_link_text);
184
+                        $more           .= '>'.$more_link_text.'</a>';
185
+                        $more = apply_filters('the_content_more_link', $more, $more_link_text);
186 186
                     }
187 187
                     $content = str_replace('NOMORELINK', '', get_the_content('NOMORELINK'));
188 188
 
189
-                    $content = wp_trim_words($content, $num_words, ' ') . $more;
189
+                    $content = wp_trim_words($content, $num_words, ' ').$more;
190 190
                 } else {
191 191
                     $content = get_the_content();
192 192
                 }
@@ -268,8 +268,8 @@  discard block
 block discarded – undo
268 268
                             || ! $hide_uncategorized
269 269
                         )
270 270
                     ) {
271
-                        $category_links[] = '<a href="' . esc_url_raw($url) . '" '
272
-                                            . 'rel="tag" ' . EED_Events_Archive::link_target() . '>'
271
+                        $category_links[] = '<a href="'.esc_url_raw($url).'" '
272
+                                            . 'rel="tag" '.EED_Events_Archive::link_target().'>'
273 273
                                             . esc_html($term->name)
274 274
                                             . '</a>';
275 275
                     }
@@ -294,8 +294,8 @@  discard block
 block discarded – undo
294 294
     {
295 295
         $datetime = EEH_Event_View::get_primary_date_obj($EVT_ID);
296 296
         $format   = ! empty($date_format) && ! empty($time_format)
297
-                ? $date_format . ' ' . $time_format
298
-                : $date_format . $time_format;
297
+                ? $date_format.' '.$time_format
298
+                : $date_format.$time_format;
299 299
         return $datetime instanceof EE_Datetime ? $datetime->get_i18n_datetime('DTT_EVT_start', $format) : '';
300 300
     }
301 301
 
@@ -315,7 +315,7 @@  discard block
 block discarded – undo
315 315
         $datetime = EEH_Event_View::get_last_date_obj($EVT_ID);
316 316
         $format   =
317 317
             ! empty($date_format) && ! empty($time_format)
318
-                ? $date_format . ' ' . $time_format
318
+                ? $date_format.' '.$time_format
319 319
                 : $date_format
320 320
                   . $time_format;
321 321
         return $datetime instanceof EE_Datetime ? $datetime->get_i18n_datetime('DTT_EVT_end', $format) : '';
@@ -337,7 +337,7 @@  discard block
 block discarded – undo
337 337
         $datetime = EEH_Event_View::get_earliest_date_obj($EVT_ID);
338 338
         $format   =
339 339
             ! empty($date_format) && ! empty($time_format)
340
-                ? $date_format . ' ' . $time_format
340
+                ? $date_format.' '.$time_format
341 341
                 : $date_format
342 342
                   . $time_format;
343 343
         return $datetime instanceof EE_Datetime ? $datetime->get_i18n_datetime('DTT_EVT_start', $format) : '';
@@ -359,7 +359,7 @@  discard block
 block discarded – undo
359 359
         $datetime = EEH_Event_View::get_latest_date_obj($EVT_ID);
360 360
         $format   =
361 361
             ! empty($date_format) && ! empty($time_format)
362
-                ? $date_format . ' ' . $time_format
362
+                ? $date_format.' '.$time_format
363 363
                 : $date_format
364 364
                   . $time_format;
365 365
         return $datetime instanceof EE_Datetime ? $datetime->get_i18n_datetime('DTT_EVT_end', $format) : '';
@@ -579,7 +579,7 @@  discard block
 block discarded – undo
579 579
             $url = $event->external_url() !== null && $event->external_url() !== ''
580 580
                 ? $event->external_url()
581 581
                 : get_permalink($event->ID());
582
-            $url = preg_match("~^(?:f|ht)tps?://~i", $url) ? $url : 'https://' . $url;
582
+            $url = preg_match("~^(?:f|ht)tps?://~i", $url) ? $url : 'https://'.$url;
583 583
             return esc_url_raw($url);
584 584
         }
585 585
         return '';
@@ -640,12 +640,12 @@  discard block
 block discarded – undo
640 640
                 // get edit CPT text
641 641
                 $post_type_obj = get_post_type_object(EspressoPostType::EVENTS);
642 642
                 // build final link html
643
-                $link = '<a class="post-edit-link" href="' . $url . '" ';
644
-                $link .= ' title="' . esc_attr($post_type_obj->labels->edit_item) . '"';
643
+                $link = '<a class="post-edit-link" href="'.$url.'" ';
644
+                $link .= ' title="'.esc_attr($post_type_obj->labels->edit_item).'"';
645 645
                 $link .= EED_Events_Archive::link_target();
646
-                $link .= '>' . $link_text . '</a>';
646
+                $link .= '>'.$link_text.'</a>';
647 647
                 // put it all together
648
-                return $before . apply_filters('edit_post_link', $link, $event->ID()) . $after;
648
+                return $before.apply_filters('edit_post_link', $link, $event->ID()).$after;
649 649
             }
650 650
         }
651 651
         return '';
Please login to merge, or discard this patch.
core/helpers/EEH_Debug_Tools.helper.php 1 patch
Indentation   +717 added lines, -717 removed lines patch added patch discarded remove patch
@@ -15,718 +15,718 @@  discard block
 block discarded – undo
15 15
  */
16 16
 class EEH_Debug_Tools
17 17
 {
18
-    /**
19
-     *    instance of the EEH_Autoloader object
20
-     *
21
-     * @var    $_instance
22
-     * @access    private
23
-     */
24
-    private static $_instance;
25
-
26
-    /**
27
-     * @var array
28
-     */
29
-    protected $_memory_usage_points = array();
30
-
31
-
32
-
33
-    /**
34
-     * @singleton method used to instantiate class object
35
-     * @access    public
36
-     * @return EEH_Debug_Tools
37
-     */
38
-    public static function instance()
39
-    {
40
-        // check if class object is instantiated, and instantiated properly
41
-        if (! self::$_instance instanceof EEH_Debug_Tools) {
42
-            self::$_instance = new self();
43
-        }
44
-        return self::$_instance;
45
-    }
46
-
47
-
48
-
49
-    /**
50
-     * private class constructor
51
-     */
52
-    private function __construct()
53
-    {
54
-        // load Kint PHP debugging library
55
-        if (
56
-            defined('EE_LOAD_KINT')
57
-            && ! class_exists('Kint')
58
-            && file_exists(EE_PLUGIN_DIR_PATH . 'tests/kint/Kint.class.php')
59
-        ) {
60
-            // despite EE4 having a check for an existing copy of the Kint debugging class,
61
-            // if another plugin was loaded AFTER EE4 and they did NOT perform a similar check,
62
-            // then hilarity would ensue as PHP throws a "Cannot redeclare class Kint" error
63
-            // so we've moved it to our test folder so that it is not included with production releases
64
-            // plz use https://wordpress.org/plugins/kint-debugger/  if testing production versions of EE
65
-            require_once(EE_PLUGIN_DIR_PATH . 'tests/kint/Kint.class.php');
66
-        }
67
-        $plugin = basename(EE_PLUGIN_DIR_PATH);
68
-        add_action("activate_{$plugin}", array('EEH_Debug_Tools', 'ee_plugin_activation_errors'));
69
-        add_action('activated_plugin', array('EEH_Debug_Tools', 'ee_plugin_activation_errors'));
70
-        add_action('shutdown', array('EEH_Debug_Tools', 'show_db_name'));
71
-    }
72
-
73
-
74
-
75
-    /**
76
-     *    show_db_name
77
-     *
78
-     * @return void
79
-     */
80
-    public static function show_db_name()
81
-    {
82
-        if (! defined('DOING_AJAX') && (defined('EE_ERROR_EMAILS') && EE_ERROR_EMAILS)) {
83
-            echo '<p style="font-size:10px;font-weight:normal;color:#E76700;margin: 1em 2em; text-align: right;">DB_NAME: '
84
-                 . DB_NAME
85
-                 . '</p>';
86
-        }
87
-        if (EE_DEBUG) {
88
-            Benchmark::displayResults();
89
-        }
90
-    }
91
-
92
-
93
-
94
-    /**
95
-     *    dump EE_Session object at bottom of page after everything else has happened
96
-     *
97
-     * @return void
98
-     */
99
-    public function espresso_session_footer_dump()
100
-    {
101
-        if (
102
-            (defined('WP_DEBUG') && WP_DEBUG)
103
-            && ! defined('DOING_AJAX')
104
-            && class_exists('Kint')
105
-            && function_exists('wp_get_current_user')
106
-            && current_user_can('update_core')
107
-            && class_exists('EE_Registry')
108
-        ) {
109
-            Kint::dump(EE_Registry::instance()->SSN->id());
110
-            Kint::dump(EE_Registry::instance()->SSN);
111
-            //          Kint::dump( EE_Registry::instance()->SSN->get_session_data('cart')->get_tickets() );
112
-            $this->espresso_list_hooked_functions();
113
-            Benchmark::displayResults();
114
-        }
115
-    }
116
-
117
-
118
-
119
-    /**
120
-     *    List All Hooked Functions
121
-     *    to list all functions for a specific hook, add ee_list_hooks={hook-name} to URL
122
-     *    http://wp.smashingmagazine.com/2009/08/18/10-useful-wordpress-hook-hacks/
123
-     *
124
-     * @param string $tag
125
-     * @return void
126
-     */
127
-    public function espresso_list_hooked_functions($tag = '')
128
-    {
129
-        global $wp_filter;
130
-        echo '<br/><br/><br/><h3>Hooked Functions</h3>';
131
-        if ($tag) {
132
-            $hook[ $tag ] = $wp_filter[ $tag ];
133
-            if (! is_array($hook[ $tag ])) {
134
-                trigger_error("Nothing found for '$tag' hook", E_USER_WARNING);
135
-                return;
136
-            }
137
-            echo '<h5>For Tag: ' . esc_html($tag) . '</h5>';
138
-        } else {
139
-            $hook = is_array($wp_filter) ? $wp_filter : array($wp_filter);
140
-            ksort($hook);
141
-        }
142
-        foreach ($hook as $tag_name => $priorities) {
143
-            echo "<br />&gt;&gt;&gt;&gt;&gt;\t<strong>esc_html($tag_name)</strong><br />";
144
-            ksort($priorities);
145
-            foreach ($priorities as $priority => $function) {
146
-                echo esc_html($priority);
147
-                foreach ($function as $name => $properties) {
148
-                    $name = esc_html($name);
149
-                    echo "\t$name<br />";
150
-                }
151
-            }
152
-        }
153
-    }
154
-
155
-
156
-
157
-    /**
158
-     *    registered_filter_callbacks
159
-     *
160
-     * @param string $hook_name
161
-     * @return array
162
-     */
163
-    public static function registered_filter_callbacks($hook_name = '')
164
-    {
165
-        $filters = array();
166
-        global $wp_filter;
167
-        if (isset($wp_filter[ $hook_name ])) {
168
-            $filters[ $hook_name ] = array();
169
-            foreach ($wp_filter[ $hook_name ] as $priority => $callbacks) {
170
-                $filters[ $hook_name ][ $priority ] = array();
171
-                foreach ($callbacks as $callback) {
172
-                    $filters[ $hook_name ][ $priority ][] = $callback['function'];
173
-                }
174
-            }
175
-        }
176
-        return $filters;
177
-    }
178
-
179
-
180
-
181
-    /**
182
-     *    captures plugin activation errors for debugging
183
-     *
184
-     * @return void
185
-     * @throws EE_Error
186
-     */
187
-    public static function ee_plugin_activation_errors()
188
-    {
189
-        if (WP_DEBUG) {
190
-            $activation_errors = ob_get_contents();
191
-            if (empty($activation_errors)) {
192
-                return;
193
-            }
194
-            $activation_errors = date('Y-m-d H:i:s') . "\n" . $activation_errors;
195
-            espresso_load_required('EEH_File', EE_HELPERS . 'EEH_File.helper.php');
196
-            if (class_exists('EEH_File')) {
197
-                try {
198
-                    EEH_File::ensure_file_exists_and_is_writable(
199
-                        EVENT_ESPRESSO_UPLOAD_DIR . 'logs/espresso_plugin_activation_errors.html'
200
-                    );
201
-                    EEH_File::write_to_file(
202
-                        EVENT_ESPRESSO_UPLOAD_DIR . 'logs/espresso_plugin_activation_errors.html',
203
-                        $activation_errors
204
-                    );
205
-                } catch (EE_Error $e) {
206
-                    EE_Error::add_error(
207
-                        sprintf(
208
-                            esc_html__(
209
-                                'The Event Espresso activation errors file could not be setup because: %s',
210
-                                'event_espresso'
211
-                            ),
212
-                            $e->getMessage()
213
-                        ),
214
-                        __FILE__,
215
-                        __FUNCTION__,
216
-                        __LINE__
217
-                    );
218
-                }
219
-            } else {
220
-                // old school attempt
221
-                file_put_contents(
222
-                    EVENT_ESPRESSO_UPLOAD_DIR . 'logs/espresso_plugin_activation_errors.html',
223
-                    $activation_errors
224
-                );
225
-            }
226
-            $activation_errors = get_option('ee_plugin_activation_errors', '') . $activation_errors;
227
-            update_option('ee_plugin_activation_errors', $activation_errors);
228
-        }
229
-    }
230
-
231
-
232
-
233
-    /**
234
-     * This basically mimics the WordPress _doing_it_wrong() function except adds our own messaging etc.
235
-     * Very useful for providing helpful messages to developers when the method of doing something has been deprecated,
236
-     * or we want to make sure they use something the right way.
237
-     *
238
-     * @access public
239
-     * @param string $function      The function that was called
240
-     * @param string $message       A message explaining what has been done incorrectly
241
-     * @param string $version       The version of Event Espresso where the error was added
242
-     * @param string $applies_when  a version string for when you want the doing_it_wrong notice to begin appearing
243
-     *                              for a deprecated function. This allows deprecation to occur during one version,
244
-     *                              but not have any notices appear until a later version. This allows developers
245
-     *                              extra time to update their code before notices appear.
246
-     * @param int    $error_type
247
-     * @uses   trigger_error()
248
-     */
249
-    public function doing_it_wrong(
250
-        $function,
251
-        $message,
252
-        $version,
253
-        $applies_when = '',
254
-        $error_type = null
255
-    ) {
256
-        $applies_when = ! empty($applies_when) ? $applies_when : espresso_version();
257
-        $error_type = $error_type !== null ? $error_type : E_USER_NOTICE;
258
-        // because we swapped the parameter order around for the last two params,
259
-        // let's verify that some third party isn't still passing an error type value for the third param
260
-        if (is_int($applies_when)) {
261
-            $error_type = $applies_when;
262
-            $applies_when = espresso_version();
263
-        }
264
-        // if not displaying notices yet, then just leave
265
-        if (version_compare(espresso_version(), $applies_when, '<')) {
266
-            return;
267
-        }
268
-        do_action('AHEE__EEH_Debug_Tools__doing_it_wrong_run', $function, $message, $version);
269
-        $version = $version === null
270
-            ? ''
271
-            : sprintf(
272
-                esc_html__('(This message was added in version %s of Event Espresso)', 'event_espresso'),
273
-                $version
274
-            );
275
-        $error_message = sprintf(
276
-            esc_html__('%1$s was called %2$sincorrectly%3$s. %4$s %5$s', 'event_espresso'),
277
-            $function,
278
-            '<strong>',
279
-            '</strong>',
280
-            $message,
281
-            $version
282
-        );
283
-        // don't trigger error if doing ajax,
284
-        // instead we'll add a transient EE_Error notice that in theory should show on the next request.
285
-        if (defined('DOING_AJAX') && DOING_AJAX) {
286
-            $error_message .= ' ' . esc_html__(
287
-                'This is a doing_it_wrong message that was triggered during an ajax request.  The request params on this request were: ',
288
-                'event_espresso'
289
-            );
290
-            $request = LoaderFactory::getLoader()->getShared(RequestInterface::class);
291
-            $error_message .= '<ul><li>';
292
-            $error_message .= implode('</li><li>', $request->requestParams());
293
-            $error_message .= '</ul>';
294
-            EE_Error::add_error($error_message, 'debug::doing_it_wrong', $function, '42');
295
-            // now we set this on the transient so it shows up on the next request.
296
-            EE_Error::get_notices(false, true);
297
-        } else {
298
-            trigger_error($error_message, $error_type);
299
-        }
300
-    }
301
-
302
-
303
-
304
-
305
-    /**
306
-     * Logger helpers
307
-     */
308
-    /**
309
-     * debug
310
-     *
311
-     * @param string $class
312
-     * @param string $func
313
-     * @param string $line
314
-     * @param array  $info
315
-     * @param bool   $display_request
316
-     * @param string $debug_index
317
-     * @param string $debug_key
318
-     */
319
-    public static function log(
320
-        $class = '',
321
-        $func = '',
322
-        $line = '',
323
-        $info = array(),
324
-        $display_request = false,
325
-        $debug_index = '',
326
-        $debug_key = 'EE_DEBUG_SPCO'
327
-    ) {
328
-        if (WP_DEBUG) {
329
-            $debug_key = $debug_key . '_' . EE_Session::instance()->id();
330
-            $debug_data = get_option($debug_key, array());
331
-            $default_data = array(
332
-                $class => $func . '() : ' . $line,
333
-            );
334
-            // don't serialize objects
335
-            $info = self::strip_objects($info);
336
-            $index = ! empty($debug_index) ? $debug_index : 0;
337
-            if (! isset($debug_data[ $index ])) {
338
-                $debug_data[ $index ] = array();
339
-            }
340
-            $debug_data[ $index ][ microtime() ] = array_merge($default_data, $info);
341
-            update_option($debug_key, $debug_data);
342
-        }
343
-    }
344
-
345
-
346
-
347
-    /**
348
-     * strip_objects
349
-     *
350
-     * @param array $info
351
-     * @return array
352
-     */
353
-    public static function strip_objects($info = array())
354
-    {
355
-        foreach ($info as $key => $value) {
356
-            if (is_array($value)) {
357
-                $info[ $key ] = self::strip_objects($value);
358
-            } elseif (is_object($value)) {
359
-                $object_class = get_class($value);
360
-                $info[ $object_class ] = array();
361
-                $info[ $object_class ]['ID'] = method_exists($value, 'ID') ? $value->ID() : spl_object_hash($value);
362
-                if (method_exists($value, 'ID')) {
363
-                    $info[ $object_class ]['ID'] = $value->ID();
364
-                }
365
-                if (method_exists($value, 'status')) {
366
-                    $info[ $object_class ]['status'] = $value->status();
367
-                } elseif (method_exists($value, 'status_ID')) {
368
-                    $info[ $object_class ]['status'] = $value->status_ID();
369
-                }
370
-                unset($info[ $key ]);
371
-            }
372
-        }
373
-        return (array) $info;
374
-    }
375
-
376
-
377
-
378
-    /**
379
-     * @param mixed      $var
380
-     * @param string     $var_name
381
-     * @param string     $file
382
-     * @param int|string $line
383
-     * @param int|string $heading_tag
384
-     * @param bool       $die
385
-     * @param string     $margin
386
-     */
387
-    public static function printv(
388
-        $var,
389
-        $var_name = '',
390
-        $file = '',
391
-        $line = '',
392
-        $heading_tag = 5,
393
-        $die = false,
394
-        $margin = ''
395
-    ) {
396
-        $var_name = ! $var_name ? 'string' : $var_name;
397
-        $var_name = ucwords(str_replace('$', '', $var_name));
398
-        $is_method = method_exists($var_name, $var);
399
-        $var_name = ucwords(str_replace('_', ' ', $var_name));
400
-        $heading_tag = EEH_Debug_Tools::headingTag($heading_tag);
401
-        // $result = EEH_Debug_Tools::headingSpacer($heading_tag);
402
-        $result = EEH_Debug_Tools::heading($var_name, $heading_tag, $margin, $line);
403
-        $result .= $is_method
404
-            ? EEH_Debug_Tools::grey_span('::') . EEH_Debug_Tools::orange_span($var . '()')
405
-            : EEH_Debug_Tools::grey_span(' : ') . EEH_Debug_Tools::orange_span($var);
406
-        $result .= EEH_Debug_Tools::file_and_line($file, $line, $heading_tag);
407
-        $result .= EEH_Debug_Tools::headingX($heading_tag);
408
-        if ($die) {
409
-            die($result);
410
-        }
411
-        echo wp_kses($result, AllowedTags::getWithFormTags());
412
-    }
413
-
414
-
415
-    protected static function headingTag($heading_tag)
416
-    {
417
-        $heading_tag = absint($heading_tag);
418
-        return $heading_tag > 0 && $heading_tag < 7 ? "h{$heading_tag}" : 'h5';
419
-    }
420
-
421
-    protected static function headingSpacer($heading_tag)
422
-    {
423
-        return EEH_Debug_Tools::plainOutput() && ($heading_tag === 'h1' || $heading_tag === 'h2')
424
-            ? self::lineBreak()
425
-            : '';
426
-    }
427
-
428
-
429
-    protected static function plainOutput()
430
-    {
431
-        return defined('EE_TESTS_DIR')
432
-               || (defined('DOING_AJAX') && DOING_AJAX && ! isset($_REQUEST['pretty_output']))
433
-               || (
434
-                   isset($_SERVER['REQUEST_URI'])
435
-                   && strpos(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH), 'wp-json') !== false
436
-               );
437
-    }
438
-
439
-
440
-    /**
441
-     * @param string $var_name
442
-     * @param string $heading_tag
443
-     * @param string $margin
444
-     * @param int    $line
445
-     * @return string
446
-     */
447
-    protected static function heading($var_name = '', $heading_tag = 'h5', $margin = '', $line = 0)
448
-    {
449
-        if (EEH_Debug_Tools::plainOutput()) {
450
-            switch ($heading_tag) {
451
-                case 'h1':
452
-                    $line_breaks = EEH_Debug_Tools::lineBreak(3);
453
-                    break;
454
-                case 'h2':
455
-                    $line_breaks = EEH_Debug_Tools::lineBreak(2);
456
-                    break;
457
-                default:
458
-                    $line_breaks = EEH_Debug_Tools::lineBreak();
459
-                    break;
460
-            }
461
-            return "{$line_breaks}{$line}) {$var_name}";
462
-        }
463
-        $margin = "1rem 1rem .5rem {$margin}";
464
-        return '
18
+	/**
19
+	 *    instance of the EEH_Autoloader object
20
+	 *
21
+	 * @var    $_instance
22
+	 * @access    private
23
+	 */
24
+	private static $_instance;
25
+
26
+	/**
27
+	 * @var array
28
+	 */
29
+	protected $_memory_usage_points = array();
30
+
31
+
32
+
33
+	/**
34
+	 * @singleton method used to instantiate class object
35
+	 * @access    public
36
+	 * @return EEH_Debug_Tools
37
+	 */
38
+	public static function instance()
39
+	{
40
+		// check if class object is instantiated, and instantiated properly
41
+		if (! self::$_instance instanceof EEH_Debug_Tools) {
42
+			self::$_instance = new self();
43
+		}
44
+		return self::$_instance;
45
+	}
46
+
47
+
48
+
49
+	/**
50
+	 * private class constructor
51
+	 */
52
+	private function __construct()
53
+	{
54
+		// load Kint PHP debugging library
55
+		if (
56
+			defined('EE_LOAD_KINT')
57
+			&& ! class_exists('Kint')
58
+			&& file_exists(EE_PLUGIN_DIR_PATH . 'tests/kint/Kint.class.php')
59
+		) {
60
+			// despite EE4 having a check for an existing copy of the Kint debugging class,
61
+			// if another plugin was loaded AFTER EE4 and they did NOT perform a similar check,
62
+			// then hilarity would ensue as PHP throws a "Cannot redeclare class Kint" error
63
+			// so we've moved it to our test folder so that it is not included with production releases
64
+			// plz use https://wordpress.org/plugins/kint-debugger/  if testing production versions of EE
65
+			require_once(EE_PLUGIN_DIR_PATH . 'tests/kint/Kint.class.php');
66
+		}
67
+		$plugin = basename(EE_PLUGIN_DIR_PATH);
68
+		add_action("activate_{$plugin}", array('EEH_Debug_Tools', 'ee_plugin_activation_errors'));
69
+		add_action('activated_plugin', array('EEH_Debug_Tools', 'ee_plugin_activation_errors'));
70
+		add_action('shutdown', array('EEH_Debug_Tools', 'show_db_name'));
71
+	}
72
+
73
+
74
+
75
+	/**
76
+	 *    show_db_name
77
+	 *
78
+	 * @return void
79
+	 */
80
+	public static function show_db_name()
81
+	{
82
+		if (! defined('DOING_AJAX') && (defined('EE_ERROR_EMAILS') && EE_ERROR_EMAILS)) {
83
+			echo '<p style="font-size:10px;font-weight:normal;color:#E76700;margin: 1em 2em; text-align: right;">DB_NAME: '
84
+				 . DB_NAME
85
+				 . '</p>';
86
+		}
87
+		if (EE_DEBUG) {
88
+			Benchmark::displayResults();
89
+		}
90
+	}
91
+
92
+
93
+
94
+	/**
95
+	 *    dump EE_Session object at bottom of page after everything else has happened
96
+	 *
97
+	 * @return void
98
+	 */
99
+	public function espresso_session_footer_dump()
100
+	{
101
+		if (
102
+			(defined('WP_DEBUG') && WP_DEBUG)
103
+			&& ! defined('DOING_AJAX')
104
+			&& class_exists('Kint')
105
+			&& function_exists('wp_get_current_user')
106
+			&& current_user_can('update_core')
107
+			&& class_exists('EE_Registry')
108
+		) {
109
+			Kint::dump(EE_Registry::instance()->SSN->id());
110
+			Kint::dump(EE_Registry::instance()->SSN);
111
+			//          Kint::dump( EE_Registry::instance()->SSN->get_session_data('cart')->get_tickets() );
112
+			$this->espresso_list_hooked_functions();
113
+			Benchmark::displayResults();
114
+		}
115
+	}
116
+
117
+
118
+
119
+	/**
120
+	 *    List All Hooked Functions
121
+	 *    to list all functions for a specific hook, add ee_list_hooks={hook-name} to URL
122
+	 *    http://wp.smashingmagazine.com/2009/08/18/10-useful-wordpress-hook-hacks/
123
+	 *
124
+	 * @param string $tag
125
+	 * @return void
126
+	 */
127
+	public function espresso_list_hooked_functions($tag = '')
128
+	{
129
+		global $wp_filter;
130
+		echo '<br/><br/><br/><h3>Hooked Functions</h3>';
131
+		if ($tag) {
132
+			$hook[ $tag ] = $wp_filter[ $tag ];
133
+			if (! is_array($hook[ $tag ])) {
134
+				trigger_error("Nothing found for '$tag' hook", E_USER_WARNING);
135
+				return;
136
+			}
137
+			echo '<h5>For Tag: ' . esc_html($tag) . '</h5>';
138
+		} else {
139
+			$hook = is_array($wp_filter) ? $wp_filter : array($wp_filter);
140
+			ksort($hook);
141
+		}
142
+		foreach ($hook as $tag_name => $priorities) {
143
+			echo "<br />&gt;&gt;&gt;&gt;&gt;\t<strong>esc_html($tag_name)</strong><br />";
144
+			ksort($priorities);
145
+			foreach ($priorities as $priority => $function) {
146
+				echo esc_html($priority);
147
+				foreach ($function as $name => $properties) {
148
+					$name = esc_html($name);
149
+					echo "\t$name<br />";
150
+				}
151
+			}
152
+		}
153
+	}
154
+
155
+
156
+
157
+	/**
158
+	 *    registered_filter_callbacks
159
+	 *
160
+	 * @param string $hook_name
161
+	 * @return array
162
+	 */
163
+	public static function registered_filter_callbacks($hook_name = '')
164
+	{
165
+		$filters = array();
166
+		global $wp_filter;
167
+		if (isset($wp_filter[ $hook_name ])) {
168
+			$filters[ $hook_name ] = array();
169
+			foreach ($wp_filter[ $hook_name ] as $priority => $callbacks) {
170
+				$filters[ $hook_name ][ $priority ] = array();
171
+				foreach ($callbacks as $callback) {
172
+					$filters[ $hook_name ][ $priority ][] = $callback['function'];
173
+				}
174
+			}
175
+		}
176
+		return $filters;
177
+	}
178
+
179
+
180
+
181
+	/**
182
+	 *    captures plugin activation errors for debugging
183
+	 *
184
+	 * @return void
185
+	 * @throws EE_Error
186
+	 */
187
+	public static function ee_plugin_activation_errors()
188
+	{
189
+		if (WP_DEBUG) {
190
+			$activation_errors = ob_get_contents();
191
+			if (empty($activation_errors)) {
192
+				return;
193
+			}
194
+			$activation_errors = date('Y-m-d H:i:s') . "\n" . $activation_errors;
195
+			espresso_load_required('EEH_File', EE_HELPERS . 'EEH_File.helper.php');
196
+			if (class_exists('EEH_File')) {
197
+				try {
198
+					EEH_File::ensure_file_exists_and_is_writable(
199
+						EVENT_ESPRESSO_UPLOAD_DIR . 'logs/espresso_plugin_activation_errors.html'
200
+					);
201
+					EEH_File::write_to_file(
202
+						EVENT_ESPRESSO_UPLOAD_DIR . 'logs/espresso_plugin_activation_errors.html',
203
+						$activation_errors
204
+					);
205
+				} catch (EE_Error $e) {
206
+					EE_Error::add_error(
207
+						sprintf(
208
+							esc_html__(
209
+								'The Event Espresso activation errors file could not be setup because: %s',
210
+								'event_espresso'
211
+							),
212
+							$e->getMessage()
213
+						),
214
+						__FILE__,
215
+						__FUNCTION__,
216
+						__LINE__
217
+					);
218
+				}
219
+			} else {
220
+				// old school attempt
221
+				file_put_contents(
222
+					EVENT_ESPRESSO_UPLOAD_DIR . 'logs/espresso_plugin_activation_errors.html',
223
+					$activation_errors
224
+				);
225
+			}
226
+			$activation_errors = get_option('ee_plugin_activation_errors', '') . $activation_errors;
227
+			update_option('ee_plugin_activation_errors', $activation_errors);
228
+		}
229
+	}
230
+
231
+
232
+
233
+	/**
234
+	 * This basically mimics the WordPress _doing_it_wrong() function except adds our own messaging etc.
235
+	 * Very useful for providing helpful messages to developers when the method of doing something has been deprecated,
236
+	 * or we want to make sure they use something the right way.
237
+	 *
238
+	 * @access public
239
+	 * @param string $function      The function that was called
240
+	 * @param string $message       A message explaining what has been done incorrectly
241
+	 * @param string $version       The version of Event Espresso where the error was added
242
+	 * @param string $applies_when  a version string for when you want the doing_it_wrong notice to begin appearing
243
+	 *                              for a deprecated function. This allows deprecation to occur during one version,
244
+	 *                              but not have any notices appear until a later version. This allows developers
245
+	 *                              extra time to update their code before notices appear.
246
+	 * @param int    $error_type
247
+	 * @uses   trigger_error()
248
+	 */
249
+	public function doing_it_wrong(
250
+		$function,
251
+		$message,
252
+		$version,
253
+		$applies_when = '',
254
+		$error_type = null
255
+	) {
256
+		$applies_when = ! empty($applies_when) ? $applies_when : espresso_version();
257
+		$error_type = $error_type !== null ? $error_type : E_USER_NOTICE;
258
+		// because we swapped the parameter order around for the last two params,
259
+		// let's verify that some third party isn't still passing an error type value for the third param
260
+		if (is_int($applies_when)) {
261
+			$error_type = $applies_when;
262
+			$applies_when = espresso_version();
263
+		}
264
+		// if not displaying notices yet, then just leave
265
+		if (version_compare(espresso_version(), $applies_when, '<')) {
266
+			return;
267
+		}
268
+		do_action('AHEE__EEH_Debug_Tools__doing_it_wrong_run', $function, $message, $version);
269
+		$version = $version === null
270
+			? ''
271
+			: sprintf(
272
+				esc_html__('(This message was added in version %s of Event Espresso)', 'event_espresso'),
273
+				$version
274
+			);
275
+		$error_message = sprintf(
276
+			esc_html__('%1$s was called %2$sincorrectly%3$s. %4$s %5$s', 'event_espresso'),
277
+			$function,
278
+			'<strong>',
279
+			'</strong>',
280
+			$message,
281
+			$version
282
+		);
283
+		// don't trigger error if doing ajax,
284
+		// instead we'll add a transient EE_Error notice that in theory should show on the next request.
285
+		if (defined('DOING_AJAX') && DOING_AJAX) {
286
+			$error_message .= ' ' . esc_html__(
287
+				'This is a doing_it_wrong message that was triggered during an ajax request.  The request params on this request were: ',
288
+				'event_espresso'
289
+			);
290
+			$request = LoaderFactory::getLoader()->getShared(RequestInterface::class);
291
+			$error_message .= '<ul><li>';
292
+			$error_message .= implode('</li><li>', $request->requestParams());
293
+			$error_message .= '</ul>';
294
+			EE_Error::add_error($error_message, 'debug::doing_it_wrong', $function, '42');
295
+			// now we set this on the transient so it shows up on the next request.
296
+			EE_Error::get_notices(false, true);
297
+		} else {
298
+			trigger_error($error_message, $error_type);
299
+		}
300
+	}
301
+
302
+
303
+
304
+
305
+	/**
306
+	 * Logger helpers
307
+	 */
308
+	/**
309
+	 * debug
310
+	 *
311
+	 * @param string $class
312
+	 * @param string $func
313
+	 * @param string $line
314
+	 * @param array  $info
315
+	 * @param bool   $display_request
316
+	 * @param string $debug_index
317
+	 * @param string $debug_key
318
+	 */
319
+	public static function log(
320
+		$class = '',
321
+		$func = '',
322
+		$line = '',
323
+		$info = array(),
324
+		$display_request = false,
325
+		$debug_index = '',
326
+		$debug_key = 'EE_DEBUG_SPCO'
327
+	) {
328
+		if (WP_DEBUG) {
329
+			$debug_key = $debug_key . '_' . EE_Session::instance()->id();
330
+			$debug_data = get_option($debug_key, array());
331
+			$default_data = array(
332
+				$class => $func . '() : ' . $line,
333
+			);
334
+			// don't serialize objects
335
+			$info = self::strip_objects($info);
336
+			$index = ! empty($debug_index) ? $debug_index : 0;
337
+			if (! isset($debug_data[ $index ])) {
338
+				$debug_data[ $index ] = array();
339
+			}
340
+			$debug_data[ $index ][ microtime() ] = array_merge($default_data, $info);
341
+			update_option($debug_key, $debug_data);
342
+		}
343
+	}
344
+
345
+
346
+
347
+	/**
348
+	 * strip_objects
349
+	 *
350
+	 * @param array $info
351
+	 * @return array
352
+	 */
353
+	public static function strip_objects($info = array())
354
+	{
355
+		foreach ($info as $key => $value) {
356
+			if (is_array($value)) {
357
+				$info[ $key ] = self::strip_objects($value);
358
+			} elseif (is_object($value)) {
359
+				$object_class = get_class($value);
360
+				$info[ $object_class ] = array();
361
+				$info[ $object_class ]['ID'] = method_exists($value, 'ID') ? $value->ID() : spl_object_hash($value);
362
+				if (method_exists($value, 'ID')) {
363
+					$info[ $object_class ]['ID'] = $value->ID();
364
+				}
365
+				if (method_exists($value, 'status')) {
366
+					$info[ $object_class ]['status'] = $value->status();
367
+				} elseif (method_exists($value, 'status_ID')) {
368
+					$info[ $object_class ]['status'] = $value->status_ID();
369
+				}
370
+				unset($info[ $key ]);
371
+			}
372
+		}
373
+		return (array) $info;
374
+	}
375
+
376
+
377
+
378
+	/**
379
+	 * @param mixed      $var
380
+	 * @param string     $var_name
381
+	 * @param string     $file
382
+	 * @param int|string $line
383
+	 * @param int|string $heading_tag
384
+	 * @param bool       $die
385
+	 * @param string     $margin
386
+	 */
387
+	public static function printv(
388
+		$var,
389
+		$var_name = '',
390
+		$file = '',
391
+		$line = '',
392
+		$heading_tag = 5,
393
+		$die = false,
394
+		$margin = ''
395
+	) {
396
+		$var_name = ! $var_name ? 'string' : $var_name;
397
+		$var_name = ucwords(str_replace('$', '', $var_name));
398
+		$is_method = method_exists($var_name, $var);
399
+		$var_name = ucwords(str_replace('_', ' ', $var_name));
400
+		$heading_tag = EEH_Debug_Tools::headingTag($heading_tag);
401
+		// $result = EEH_Debug_Tools::headingSpacer($heading_tag);
402
+		$result = EEH_Debug_Tools::heading($var_name, $heading_tag, $margin, $line);
403
+		$result .= $is_method
404
+			? EEH_Debug_Tools::grey_span('::') . EEH_Debug_Tools::orange_span($var . '()')
405
+			: EEH_Debug_Tools::grey_span(' : ') . EEH_Debug_Tools::orange_span($var);
406
+		$result .= EEH_Debug_Tools::file_and_line($file, $line, $heading_tag);
407
+		$result .= EEH_Debug_Tools::headingX($heading_tag);
408
+		if ($die) {
409
+			die($result);
410
+		}
411
+		echo wp_kses($result, AllowedTags::getWithFormTags());
412
+	}
413
+
414
+
415
+	protected static function headingTag($heading_tag)
416
+	{
417
+		$heading_tag = absint($heading_tag);
418
+		return $heading_tag > 0 && $heading_tag < 7 ? "h{$heading_tag}" : 'h5';
419
+	}
420
+
421
+	protected static function headingSpacer($heading_tag)
422
+	{
423
+		return EEH_Debug_Tools::plainOutput() && ($heading_tag === 'h1' || $heading_tag === 'h2')
424
+			? self::lineBreak()
425
+			: '';
426
+	}
427
+
428
+
429
+	protected static function plainOutput()
430
+	{
431
+		return defined('EE_TESTS_DIR')
432
+			   || (defined('DOING_AJAX') && DOING_AJAX && ! isset($_REQUEST['pretty_output']))
433
+			   || (
434
+				   isset($_SERVER['REQUEST_URI'])
435
+				   && strpos(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH), 'wp-json') !== false
436
+			   );
437
+	}
438
+
439
+
440
+	/**
441
+	 * @param string $var_name
442
+	 * @param string $heading_tag
443
+	 * @param string $margin
444
+	 * @param int    $line
445
+	 * @return string
446
+	 */
447
+	protected static function heading($var_name = '', $heading_tag = 'h5', $margin = '', $line = 0)
448
+	{
449
+		if (EEH_Debug_Tools::plainOutput()) {
450
+			switch ($heading_tag) {
451
+				case 'h1':
452
+					$line_breaks = EEH_Debug_Tools::lineBreak(3);
453
+					break;
454
+				case 'h2':
455
+					$line_breaks = EEH_Debug_Tools::lineBreak(2);
456
+					break;
457
+				default:
458
+					$line_breaks = EEH_Debug_Tools::lineBreak();
459
+					break;
460
+			}
461
+			return "{$line_breaks}{$line}) {$var_name}";
462
+		}
463
+		$margin = "1rem 1rem .5rem {$margin}";
464
+		return '
465 465
         <' . $heading_tag . ' style="color:#2EA2CC; margin:' . $margin . '; position: sticky;">
466 466
             <b>' . $var_name . '</b>';
467
-    }
467
+	}
468 468
 
469 469
 
470 470
 
471
-    /**
472
-     * @param string $heading_tag
473
-     * @return string
474
-     */
475
-    protected static function headingX($heading_tag = 'h5')
476
-    {
477
-        if (EEH_Debug_Tools::plainOutput()) {
478
-            return '';
479
-        }
480
-        return '
471
+	/**
472
+	 * @param string $heading_tag
473
+	 * @return string
474
+	 */
475
+	protected static function headingX($heading_tag = 'h5')
476
+	{
477
+		if (EEH_Debug_Tools::plainOutput()) {
478
+			return '';
479
+		}
480
+		return '
481 481
         </' . $heading_tag . '>';
482
-    }
483
-
484
-
485
-
486
-    /**
487
-     * @param string $content
488
-     * @return string
489
-     */
490
-    protected static function grey_span($content = '')
491
-    {
492
-        if (EEH_Debug_Tools::plainOutput()) {
493
-            return $content;
494
-        }
495
-        return '<span style="color:#999">' . $content . '</span>';
496
-    }
497
-
498
-
499
-
500
-    /**
501
-     * @param string $file
502
-     * @param int    $line
503
-     * @return string
504
-     */
505
-    protected static function file_and_line($file, $line, $heading_tag)
506
-    {
507
-        if ($file === '' || $line === '') {
508
-            return '';
509
-        }
510
-        $file = str_replace(EE_PLUGIN_DIR_PATH, '/', $file);
511
-        if (EEH_Debug_Tools::plainOutput()) {
512
-            if ($heading_tag === 'h1' || $heading_tag === 'h2') {
513
-                return " ({$file})" . EEH_Debug_Tools::lineBreak();
514
-            }
515
-            return '';
516
-        }
517
-        return EEH_Debug_Tools::lineBreak()
518
-               . '<span style="font-size:9px;font-weight:normal;color:#666;line-height: 12px;">'
519
-               . $file
520
-               . EEH_Debug_Tools::lineBreak()
521
-               . 'line no: '
522
-               . $line
523
-               . '</span>';
524
-    }
525
-
526
-
527
-
528
-    /**
529
-     * @param string $content
530
-     * @return string
531
-     */
532
-    protected static function orange_span($content = '')
533
-    {
534
-        if (EEH_Debug_Tools::plainOutput()) {
535
-            return $content;
536
-        }
537
-        return '<span style="color:#E76700">' . $content . '</span>';
538
-    }
539
-
540
-
541
-
542
-    /**
543
-     * @param mixed $var
544
-     * @return string
545
-     */
546
-    protected static function pre_span($var)
547
-    {
548
-        ob_start();
549
-        var_dump($var);
550
-        $var = ob_get_clean();
551
-        if (EEH_Debug_Tools::plainOutput()) {
552
-            return str_replace("\n", '', $var);
553
-        }
554
-        $style =[
555
-            'background: #334',
556
-            'color: #9C3',
557
-            'display: inline-block',
558
-            'padding: .4em .6em',
559
-        ];
560
-        return '<pre style="' . implode('; ', $style) . '">' . $var . '</pre>';
561
-    }
562
-
563
-
564
-
565
-    /**
566
-     * @param mixed      $var
567
-     * @param string     $var_name
568
-     * @param string     $file
569
-     * @param int|string $line
570
-     * @param int|string $heading_tag
571
-     * @param bool       $die
572
-     */
573
-    public static function printr(
574
-        $var,
575
-        $var_name = '',
576
-        $file = '',
577
-        $line = '',
578
-        $heading_tag = 5,
579
-        $die = false
580
-    ) {
581
-        // return;
582
-        $file = str_replace(rtrim(ABSPATH, '\\/'), '', $file);
583
-        if (empty($var) && empty($var_name)) {
584
-            $var = $file;
585
-            $var_name = "line $line";
586
-            $file = '';
587
-            $line = '';
588
-        }
589
-        $margin = is_admin() ? '180px' : '2rem';
590
-        if (is_string($var)) {
591
-            EEH_Debug_Tools::printv($var, $var_name, $file, $line, $heading_tag, $die, $margin);
592
-            return;
593
-        }
594
-        if (is_object($var)) {
595
-            $var_name = ! $var_name ? 'object' : $var_name;
596
-        } elseif (is_array($var)) {
597
-            $var_name = ! $var_name ? 'array' : $var_name;
598
-        } elseif (is_numeric($var)) {
599
-            $var_name = ! $var_name ? 'numeric' : $var_name;
600
-        } elseif ($var === null) {
601
-            $var_name = ! $var_name ? 'null' : $var_name;
602
-        }
603
-        $var_name = EEH_Debug_Tools::trimVarName($var_name);
604
-        $heading_tag = EEH_Debug_Tools::headingTag($heading_tag);
605
-        // $result = EEH_Debug_Tools::headingSpacer($heading_tag);
606
-        $result = EEH_Debug_Tools::heading($var_name, $heading_tag, $margin, $line);
607
-        $result .= EEH_Debug_Tools::grey_span(' : ') . EEH_Debug_Tools::orange_span(
608
-            EEH_Debug_Tools::pre_span($var)
609
-        );
610
-        $result .= EEH_Debug_Tools::file_and_line($file, $line, $heading_tag);
611
-        $result .= EEH_Debug_Tools::headingX($heading_tag);
612
-        if ($die) {
613
-            die($result);
614
-        }
615
-        echo wp_kses($result, AllowedTags::getWithFormTags());
616
-    }
617
-
618
-
619
-    private static function trimVarName($var_name): string
620
-    {
621
-        $converted = str_replace(['$', '_', 'this->'], ['', ' ', ''], $var_name);
622
-        $words = explode(' ', $converted);
623
-        $words = array_map(
624
-            function ($word) {
625
-                return $word === 'id' || $word === 'Id' ? 'ID' : $word;
626
-            },
627
-            $words
628
-        );
629
-        return ucwords(implode(' ', $words));
630
-    }
631
-
632
-
633
-    private static function lineBreak($lines = 1): string
634
-    {
635
-        $linebreak = defined('DOING_AJAX') && DOING_AJAX ? '<br />' : PHP_EOL;
636
-        return str_repeat($linebreak, $lines);
637
-    }
638
-
639
-
640
-    public static function shortClassName(string $fqcn): string
641
-    {
642
-        return substr(strrchr($fqcn, '\\'), 1);
643
-    }
644
-
645
-
646
-
647
-    /******************** deprecated ********************/
648
-
649
-
650
-
651
-    /**
652
-     * @deprecated 4.9.39.rc.034
653
-     */
654
-    public function reset_times()
655
-    {
656
-        Benchmark::resetTimes();
657
-    }
658
-
659
-
660
-
661
-    /**
662
-     * @deprecated 4.9.39.rc.034
663
-     * @param null $timer_name
664
-     */
665
-    public function start_timer($timer_name = null)
666
-    {
667
-        Benchmark::startTimer($timer_name);
668
-    }
669
-
670
-
671
-
672
-    /**
673
-     * @deprecated 4.9.39.rc.034
674
-     * @param string $timer_name
675
-     */
676
-    public function stop_timer($timer_name = '')
677
-    {
678
-        Benchmark::stopTimer($timer_name);
679
-    }
680
-
681
-
682
-
683
-    /**
684
-     * @deprecated 4.9.39.rc.034
685
-     * @param string  $label      The label to show for this time eg "Start of calling Some_Class::some_function"
686
-     * @param boolean $output_now whether to echo now, or wait until EEH_Debug_Tools::show_times() is called
687
-     * @return void
688
-     */
689
-    public function measure_memory($label, $output_now = false)
690
-    {
691
-        Benchmark::measureMemory($label, $output_now);
692
-    }
693
-
694
-
695
-
696
-    /**
697
-     * @deprecated 4.9.39.rc.034
698
-     * @param int $size
699
-     * @return string
700
-     */
701
-    public function convert($size)
702
-    {
703
-        return Benchmark::convert($size);
704
-    }
705
-
706
-
482
+	}
483
+
484
+
485
+
486
+	/**
487
+	 * @param string $content
488
+	 * @return string
489
+	 */
490
+	protected static function grey_span($content = '')
491
+	{
492
+		if (EEH_Debug_Tools::plainOutput()) {
493
+			return $content;
494
+		}
495
+		return '<span style="color:#999">' . $content . '</span>';
496
+	}
497
+
498
+
499
+
500
+	/**
501
+	 * @param string $file
502
+	 * @param int    $line
503
+	 * @return string
504
+	 */
505
+	protected static function file_and_line($file, $line, $heading_tag)
506
+	{
507
+		if ($file === '' || $line === '') {
508
+			return '';
509
+		}
510
+		$file = str_replace(EE_PLUGIN_DIR_PATH, '/', $file);
511
+		if (EEH_Debug_Tools::plainOutput()) {
512
+			if ($heading_tag === 'h1' || $heading_tag === 'h2') {
513
+				return " ({$file})" . EEH_Debug_Tools::lineBreak();
514
+			}
515
+			return '';
516
+		}
517
+		return EEH_Debug_Tools::lineBreak()
518
+			   . '<span style="font-size:9px;font-weight:normal;color:#666;line-height: 12px;">'
519
+			   . $file
520
+			   . EEH_Debug_Tools::lineBreak()
521
+			   . 'line no: '
522
+			   . $line
523
+			   . '</span>';
524
+	}
525
+
526
+
527
+
528
+	/**
529
+	 * @param string $content
530
+	 * @return string
531
+	 */
532
+	protected static function orange_span($content = '')
533
+	{
534
+		if (EEH_Debug_Tools::plainOutput()) {
535
+			return $content;
536
+		}
537
+		return '<span style="color:#E76700">' . $content . '</span>';
538
+	}
539
+
540
+
541
+
542
+	/**
543
+	 * @param mixed $var
544
+	 * @return string
545
+	 */
546
+	protected static function pre_span($var)
547
+	{
548
+		ob_start();
549
+		var_dump($var);
550
+		$var = ob_get_clean();
551
+		if (EEH_Debug_Tools::plainOutput()) {
552
+			return str_replace("\n", '', $var);
553
+		}
554
+		$style =[
555
+			'background: #334',
556
+			'color: #9C3',
557
+			'display: inline-block',
558
+			'padding: .4em .6em',
559
+		];
560
+		return '<pre style="' . implode('; ', $style) . '">' . $var . '</pre>';
561
+	}
562
+
563
+
564
+
565
+	/**
566
+	 * @param mixed      $var
567
+	 * @param string     $var_name
568
+	 * @param string     $file
569
+	 * @param int|string $line
570
+	 * @param int|string $heading_tag
571
+	 * @param bool       $die
572
+	 */
573
+	public static function printr(
574
+		$var,
575
+		$var_name = '',
576
+		$file = '',
577
+		$line = '',
578
+		$heading_tag = 5,
579
+		$die = false
580
+	) {
581
+		// return;
582
+		$file = str_replace(rtrim(ABSPATH, '\\/'), '', $file);
583
+		if (empty($var) && empty($var_name)) {
584
+			$var = $file;
585
+			$var_name = "line $line";
586
+			$file = '';
587
+			$line = '';
588
+		}
589
+		$margin = is_admin() ? '180px' : '2rem';
590
+		if (is_string($var)) {
591
+			EEH_Debug_Tools::printv($var, $var_name, $file, $line, $heading_tag, $die, $margin);
592
+			return;
593
+		}
594
+		if (is_object($var)) {
595
+			$var_name = ! $var_name ? 'object' : $var_name;
596
+		} elseif (is_array($var)) {
597
+			$var_name = ! $var_name ? 'array' : $var_name;
598
+		} elseif (is_numeric($var)) {
599
+			$var_name = ! $var_name ? 'numeric' : $var_name;
600
+		} elseif ($var === null) {
601
+			$var_name = ! $var_name ? 'null' : $var_name;
602
+		}
603
+		$var_name = EEH_Debug_Tools::trimVarName($var_name);
604
+		$heading_tag = EEH_Debug_Tools::headingTag($heading_tag);
605
+		// $result = EEH_Debug_Tools::headingSpacer($heading_tag);
606
+		$result = EEH_Debug_Tools::heading($var_name, $heading_tag, $margin, $line);
607
+		$result .= EEH_Debug_Tools::grey_span(' : ') . EEH_Debug_Tools::orange_span(
608
+			EEH_Debug_Tools::pre_span($var)
609
+		);
610
+		$result .= EEH_Debug_Tools::file_and_line($file, $line, $heading_tag);
611
+		$result .= EEH_Debug_Tools::headingX($heading_tag);
612
+		if ($die) {
613
+			die($result);
614
+		}
615
+		echo wp_kses($result, AllowedTags::getWithFormTags());
616
+	}
617
+
618
+
619
+	private static function trimVarName($var_name): string
620
+	{
621
+		$converted = str_replace(['$', '_', 'this->'], ['', ' ', ''], $var_name);
622
+		$words = explode(' ', $converted);
623
+		$words = array_map(
624
+			function ($word) {
625
+				return $word === 'id' || $word === 'Id' ? 'ID' : $word;
626
+			},
627
+			$words
628
+		);
629
+		return ucwords(implode(' ', $words));
630
+	}
631
+
632
+
633
+	private static function lineBreak($lines = 1): string
634
+	{
635
+		$linebreak = defined('DOING_AJAX') && DOING_AJAX ? '<br />' : PHP_EOL;
636
+		return str_repeat($linebreak, $lines);
637
+	}
638
+
639
+
640
+	public static function shortClassName(string $fqcn): string
641
+	{
642
+		return substr(strrchr($fqcn, '\\'), 1);
643
+	}
644
+
645
+
646
+
647
+	/******************** deprecated ********************/
648
+
649
+
650
+
651
+	/**
652
+	 * @deprecated 4.9.39.rc.034
653
+	 */
654
+	public function reset_times()
655
+	{
656
+		Benchmark::resetTimes();
657
+	}
658
+
659
+
660
+
661
+	/**
662
+	 * @deprecated 4.9.39.rc.034
663
+	 * @param null $timer_name
664
+	 */
665
+	public function start_timer($timer_name = null)
666
+	{
667
+		Benchmark::startTimer($timer_name);
668
+	}
669
+
670
+
671
+
672
+	/**
673
+	 * @deprecated 4.9.39.rc.034
674
+	 * @param string $timer_name
675
+	 */
676
+	public function stop_timer($timer_name = '')
677
+	{
678
+		Benchmark::stopTimer($timer_name);
679
+	}
680
+
681
+
682
+
683
+	/**
684
+	 * @deprecated 4.9.39.rc.034
685
+	 * @param string  $label      The label to show for this time eg "Start of calling Some_Class::some_function"
686
+	 * @param boolean $output_now whether to echo now, or wait until EEH_Debug_Tools::show_times() is called
687
+	 * @return void
688
+	 */
689
+	public function measure_memory($label, $output_now = false)
690
+	{
691
+		Benchmark::measureMemory($label, $output_now);
692
+	}
693
+
694
+
695
+
696
+	/**
697
+	 * @deprecated 4.9.39.rc.034
698
+	 * @param int $size
699
+	 * @return string
700
+	 */
701
+	public function convert($size)
702
+	{
703
+		return Benchmark::convert($size);
704
+	}
705
+
706
+
707 707
 
708
-    /**
709
-     * @deprecated 4.9.39.rc.034
710
-     * @param bool $output_now
711
-     * @return string
712
-     */
713
-    public function show_times($output_now = true)
714
-    {
715
-        return Benchmark::displayResults($output_now);
716
-    }
708
+	/**
709
+	 * @deprecated 4.9.39.rc.034
710
+	 * @param bool $output_now
711
+	 * @return string
712
+	 */
713
+	public function show_times($output_now = true)
714
+	{
715
+		return Benchmark::displayResults($output_now);
716
+	}
717 717
 
718 718
 
719 719
 
720
-    /**
721
-     * @deprecated 4.9.39.rc.034
722
-     * @param string $timer_name
723
-     * @param float  $total_time
724
-     * @return string
725
-     */
726
-    public function format_time($timer_name, $total_time)
727
-    {
728
-        return Benchmark::formatTime($timer_name, $total_time);
729
-    }
720
+	/**
721
+	 * @deprecated 4.9.39.rc.034
722
+	 * @param string $timer_name
723
+	 * @param float  $total_time
724
+	 * @return string
725
+	 */
726
+	public function format_time($timer_name, $total_time)
727
+	{
728
+		return Benchmark::formatTime($timer_name, $total_time);
729
+	}
730 730
 }
731 731
 
732 732
 
@@ -736,31 +736,31 @@  discard block
 block discarded – undo
736 736
  * Plugin URI: http://upthemes.com/plugins/kint-debugger/
737 737
  */
738 738
 if (class_exists('Kint') && ! function_exists('dump_wp_query')) {
739
-    function dump_wp_query()
740
-    {
741
-        global $wp_query;
742
-        d($wp_query);
743
-    }
739
+	function dump_wp_query()
740
+	{
741
+		global $wp_query;
742
+		d($wp_query);
743
+	}
744 744
 }
745 745
 /**
746 746
  * borrowed from Kint Debugger
747 747
  * Plugin URI: http://upthemes.com/plugins/kint-debugger/
748 748
  */
749 749
 if (class_exists('Kint') && ! function_exists('dump_wp')) {
750
-    function dump_wp()
751
-    {
752
-        global $wp;
753
-        d($wp);
754
-    }
750
+	function dump_wp()
751
+	{
752
+		global $wp;
753
+		d($wp);
754
+	}
755 755
 }
756 756
 /**
757 757
  * borrowed from Kint Debugger
758 758
  * Plugin URI: http://upthemes.com/plugins/kint-debugger/
759 759
  */
760 760
 if (class_exists('Kint') && ! function_exists('dump_post')) {
761
-    function dump_post()
762
-    {
763
-        global $post;
764
-        d($post);
765
-    }
761
+	function dump_post()
762
+	{
763
+		global $post;
764
+		d($post);
765
+	}
766 766
 }
Please login to merge, or discard this patch.
core/EE_Dependency_Map.core.php 1 patch
Indentation   +1153 added lines, -1153 removed lines patch added patch discarded remove patch
@@ -20,1157 +20,1157 @@
 block discarded – undo
20 20
  */
21 21
 class EE_Dependency_Map
22 22
 {
23
-    /**
24
-     * This means that the requested class dependency is not present in the dependency map
25
-     */
26
-    const not_registered = 0;
27
-
28
-    /**
29
-     * This instructs class loaders to ALWAYS return a newly instantiated object for the requested class.
30
-     */
31
-    const load_new_object = 1;
32
-
33
-    /**
34
-     * This instructs class loaders to return a previously instantiated and cached object for the requested class.
35
-     * IF a previously instantiated object does not exist, a new one will be created and added to the cache.
36
-     */
37
-    const load_from_cache = 2;
38
-
39
-    /**
40
-     * When registering a dependency,
41
-     * this indicates to keep any existing dependencies that already exist,
42
-     * and simply discard any new dependencies declared in the incoming data
43
-     */
44
-    const KEEP_EXISTING_DEPENDENCIES = 0;
45
-
46
-    /**
47
-     * When registering a dependency,
48
-     * this indicates to overwrite any existing dependencies that already exist using the incoming data
49
-     */
50
-    const OVERWRITE_DEPENDENCIES = 1;
51
-
52
-    protected static ?EE_Dependency_Map $_instance = null;
53
-
54
-    private ClassInterfaceCache $class_cache;
55
-
56
-    protected ?RequestInterface $request = null;
57
-
58
-    protected ?LegacyRequestInterface $legacy_request = null;
59
-
60
-    protected ?ResponseInterface $response = null;
61
-
62
-    protected ?LoaderInterface $loader = null;
63
-
64
-    protected array $_dependency_map = [];
65
-
66
-    protected array $_class_loaders = [];
67
-
68
-
69
-    /**
70
-     * EE_Dependency_Map constructor.
71
-     *
72
-     * @param ClassInterfaceCache $class_cache
73
-     */
74
-    protected function __construct(ClassInterfaceCache $class_cache)
75
-    {
76
-        $this->class_cache = $class_cache;
77
-        do_action('EE_Dependency_Map____construct', $this);
78
-    }
79
-
80
-
81
-    /**
82
-     * @return void
83
-     * @throws InvalidAliasException
84
-     */
85
-    public function initialize()
86
-    {
87
-        $this->_register_core_dependencies();
88
-        $this->_register_core_class_loaders();
89
-        $this->_register_core_aliases();
90
-    }
91
-
92
-
93
-    /**
94
-     * @singleton method used to instantiate class object
95
-     * @param ClassInterfaceCache|null $class_cache
96
-     * @return EE_Dependency_Map
97
-     */
98
-    public static function instance(ClassInterfaceCache $class_cache = null): EE_Dependency_Map
99
-    {
100
-        // check if class object is instantiated, and instantiated properly
101
-        if (
102
-            ! EE_Dependency_Map::$_instance instanceof EE_Dependency_Map
103
-            && $class_cache instanceof ClassInterfaceCache
104
-        ) {
105
-            EE_Dependency_Map::$_instance = new EE_Dependency_Map($class_cache);
106
-        }
107
-        return EE_Dependency_Map::$_instance;
108
-    }
109
-
110
-
111
-    /**
112
-     * @param RequestInterface $request
113
-     */
114
-    public function setRequest(RequestInterface $request)
115
-    {
116
-        $this->request = $request;
117
-    }
118
-
119
-
120
-    /**
121
-     * @param LegacyRequestInterface $legacy_request
122
-     */
123
-    public function setLegacyRequest(LegacyRequestInterface $legacy_request)
124
-    {
125
-        $this->legacy_request = $legacy_request;
126
-    }
127
-
128
-
129
-    /**
130
-     * @param ResponseInterface $response
131
-     */
132
-    public function setResponse(ResponseInterface $response)
133
-    {
134
-        $this->response = $response;
135
-    }
136
-
137
-
138
-    /**
139
-     * @param LoaderInterface $loader
140
-     */
141
-    public function setLoader(LoaderInterface $loader)
142
-    {
143
-        $this->loader = $loader;
144
-    }
145
-
146
-
147
-    /**
148
-     * @param string $class
149
-     * @param array  $dependencies
150
-     * @param int    $overwrite
151
-     * @return bool
152
-     */
153
-    public static function register_dependencies(
154
-        string $class,
155
-        array $dependencies,
156
-        int $overwrite = EE_Dependency_Map::KEEP_EXISTING_DEPENDENCIES
157
-    ): bool {
158
-        return EE_Dependency_Map::$_instance->registerDependencies($class, $dependencies, $overwrite);
159
-    }
160
-
161
-
162
-    /**
163
-     * Assigns an array of class names and corresponding load sources (new or cached)
164
-     * to the class specified by the first parameter.
165
-     * IMPORTANT !!!
166
-     * The order of elements in the incoming $dependencies array MUST match
167
-     * the order of the constructor parameters for the class in question.
168
-     * This is especially important when overriding any existing dependencies that are registered.
169
-     * the third parameter controls whether any duplicate dependencies are overwritten or not.
170
-     *
171
-     * @param string $class
172
-     * @param array  $dependencies
173
-     * @param int    $overwrite
174
-     * @return bool
175
-     */
176
-    public function registerDependencies(
177
-        string $class,
178
-        array $dependencies,
179
-        int $overwrite = EE_Dependency_Map::KEEP_EXISTING_DEPENDENCIES
180
-    ): bool {
181
-        if (empty($dependencies)) {
182
-            return false;
183
-        }
184
-        $class      = trim($class, '\\');
185
-        $registered = false;
186
-        if (empty(EE_Dependency_Map::$_instance->_dependency_map[ $class ])) {
187
-            EE_Dependency_Map::$_instance->_dependency_map[ $class ] = [];
188
-        }
189
-        // we need to make sure that any aliases used when registering a dependency
190
-        // get resolved to the correct class name
191
-        foreach ($dependencies as $dependency => $load_source) {
192
-            $alias = EE_Dependency_Map::$_instance->getFqnForAlias($dependency);
193
-            if (
194
-                $overwrite === EE_Dependency_Map::OVERWRITE_DEPENDENCIES
195
-                || ! isset(
196
-                    EE_Dependency_Map::$_instance->_dependency_map[ $class ][ $dependency ],
197
-                    EE_Dependency_Map::$_instance->_dependency_map[ $class ][ $alias ]
198
-                )
199
-            ) {
200
-                unset($dependencies[ $dependency ]);
201
-                $dependencies[ $alias ] = $load_source;
202
-                $registered             = true;
203
-            }
204
-        }
205
-        // now add our two lists of dependencies together.
206
-        // using Union (+=) favours the arrays in precedence from left to right,
207
-        // so $dependencies is NOT overwritten because it is listed first
208
-        // ie: with A = B + C, entries in B take precedence over duplicate entries in C
209
-        // Union is way faster than array_merge() but should be used with caution...
210
-        // especially with numerically indexed arrays
211
-        $dependencies += EE_Dependency_Map::$_instance->_dependency_map[ $class ];
212
-        // now we need to ensure that the resulting dependencies
213
-        // array only has the entries that are required for the class
214
-        // so first count how many dependencies were originally registered for the class
215
-        $dependency_count = count(EE_Dependency_Map::$_instance->_dependency_map[ $class ]);
216
-        // if that count is non-zero (meaning dependencies were already registered)
217
-        EE_Dependency_Map::$_instance->_dependency_map[ $class ] = $dependency_count
218
-            // then truncate the  final array to match that count
219
-            ? array_slice($dependencies, 0, $dependency_count)
220
-            // otherwise just take the incoming array because nothing previously existed
221
-            : $dependencies;
222
-        return $registered
223
-            || count(EE_Dependency_Map::$_instance->_dependency_map[ $class ]) === count($dependencies);
224
-    }
225
-
226
-
227
-    /**
228
-     * @param string          $class_name
229
-     * @param callable|string $loader
230
-     * @param bool            $overwrite
231
-     * @return bool
232
-     * @throws DomainException
233
-     */
234
-    public static function register_class_loader(
235
-        string $class_name,
236
-        $loader = 'load_core',
237
-        bool $overwrite = false
238
-    ): bool {
239
-        return EE_Dependency_Map::$_instance->registerClassLoader($class_name, $loader, $overwrite);
240
-    }
241
-
242
-
243
-    /**
244
-     * @param string         $class_name
245
-     * @param Closure|string $loader
246
-     * @param bool           $overwrite
247
-     * @return bool
248
-     * @throws DomainException
249
-     */
250
-    public function registerClassLoader(string $class_name, $loader = 'load_core', bool $overwrite = false): bool
251
-    {
252
-        if (! $loader instanceof Closure && strpos($class_name, '\\') !== false) {
253
-            throw new DomainException(
254
-                esc_html__('Don\'t use class loaders for FQCNs.', 'event_espresso')
255
-            );
256
-        }
257
-        // check that loader is callable or method starts with "load_" and exists in EE_Registry
258
-        if (
259
-            ! is_callable($loader)
260
-            && (
261
-                strpos($loader, 'load_') !== 0
262
-                || ! method_exists('EE_Registry', $loader)
263
-            )
264
-        ) {
265
-            throw new DomainException(
266
-                sprintf(
267
-                    esc_html__(
268
-                        '"%1$s" is not a valid loader method on EE_Registry.',
269
-                        'event_espresso'
270
-                    ),
271
-                    $loader
272
-                )
273
-            );
274
-        }
275
-        $class_name = EE_Dependency_Map::$_instance->getFqnForAlias($class_name);
276
-        if ($overwrite || ! isset(EE_Dependency_Map::$_instance->_class_loaders[ $class_name ])) {
277
-            EE_Dependency_Map::$_instance->_class_loaders[ $class_name ] = $loader;
278
-            return true;
279
-        }
280
-        return false;
281
-    }
282
-
283
-
284
-    /**
285
-     * @return array
286
-     */
287
-    public function dependency_map(): array
288
-    {
289
-        return $this->_dependency_map;
290
-    }
291
-
292
-
293
-    /**
294
-     * returns TRUE if dependency map contains a listing for the provided class name
295
-     *
296
-     * @param string $class_name
297
-     * @return boolean
298
-     */
299
-    public function has(string $class_name = ''): bool
300
-    {
301
-        // all legacy models have the same dependencies
302
-        if (strpos($class_name, 'EEM_') === 0) {
303
-            $class_name = 'LEGACY_MODELS';
304
-        }
305
-        return isset($this->_dependency_map[ $class_name ]);
306
-    }
307
-
308
-
309
-    /**
310
-     * returns TRUE if dependency map contains a listing for the provided class name AND dependency
311
-     *
312
-     * @param string $class_name
313
-     * @param string $dependency
314
-     * @return bool
315
-     */
316
-    public function has_dependency_for_class(string $class_name = '', string $dependency = ''): bool
317
-    {
318
-        // all legacy models have the same dependencies
319
-        if (strpos($class_name, 'EEM_') === 0) {
320
-            $class_name = 'LEGACY_MODELS';
321
-        }
322
-        $dependency = $this->getFqnForAlias($dependency, $class_name);
323
-        return isset($this->_dependency_map[ $class_name ][ $dependency ]);
324
-    }
325
-
326
-
327
-    /**
328
-     * returns loading strategy for whether a previously cached dependency should be loaded or a new instance returned
329
-     *
330
-     * @param string $class_name
331
-     * @param string $dependency
332
-     * @return int
333
-     */
334
-    public function loading_strategy_for_class_dependency(string $class_name = '', string $dependency = ''): int
335
-    {
336
-        // all legacy models have the same dependencies
337
-        if (strpos($class_name, 'EEM_') === 0) {
338
-            $class_name = 'LEGACY_MODELS';
339
-        }
340
-        $dependency = $this->getFqnForAlias($dependency);
341
-        return $this->has_dependency_for_class($class_name, $dependency)
342
-            ? $this->_dependency_map[ $class_name ][ $dependency ]
343
-            : EE_Dependency_Map::not_registered;
344
-    }
345
-
346
-
347
-    /**
348
-     * @param string $class_name
349
-     * @return string | Closure
350
-     */
351
-    public function class_loader(string $class_name)
352
-    {
353
-        // all legacy models use load_model()
354
-        if (strpos($class_name, 'EEM_') === 0) {
355
-            return 'load_model';
356
-        }
357
-        // EE_CPT_*_Strategy classes like EE_CPT_Event_Strategy, EE_CPT_Venue_Strategy, etc
358
-        // perform strpos() first to avoid loading regex every time we load a class
359
-        if (
360
-            strpos($class_name, 'EE_CPT_') === 0
361
-            && preg_match('/^EE_CPT_([a-zA-Z]+)_Strategy$/', $class_name)
362
-        ) {
363
-            return 'load_core';
364
-        }
365
-        $class_name = $this->getFqnForAlias($class_name);
366
-        return $this->_class_loaders[ $class_name ] ?? '';
367
-    }
368
-
369
-
370
-    /**
371
-     * @return array
372
-     */
373
-    public function class_loaders(): array
374
-    {
375
-        return $this->_class_loaders;
376
-    }
377
-
378
-
379
-    /**
380
-     * adds an alias for a classname
381
-     *
382
-     * @param string $fqcn      the class name that should be used (concrete class to replace interface)
383
-     * @param string $alias     the class name that would be type hinted for (abstract parent or interface)
384
-     * @param string $for_class the class that has the dependency (is type hinting for the interface)
385
-     * @throws InvalidAliasException
386
-     */
387
-    public function add_alias(string $fqcn, string $alias, string $for_class = '')
388
-    {
389
-        $this->class_cache->addAlias($fqcn, $alias, $for_class);
390
-    }
391
-
392
-
393
-    /**
394
-     * Returns TRUE if the provided fully qualified name IS an alias
395
-     * WHY?
396
-     * Because if a class is type hinting for a concretion,
397
-     * then why would we need to find another class to supply it?
398
-     * ie: if a class asks for `Fully/Qualified/Namespace/SpecificClassName`,
399
-     * then give it an instance of `Fully/Qualified/Namespace/SpecificClassName`.
400
-     * Don't go looking for some substitute.
401
-     * Whereas if a class is type hinting for an interface...
402
-     * then we need to find an actual class to use.
403
-     * So the interface IS the alias for some other FQN,
404
-     * and we need to find out if `Fully/Qualified/Namespace/SomeInterface`
405
-     * represents some other class.
406
-     *
407
-     * @param string $fqn
408
-     * @param string $for_class
409
-     * @return bool
410
-     */
411
-    public function isAlias(string $fqn = '', string $for_class = ''): bool
412
-    {
413
-        return $this->class_cache->isAlias($fqn, $for_class);
414
-    }
415
-
416
-
417
-    /**
418
-     * Returns a FQN for provided alias if one exists, otherwise returns the original $alias
419
-     * functions recursively, so that multiple aliases can be used to drill down to a FQN
420
-     *  for example:
421
-     *      if the following two entries were added to the _aliases array:
422
-     *          array(
423
-     *              'interface_alias'           => 'some\namespace\interface'
424
-     *              'some\namespace\interface'  => 'some\namespace\classname'
425
-     *          )
426
-     *      then one could use EE_Registry::instance()->create( 'interface_alias' )
427
-     *      to load an instance of 'some\namespace\classname'
428
-     *
429
-     * @param string $alias
430
-     * @param string $for_class
431
-     * @return string
432
-     */
433
-    public function getFqnForAlias(string $alias = '', string $for_class = ''): string
434
-    {
435
-        return $this->class_cache->getFqnForAlias($alias, $for_class);
436
-    }
437
-
438
-
439
-    /**
440
-     * Registers the core dependencies and whether a previously instantiated object should be loaded from the cache,
441
-     * if one exists, or whether a new object should be generated every time the requested class is loaded.
442
-     * This is done by using the following class constants:
443
-     *        EE_Dependency_Map::load_from_cache - loads previously instantiated object
444
-     *        EE_Dependency_Map::load_new_object - generates a new object every time
445
-     */
446
-    protected function _register_core_dependencies()
447
-    {
448
-        $this->_dependency_map = [
449
-            'EE_Admin'                                                                                                           => [
450
-                'EventEspresso\core\services\loaders\Loader'  => EE_Dependency_Map::load_from_cache,
451
-                'EventEspresso\core\services\request\Request' => EE_Dependency_Map::load_from_cache,
452
-            ],
453
-            'EE_Maintenance_Mode'                                                                                                => [
454
-                'EventEspresso\core\services\loaders\Loader'  => EE_Dependency_Map::load_from_cache,
455
-                'EventEspresso\core\services\request\Request' => EE_Dependency_Map::load_from_cache,
456
-            ],
457
-            'EE_Request_Handler'                                                                                                 => [
458
-                'EventEspresso\core\services\request\Request'  => EE_Dependency_Map::load_from_cache,
459
-                'EventEspresso\core\services\request\Response' => EE_Dependency_Map::load_from_cache,
460
-            ],
461
-            'EE_System'                                                                                                          => [
462
-                'EventEspresso\core\services\loaders\Loader'  => EE_Dependency_Map::load_from_cache,
463
-                'EE_Maintenance_Mode'                         => EE_Dependency_Map::load_from_cache,
464
-                'EE_Registry'                                 => EE_Dependency_Map::load_from_cache,
465
-                'EventEspresso\core\services\request\Request' => EE_Dependency_Map::load_from_cache,
466
-                'EventEspresso\core\services\routing\Router'  => EE_Dependency_Map::load_from_cache,
467
-            ],
468
-            'EE_Session'                                                                                                         => [
469
-                'EventEspresso\core\services\cache\TransientCacheStorage'  => EE_Dependency_Map::load_from_cache,
470
-                'EventEspresso\core\domain\values\session\SessionLifespan' => EE_Dependency_Map::load_from_cache,
471
-                'EventEspresso\core\services\request\Request'              => EE_Dependency_Map::load_from_cache,
472
-                'EventEspresso\core\services\session\SessionStartHandler'  => EE_Dependency_Map::load_from_cache,
473
-                'EventEspresso\core\services\encryption\Base64Encoder'     => EE_Dependency_Map::load_from_cache,
474
-            ],
475
-            'EventEspresso\core\services\session\SessionStartHandler'                                                            => [
476
-                'EventEspresso\core\services\request\Request' => EE_Dependency_Map::load_from_cache,
477
-            ],
478
-            'EE_Cart'                                                                                                            => [
479
-                'EE_Session' => EE_Dependency_Map::load_from_cache,
480
-            ],
481
-            'EE_Messenger_Collection_Loader'                                                                                     => [
482
-                'EE_Messenger_Collection' => EE_Dependency_Map::load_new_object,
483
-            ],
484
-            'EE_Message_Type_Collection_Loader'                                                                                  => [
485
-                'EE_Message_Type_Collection' => EE_Dependency_Map::load_new_object,
486
-            ],
487
-            'EE_Message_Resource_Manager'                                                                                        => [
488
-                'EE_Messenger_Collection_Loader'    => EE_Dependency_Map::load_new_object,
489
-                'EE_Message_Type_Collection_Loader' => EE_Dependency_Map::load_new_object,
490
-                'EEM_Message_Template_Group'        => EE_Dependency_Map::load_from_cache,
491
-            ],
492
-            'EE_Message_Factory'                                                                                                 => [
493
-                'EE_Message_Resource_Manager' => EE_Dependency_Map::load_from_cache,
494
-            ],
495
-            'EE_messages'                                                                                                        => [
496
-                'EE_Message_Resource_Manager' => EE_Dependency_Map::load_from_cache,
497
-            ],
498
-            'EE_Messages_Generator'                                                                                              => [
499
-                'EE_Messages_Queue'                    => EE_Dependency_Map::load_new_object,
500
-                'EE_Messages_Data_Handler_Collection'  => EE_Dependency_Map::load_new_object,
501
-                'EE_Message_Template_Group_Collection' => EE_Dependency_Map::load_new_object,
502
-                'EEH_Parse_Shortcodes'                 => EE_Dependency_Map::load_from_cache,
503
-            ],
504
-            'EE_Messages_Processor'                                                                                              => [
505
-                'EE_Message_Resource_Manager' => EE_Dependency_Map::load_from_cache,
506
-            ],
507
-            'EE_Messages_Queue'                                                                                                  => [
508
-                'EE_Message_Repository' => EE_Dependency_Map::load_new_object,
509
-            ],
510
-            'EE_Messages_Template_Defaults'                                                                                      => [
511
-                'EEM_Message_Template_Group' => EE_Dependency_Map::load_from_cache,
512
-                'EEM_Message_Template'       => EE_Dependency_Map::load_from_cache,
513
-            ],
514
-            'EE_Message_To_Generate_From_Request'                                                                                => [
515
-                'EE_Message_Resource_Manager'                 => EE_Dependency_Map::load_from_cache,
516
-                'EventEspresso\core\services\request\Request' => EE_Dependency_Map::load_from_cache,
517
-            ],
518
-            'EventEspresso\core\services\commands\CommandBus'                                                                    => [
519
-                'EventEspresso\core\services\commands\CommandHandlerManager' => EE_Dependency_Map::load_from_cache,
520
-            ],
521
-            'EventEspresso\services\commands\CommandHandler'                                                                     => [
522
-                'EE_Registry'         => EE_Dependency_Map::load_from_cache,
523
-                'CommandBusInterface' => EE_Dependency_Map::load_from_cache,
524
-            ],
525
-            'EventEspresso\core\services\commands\CommandHandlerManager'                                                         => [
526
-                'EventEspresso\core\services\loaders\Loader' => EE_Dependency_Map::load_from_cache,
527
-            ],
528
-            'EventEspresso\core\services\commands\CompositeCommandHandler'                                                       => [
529
-                'EventEspresso\core\services\commands\CommandBus'     => EE_Dependency_Map::load_from_cache,
530
-                'EventEspresso\core\services\commands\CommandFactory' => EE_Dependency_Map::load_from_cache,
531
-            ],
532
-            'EventEspresso\core\services\commands\CommandFactory'                                                                => [
533
-                'EventEspresso\core\services\loaders\Loader' => EE_Dependency_Map::load_from_cache,
534
-            ],
535
-            'EventEspresso\core\services\commands\middleware\CapChecker'                                                         => [
536
-                'EventEspresso\core\domain\services\capabilities\CapabilitiesChecker' => EE_Dependency_Map::load_from_cache,
537
-            ],
538
-            'EventEspresso\core\domain\services\capabilities\CapabilitiesChecker'                                                => [
539
-                'EE_Capabilities' => EE_Dependency_Map::load_from_cache,
540
-            ],
541
-            'EventEspresso\core\domain\services\capabilities\RegistrationsCapChecker'                                            => [
542
-                'EE_Capabilities' => EE_Dependency_Map::load_from_cache,
543
-            ],
544
-            'EventEspresso\core\domain\services\commands\registration\CreateRegistrationCommandHandler'                          => [
545
-                'EventEspresso\core\domain\services\registration\CreateRegistrationService' => EE_Dependency_Map::load_from_cache,
546
-            ],
547
-            'EventEspresso\core\domain\services\commands\registration\CopyRegistrationDetailsCommandHandler'                     => [
548
-                'EventEspresso\core\domain\services\registration\CopyRegistrationService' => EE_Dependency_Map::load_from_cache,
549
-            ],
550
-            'EventEspresso\core\domain\services\commands\registration\CopyRegistrationPaymentsCommandHandler'                    => [
551
-                'EventEspresso\core\domain\services\registration\CopyRegistrationService' => EE_Dependency_Map::load_from_cache,
552
-            ],
553
-            'EventEspresso\core\domain\services\commands\registration\CancelRegistrationAndTicketLineItemCommandHandler'         => [
554
-                'EventEspresso\core\domain\services\registration\CancelTicketLineItemService' => EE_Dependency_Map::load_from_cache,
555
-            ],
556
-            'EventEspresso\core\domain\services\commands\registration\UpdateRegistrationAndTransactionAfterChangeCommandHandler' => [
557
-                'EventEspresso\core\domain\services\registration\UpdateRegistrationService' => EE_Dependency_Map::load_from_cache,
558
-            ],
559
-            'EventEspresso\core\domain\services\commands\ticket\CreateTicketLineItemCommandHandler'                              => [
560
-                'EventEspresso\core\domain\services\ticket\CreateTicketLineItemService' => EE_Dependency_Map::load_from_cache,
561
-            ],
562
-            'EventEspresso\core\domain\services\commands\ticket\CancelTicketLineItemCommandHandler'                              => [
563
-                'EventEspresso\core\domain\services\ticket\CancelTicketLineItemService' => EE_Dependency_Map::load_from_cache,
564
-            ],
565
-            'EventEspresso\core\domain\services\registration\CancelRegistrationService'                                          => [
566
-                'EventEspresso\core\domain\services\ticket\CancelTicketLineItemService' => EE_Dependency_Map::load_from_cache,
567
-            ],
568
-            'EventEspresso\core\domain\services\commands\attendee\CreateAttendeeCommandHandler'                                  => [
569
-                'EEM_Attendee' => EE_Dependency_Map::load_from_cache,
570
-            ],
571
-            'EventEspresso\core\domain\values\session\SessionLifespan'                                                           => [
572
-                'EventEspresso\core\domain\values\session\SessionLifespanOption' => EE_Dependency_Map::load_from_cache,
573
-            ],
574
-            'EventEspresso\caffeinated\admin\extend\registration_form\forms\SessionLifespanForm'                                 => [
575
-                'EventEspresso\core\domain\values\session\SessionLifespanOption' => EE_Dependency_Map::load_from_cache,
576
-            ],
577
-            'EventEspresso\caffeinated\admin\extend\registration_form\forms\SessionLifespanFormHandler'                          => [
578
-                'EventEspresso\core\domain\values\session\SessionLifespanOption' => EE_Dependency_Map::load_from_cache,
579
-            ],
580
-            'EventEspresso\core\services\database\TableManager'                                                                  => [
581
-                'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
582
-            ],
583
-            'EE_Data_Migration_Class_Base'                                                                                       => [
584
-                'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
585
-                'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
586
-            ],
587
-            'EE_DMS_Core_4_1_0'                                                                                                  => [
588
-                'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
589
-                'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
590
-            ],
591
-            'EE_DMS_Core_4_2_0'                                                                                                  => [
592
-                'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
593
-                'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
594
-            ],
595
-            'EE_DMS_Core_4_3_0'                                                                                                  => [
596
-                'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
597
-                'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
598
-            ],
599
-            'EE_DMS_Core_4_4_0'                                                                                                  => [
600
-                'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
601
-                'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
602
-            ],
603
-            'EE_DMS_Core_4_5_0'                                                                                                  => [
604
-                'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
605
-                'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
606
-            ],
607
-            'EE_DMS_Core_4_6_0'                                                                                                  => [
608
-                'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
609
-                'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
610
-            ],
611
-            'EE_DMS_Core_4_7_0'                                                                                                  => [
612
-                'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
613
-                'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
614
-            ],
615
-            'EE_DMS_Core_4_8_0'                                                                                                  => [
616
-                'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
617
-                'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
618
-            ],
619
-            'EE_DMS_Core_4_9_0'                                                                                                  => [
620
-                'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
621
-                'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
622
-            ],
623
-            'EE_DMS_Core_4_10_0'                                                                                                 => [
624
-                'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
625
-                'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
626
-                'EE_DMS_Core_4_9_0'                                  => EE_Dependency_Map::load_from_cache,
627
-            ],
628
-            'EE_DMS_Core_5_0_0'                                                                                                  => [
629
-                'EE_DMS_Core_4_10_0'                                 => EE_Dependency_Map::load_from_cache,
630
-                'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
631
-                'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
632
-            ],
633
-            'EventEspresso\core\services\assets\I18nRegistry'                                                                    => [
634
-                'EventEspresso\core\domain\Domain' => EE_Dependency_Map::load_from_cache,
635
-            ],
636
-            'EventEspresso\core\services\assets\Registry'                                                                        => [
637
-                'EventEspresso\core\services\assets\AssetCollection' => EE_Dependency_Map::load_from_cache,
638
-                'EventEspresso\core\services\assets\AssetManifest'   => EE_Dependency_Map::load_from_cache,
639
-            ],
640
-            'EventEspresso\core\domain\entities\shortcodes\EspressoCancelled'                                                    => [
641
-                'EventEspresso\core\services\cache\PostRelatedCacheManager' => EE_Dependency_Map::load_from_cache,
642
-            ],
643
-            'EventEspresso\core\domain\entities\shortcodes\EspressoCheckout'                                                     => [
644
-                'EventEspresso\core\services\cache\PostRelatedCacheManager' => EE_Dependency_Map::load_from_cache,
645
-            ],
646
-            'EventEspresso\core\domain\entities\shortcodes\EspressoEventAttendees'                                               => [
647
-                'EventEspresso\core\services\cache\PostRelatedCacheManager' => EE_Dependency_Map::load_from_cache,
648
-            ],
649
-            'EventEspresso\core\domain\entities\shortcodes\EspressoEvents'                                                       => [
650
-                'EventEspresso\core\services\cache\PostRelatedCacheManager' => EE_Dependency_Map::load_from_cache,
651
-            ],
652
-            'EventEspresso\core\domain\entities\shortcodes\EspressoThankYou'                                                     => [
653
-                'EventEspresso\core\services\cache\PostRelatedCacheManager' => EE_Dependency_Map::load_from_cache,
654
-            ],
655
-            'EventEspresso\core\domain\entities\shortcodes\EspressoTicketSelector'                                               => [
656
-                'EventEspresso\core\services\cache\PostRelatedCacheManager' => EE_Dependency_Map::load_from_cache,
657
-            ],
658
-            'EventEspresso\core\domain\entities\shortcodes\EspressoTxnPage'                                                      => [
659
-                'EventEspresso\core\services\cache\PostRelatedCacheManager' => EE_Dependency_Map::load_from_cache,
660
-            ],
661
-            'EventEspresso\core\services\cache\BasicCacheManager'                                                                => [
662
-                'EventEspresso\core\services\cache\TransientCacheStorage' => EE_Dependency_Map::load_from_cache,
663
-            ],
664
-            'EventEspresso\core\services\cache\PostRelatedCacheManager'                                                          => [
665
-                'EventEspresso\core\services\cache\TransientCacheStorage' => EE_Dependency_Map::load_from_cache,
666
-            ],
667
-            'EventEspresso\core\domain\services\validation\email\EmailValidationService'                                         => [
668
-                'EE_Registration_Config'                     => EE_Dependency_Map::load_from_cache,
669
-                'EventEspresso\core\services\loaders\Loader' => EE_Dependency_Map::load_from_cache,
670
-            ],
671
-            'EventEspresso\core\domain\values\EmailAddress'                                                                      => [
672
-                null,
673
-                'EventEspresso\core\domain\services\validation\email\EmailValidationService' => EE_Dependency_Map::load_from_cache,
674
-            ],
675
-            'EventEspresso\core\services\orm\ModelFieldFactory'                                                                  => [
676
-                'EventEspresso\core\services\loaders\Loader' => EE_Dependency_Map::load_from_cache,
677
-            ],
678
-            'LEGACY_MODELS'                                                                                                      => [
679
-                null,
680
-                'EventEspresso\core\services\database\ModelFieldFactory' => EE_Dependency_Map::load_from_cache,
681
-            ],
682
-            'EE_Module_Request_Router'                                                                                           => [
683
-                'EventEspresso\core\services\request\Request' => EE_Dependency_Map::load_from_cache,
684
-            ],
685
-            'EE_Registration_Processor'                                                                                          => [
686
-                'EventEspresso\core\services\request\Request' => EE_Dependency_Map::load_from_cache,
687
-            ],
688
-            'EventEspresso\core\services\notifications\PersistentAdminNoticeManager'                                             => [
689
-                'EventEspresso\core\domain\services\capabilities\CapabilitiesChecker' => EE_Dependency_Map::load_from_cache,
690
-                'EventEspresso\core\services\request\Request'                         => EE_Dependency_Map::load_from_cache,
691
-            ],
692
-            'EventEspresso\caffeinated\modules\recaptcha_invisible\InvisibleRecaptcha'                                           => [
693
-                'EE_Registration_Config' => EE_Dependency_Map::load_from_cache,
694
-                'EE_Session'             => EE_Dependency_Map::load_from_cache,
695
-            ],
696
-            'EventEspresso\modules\ticket_selector\DisplayTicketSelector'                                                        => [
697
-                'EventEspresso\core\domain\entities\users\CurrentUser' => EE_Dependency_Map::load_from_cache,
698
-                'EventEspresso\core\services\request\Request'          => EE_Dependency_Map::load_from_cache,
699
-                'EE_Ticket_Selector_Config'                            => EE_Dependency_Map::load_from_cache,
700
-            ],
701
-            'EventEspresso\modules\ticket_selector\ProcessTicketSelector'                                                        => [
702
-                'EE_Core_Config'                                                          => EE_Dependency_Map::load_from_cache,
703
-                'EventEspresso\core\services\request\Request'                             => EE_Dependency_Map::load_from_cache,
704
-                'EE_Session'                                                              => EE_Dependency_Map::load_from_cache,
705
-                'EEM_Ticket'                                                              => EE_Dependency_Map::load_from_cache,
706
-                'EventEspresso\modules\ticket_selector\TicketDatetimeAvailabilityTracker' => EE_Dependency_Map::load_from_cache,
707
-            ],
708
-            'EventEspresso\modules\ticket_selector\ProcessTicketSelectorPostData'                                                => [
709
-                'EventEspresso\core\services\request\Request' => EE_Dependency_Map::load_from_cache,
710
-                'EEM_Event'                                   => EE_Dependency_Map::load_from_cache,
711
-            ],
712
-            'EventEspresso\modules\ticket_selector\TicketDatetimeAvailabilityTracker'                                            => [
713
-                'EEM_Datetime' => EE_Dependency_Map::load_from_cache,
714
-            ],
715
-            'EventEspresso\core\domain\entities\custom_post_types\CustomPostTypeDefinitions'                                     => [
716
-                'EE_Core_Config'                             => EE_Dependency_Map::load_from_cache,
717
-                'EventEspresso\core\services\loaders\Loader' => EE_Dependency_Map::load_from_cache,
718
-            ],
719
-            'EventEspresso\core\domain\services\custom_post_types\RegisterCustomPostTypes'                                       => [
720
-                'EventEspresso\core\domain\entities\custom_post_types\CustomPostTypeDefinitions' => EE_Dependency_Map::load_from_cache,
721
-            ],
722
-            'EventEspresso\core\domain\services\custom_post_types\RegisterCustomTaxonomies'                                      => [
723
-                'EventEspresso\core\domain\entities\custom_post_types\CustomTaxonomyDefinitions' => EE_Dependency_Map::load_from_cache,
724
-            ],
725
-            'EE_CPT_Strategy'                                                                                                    => [
726
-                'EventEspresso\core\domain\entities\custom_post_types\CustomPostTypeDefinitions' => EE_Dependency_Map::load_from_cache,
727
-                'EventEspresso\core\domain\entities\custom_post_types\CustomTaxonomyDefinitions' => EE_Dependency_Map::load_from_cache,
728
-            ],
729
-            'EventEspresso\core\services\loaders\ObjectIdentifier'                                                               => [
730
-                'EventEspresso\core\services\loaders\ClassInterfaceCache' => EE_Dependency_Map::load_from_cache,
731
-            ],
732
-            'EventEspresso\core\CPTs\CptQueryModifier'                                                                           => [
733
-                null,
734
-                null,
735
-                null,
736
-                'EventEspresso\core\services\request\CurrentPage' => EE_Dependency_Map::load_from_cache,
737
-                'EventEspresso\core\services\request\Request'     => EE_Dependency_Map::load_from_cache,
738
-                'EventEspresso\core\services\loaders\Loader'      => EE_Dependency_Map::load_from_cache,
739
-            ],
740
-            'EventEspresso\core\services\dependencies\DependencyResolver'                                                        => [
741
-                'EventEspresso\core\services\container\Mirror'            => EE_Dependency_Map::load_from_cache,
742
-                'EventEspresso\core\services\loaders\ClassInterfaceCache' => EE_Dependency_Map::load_from_cache,
743
-                'EE_Dependency_Map'                                       => EE_Dependency_Map::load_from_cache,
744
-            ],
745
-            'EventEspresso\core\services\routing\RouteMatchSpecificationDependencyResolver'                                      => [
746
-                'EventEspresso\core\services\container\Mirror'            => EE_Dependency_Map::load_from_cache,
747
-                'EventEspresso\core\services\loaders\ClassInterfaceCache' => EE_Dependency_Map::load_from_cache,
748
-                'EE_Dependency_Map'                                       => EE_Dependency_Map::load_from_cache,
749
-            ],
750
-            'EventEspresso\core\services\routing\RouteMatchSpecificationFactory'                                                 => [
751
-                'EventEspresso\core\services\routing\RouteMatchSpecificationDependencyResolver' => EE_Dependency_Map::load_from_cache,
752
-                'EventEspresso\core\services\loaders\Loader'                                    => EE_Dependency_Map::load_from_cache,
753
-            ],
754
-            'EventEspresso\core\services\routing\RouteMatchSpecificationManager'                                                 => [
755
-                'EventEspresso\core\services\routing\RouteMatchSpecificationCollection' => EE_Dependency_Map::load_from_cache,
756
-                'EventEspresso\core\services\routing\RouteMatchSpecificationFactory'    => EE_Dependency_Map::load_from_cache,
757
-            ],
758
-            'EventEspresso\core\services\request\files\FilesDataHandler'                                                         => [
759
-                'EventEspresso\core\services\request\Request' => EE_Dependency_Map::load_from_cache,
760
-            ],
761
-            'EventEspresso\core\libraries\batch\BatchRequestProcessor'                                                           => [
762
-                'EventEspresso\core\services\loaders\Loader'  => EE_Dependency_Map::load_from_cache,
763
-                'EventEspresso\core\services\request\Request' => EE_Dependency_Map::load_from_cache,
764
-            ],
765
-            'EventEspresso\core\domain\services\converters\RestApiSpoofer'                                                       => [
766
-                'WP_REST_Server'                                               => EE_Dependency_Map::load_from_cache,
767
-                'EED_Core_Rest_Api'                                            => EE_Dependency_Map::load_from_cache,
768
-                'EventEspresso\core\libraries\rest_api\controllers\model\Read' => EE_Dependency_Map::load_from_cache,
769
-                null,
770
-            ],
771
-            'EventEspresso\core\services\routing\RouteHandler'                                                                   => [
772
-                'EventEspresso\core\domain\services\capabilities\CapabilitiesChecker' => EE_Dependency_Map::load_from_cache,
773
-                'EventEspresso\core\services\json\JsonDataNodeHandler'                => EE_Dependency_Map::load_from_cache,
774
-                'EventEspresso\core\services\loaders\Loader'                          => EE_Dependency_Map::load_from_cache,
775
-                'EventEspresso\core\services\request\Request'                         => EE_Dependency_Map::load_from_cache,
776
-                'EventEspresso\core\services\routing\RouteCollection'                 => EE_Dependency_Map::load_from_cache,
777
-            ],
778
-            'EventEspresso\core\services\json\JsonDataNodeHandler'                                                               => [
779
-                'EventEspresso\core\services\json\JsonDataNodeValidator' => EE_Dependency_Map::load_from_cache,
780
-            ],
781
-            'EventEspresso\core\services\routing\Router'                                                                         => [
782
-                'EE_Dependency_Map'                                => EE_Dependency_Map::load_from_cache,
783
-                'EventEspresso\core\services\loaders\Loader'       => EE_Dependency_Map::load_from_cache,
784
-                'EventEspresso\core\services\routing\RouteHandler' => EE_Dependency_Map::load_from_cache,
785
-            ],
786
-            'EventEspresso\core\services\assets\AssetManifest'                                                                   => [
787
-                'EventEspresso\core\domain\Domain' => EE_Dependency_Map::load_from_cache,
788
-            ],
789
-            'EventEspresso\core\services\assets\AssetManifestFactory'                                                            => [
790
-                'EventEspresso\core\services\loaders\Loader' => EE_Dependency_Map::load_from_cache,
791
-            ],
792
-            'EventEspresso\core\services\assets\BaristaFactory'                                                                  => [
793
-                'EventEspresso\core\services\assets\AssetManifestFactory' => EE_Dependency_Map::load_from_cache,
794
-                'EventEspresso\core\services\loaders\Loader'              => EE_Dependency_Map::load_from_cache,
795
-            ],
796
-            'EventEspresso\core\domain\services\capabilities\FeatureFlagsConfig' => [
797
-                'EventEspresso\core\domain\Domain'                                    => EE_Dependency_Map::load_from_cache,
798
-                'EventEspresso\core\services\json\JsonDataHandler'                    => EE_Dependency_Map::load_from_cache,
799
-            ],
800
-            'EventEspresso\core\domain\services\capabilities\FeatureFlags'       => [
801
-                'EventEspresso\core\domain\services\capabilities\CapabilitiesChecker' => EE_Dependency_Map::load_from_cache,
802
-                'EventEspresso\core\domain\services\capabilities\FeatureFlagsConfig'  => EE_Dependency_Map::load_from_cache,
803
-            ],
804
-            'EventEspresso\core\services\addon\AddonManager'                                                                     => [
805
-                'EventEspresso\core\services\addon\AddonCollection'              => EE_Dependency_Map::load_from_cache,
806
-                'EventEspresso\core\Psr4Autoloader'                              => EE_Dependency_Map::load_from_cache,
807
-                'EventEspresso\core\services\addon\api\v1\RegisterAddon'         => EE_Dependency_Map::load_from_cache,
808
-                'EventEspresso\core\services\addon\api\IncompatibleAddonHandler' => EE_Dependency_Map::load_from_cache,
809
-                'EventEspresso\core\services\addon\api\ThirdPartyPluginHandler'  => EE_Dependency_Map::load_from_cache,
810
-            ],
811
-            'EventEspresso\core\services\addon\api\ThirdPartyPluginHandler'                                                      => [
812
-                'EventEspresso\core\services\request\Request' => EE_Dependency_Map::load_from_cache,
813
-            ],
814
-            'EventEspresso\core\libraries\batch\JobHandlers\ExecuteBatchDeletion'                                                => [
815
-                'EventEspresso\core\services\orm\tree_traversal\NodeGroupDao' => EE_Dependency_Map::load_from_cache,
816
-            ],
817
-            'EventEspresso\core\libraries\batch\JobHandlers\PreviewEventDeletion'                                                => [
818
-                'EventEspresso\core\services\orm\tree_traversal\NodeGroupDao' => EE_Dependency_Map::load_from_cache,
819
-            ],
820
-            'EventEspresso\core\domain\services\admin\events\data\PreviewDeletion'                                               => [
821
-                'EventEspresso\core\services\orm\tree_traversal\NodeGroupDao' => EE_Dependency_Map::load_from_cache,
822
-                'EEM_Event'                                                   => EE_Dependency_Map::load_from_cache,
823
-                'EEM_Datetime'                                                => EE_Dependency_Map::load_from_cache,
824
-                'EEM_Registration'                                            => EE_Dependency_Map::load_from_cache,
825
-            ],
826
-            'EventEspresso\core\domain\services\admin\events\data\ConfirmDeletion'                                               => [
827
-                'EventEspresso\core\services\orm\tree_traversal\NodeGroupDao' => EE_Dependency_Map::load_from_cache,
828
-            ],
829
-            'EventEspresso\core\services\request\CurrentPage'                                                                    => [
830
-                'EE_CPT_Strategy'                             => EE_Dependency_Map::load_from_cache,
831
-                'EventEspresso\core\services\request\Request' => EE_Dependency_Map::load_from_cache,
832
-            ],
833
-            'EventEspresso\core\services\shortcodes\LegacyShortcodesManager'                                                     => [
834
-                'EE_Registry'                                     => EE_Dependency_Map::load_from_cache,
835
-                'EventEspresso\core\services\request\CurrentPage' => EE_Dependency_Map::load_from_cache,
836
-            ],
837
-            'EventEspresso\core\services\shortcodes\ShortcodesManager'                                                           => [
838
-                'EventEspresso\core\services\shortcodes\LegacyShortcodesManager' => EE_Dependency_Map::load_from_cache,
839
-                'EventEspresso\core\services\request\CurrentPage'                => EE_Dependency_Map::load_from_cache,
840
-            ],
841
-            'EventEspresso\core\domain\entities\users\CurrentUser'                                                               => [
842
-                'EventEspresso\core\domain\entities\users\EventManagers' => EE_Dependency_Map::load_from_cache,
843
-            ],
844
-            'EventEspresso\core\services\form\meta\InputTypes'                                                                   => [
845
-                'EventEspresso\core\services\form\meta\inputs\Block'    => EE_Dependency_Map::load_from_cache,
846
-                'EventEspresso\core\services\form\meta\inputs\Button'   => EE_Dependency_Map::load_from_cache,
847
-                'EventEspresso\core\services\form\meta\inputs\DateTime' => EE_Dependency_Map::load_from_cache,
848
-                'EventEspresso\core\services\form\meta\inputs\Input'    => EE_Dependency_Map::load_from_cache,
849
-                'EventEspresso\core\services\form\meta\inputs\Number'   => EE_Dependency_Map::load_from_cache,
850
-                'EventEspresso\core\services\form\meta\inputs\Phone'    => EE_Dependency_Map::load_from_cache,
851
-                'EventEspresso\core\services\form\meta\inputs\Select'   => EE_Dependency_Map::load_from_cache,
852
-                'EventEspresso\core\services\form\meta\inputs\Text'     => EE_Dependency_Map::load_from_cache,
853
-            ],
854
-            'EventEspresso\core\domain\services\registration\form\v1\RegFormDependencyHandler'                                   => [
855
-                'EE_Dependency_Map' => EE_Dependency_Map::load_from_cache,
856
-            ],
857
-            'EventEspresso\core\services\calculators\LineItemCalculator'                                                         => [
858
-                'EventEspresso\core\services\helpers\DecimalValues' => EE_Dependency_Map::load_from_cache,
859
-            ],
860
-            'EventEspresso\core\services\helpers\DecimalValues'                                                                  => [
861
-                'EE_Currency_Config' => EE_Dependency_Map::load_from_cache,
862
-            ],
863
-            'EE_Brewing_Regular'                                                                                                 => [
864
-                'EE_Dependency_Map'                                  => EE_Dependency_Map::load_from_cache,
865
-                'EventEspresso\core\services\loaders\Loader'         => EE_Dependency_Map::load_from_cache,
866
-                'EventEspresso\core\services\routing\RouteHandler'   => EE_Dependency_Map::load_from_cache,
867
-                'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
868
-            ],
869
-            'EventEspresso\core\domain\services\messages\MessageTemplateRequestData'                                             => [
870
-                'EventEspresso\core\services\request\Request' => EE_Dependency_Map::load_from_cache,
871
-            ],
872
-            'EventEspresso\core\domain\services\messages\MessageTemplateValidator'                                               => [
873
-                'EventEspresso\core\services\loaders\Loader' => EE_Dependency_Map::load_from_cache,
874
-            ],
875
-            'EventEspresso\core\domain\services\messages\MessageTemplateManager'                                                 => [
876
-                'EEM_Message_Template'                                                   => EE_Dependency_Map::load_from_cache,
877
-                'EEM_Message_Template_Group'                                             => EE_Dependency_Map::load_from_cache,
878
-                'EventEspresso\core\domain\services\messages\MessageTemplateRequestData' => EE_Dependency_Map::load_from_cache,
879
-                'EventEspresso\core\domain\services\messages\MessageTemplateValidator'   => EE_Dependency_Map::load_from_cache,
880
-                'EventEspresso\core\services\request\Request'                            => EE_Dependency_Map::load_from_cache,
881
-            ],
882
-            'EventEspresso\core\services\request\sanitizers\RequestSanitizer'                                                    => [
883
-                'EventEspresso\core\domain\services\validation\email\strategies\Basic' => EE_Dependency_Map::load_from_cache,
884
-            ],
885
-            'EE_CPT_Event_Strategy'                                                    => [
886
-                null,
887
-                null,
888
-                'EE_Template_Config' => EE_Dependency_Map::load_from_cache,
889
-            ],
890
-        ];
891
-    }
892
-
893
-
894
-    /**
895
-     * Registers how core classes are loaded.
896
-     * This can either be done by simply providing the name of one of the EE_Registry loader methods such as:
897
-     *        'EE_Request_Handler' => 'load_core'
898
-     *        'EE_Messages_Queue'  => 'load_lib'
899
-     *        'EEH_Debug_Tools'    => 'load_helper'
900
-     * or, if greater control is required, by providing a custom closure. For example:
901
-     *        'Some_Class' => function () {
902
-     *            return new Some_Class();
903
-     *        },
904
-     * This is required for instantiating dependencies
905
-     * where an interface has been type hinted in a class constructor. For example:
906
-     *        'Required_Interface' => function () {
907
-     *            return new A_Class_That_Implements_Required_Interface();
908
-     *        },
909
-     */
910
-    protected function _register_core_class_loaders()
911
-    {
912
-        $this->_class_loaders = [
913
-            // load_core
914
-            'EE_Dependency_Map'                            => function () {
915
-                return $this;
916
-            },
917
-            'EE_Capabilities'                              => 'load_core',
918
-            'EE_Encryption'                                => 'load_core',
919
-            'EE_Front_Controller'                          => 'load_core',
920
-            'EE_Module_Request_Router'                     => 'load_core',
921
-            'EE_Registry'                                  => 'load_core',
922
-            'EE_Request'                                   => function () {
923
-                return $this->legacy_request;
924
-            },
925
-            'EventEspresso\core\services\request\Request'  => function () {
926
-                return $this->request;
927
-            },
928
-            'EventEspresso\core\services\request\Response' => function () {
929
-                return $this->response;
930
-            },
931
-            'EE_Base'                                      => 'load_core',
932
-            'EE_Request_Handler'                           => 'load_core',
933
-            'EE_Session'                                   => 'load_core',
934
-            'EE_Cron_Tasks'                                => 'load_core',
935
-            'EE_System'                                    => 'load_core',
936
-            'EE_Maintenance_Mode'                          => 'load_core',
937
-            'EE_Register_CPTs'                             => 'load_core',
938
-            'EE_Admin'                                     => 'load_core',
939
-            'EE_CPT_Strategy'                              => 'load_core',
940
-            // load_class
941
-            'EE_Registration_Processor'                    => 'load_class',
942
-            'EE_Transaction_Payments'                      => 'load_class',
943
-            'EE_Transaction_Processor'                     => 'load_class',
944
-            // load_lib
945
-            'EE_Message_Resource_Manager'                  => 'load_lib',
946
-            'EE_Message_Type_Collection'                   => 'load_lib',
947
-            'EE_Message_Type_Collection_Loader'            => 'load_lib',
948
-            'EE_Messenger_Collection'                      => 'load_lib',
949
-            'EE_Messenger_Collection_Loader'               => 'load_lib',
950
-            'EE_Messages_Processor'                        => 'load_lib',
951
-            'EE_Message_Repository'                        => 'load_lib',
952
-            'EE_Messages_Queue'                            => 'load_lib',
953
-            'EE_Messages_Data_Handler_Collection'          => 'load_lib',
954
-            'EE_Message_Template_Group_Collection'         => 'load_lib',
955
-            'EE_Payment_Method_Manager'                    => 'load_lib',
956
-            'EE_Payment_Processor'                         => 'load_core',
957
-            'EE_DMS_Core_4_1_0'                            => 'load_dms',
958
-            'EE_DMS_Core_4_2_0'                            => 'load_dms',
959
-            'EE_DMS_Core_4_3_0'                            => 'load_dms',
960
-            'EE_DMS_Core_4_5_0'                            => 'load_dms',
961
-            'EE_DMS_Core_4_6_0'                            => 'load_dms',
962
-            'EE_DMS_Core_4_7_0'                            => 'load_dms',
963
-            'EE_DMS_Core_4_8_0'                            => 'load_dms',
964
-            'EE_DMS_Core_4_9_0'                            => 'load_dms',
965
-            'EE_DMS_Core_4_10_0'                           => 'load_dms',
966
-            'EE_DMS_Core_5_0_0'                            => 'load_dms',
967
-            'EE_Messages_Generator'                        => static function () {
968
-                return EE_Registry::instance()->load_lib(
969
-                    'Messages_Generator',
970
-                    [],
971
-                    false,
972
-                    false
973
-                );
974
-            },
975
-            'EE_Messages_Template_Defaults'                => static function ($arguments = []) {
976
-                return EE_Registry::instance()->load_lib(
977
-                    'Messages_Template_Defaults',
978
-                    $arguments,
979
-                    false,
980
-                    false
981
-                );
982
-            },
983
-            // load_helper
984
-            'EEH_Parse_Shortcodes'                         => static function () {
985
-                if (EE_Registry::instance()->load_helper('Parse_Shortcodes')) {
986
-                    return new EEH_Parse_Shortcodes();
987
-                }
988
-                return null;
989
-            },
990
-            'EE_Template_Config'                           => static function () {
991
-                return EE_Config::instance()->template_settings;
992
-            },
993
-            'EE_Currency_Config'                           => static function () {
994
-                return EE_Currency_Config::getCurrencyConfig();
995
-            },
996
-            'EE_Registration_Config'                       => static function () {
997
-                return EE_Config::instance()->registration;
998
-            },
999
-            'EE_Core_Config'                               => static function () {
1000
-                return EE_Config::instance()->core;
1001
-            },
1002
-            'EventEspresso\core\services\loaders\Loader'   => static function () {
1003
-                return LoaderFactory::getLoader();
1004
-            },
1005
-            'EE_Network_Config'                            => static function () {
1006
-                return EE_Network_Config::instance();
1007
-            },
1008
-            'EE_Config'                                    => static function () {
1009
-                return EE_Config::instance();
1010
-            },
1011
-            'EventEspresso\core\domain\Domain'             => static function () {
1012
-                return DomainFactory::getEventEspressoCoreDomain();
1013
-            },
1014
-            'EE_Admin_Config'                              => static function () {
1015
-                return EE_Config::instance()->admin;
1016
-            },
1017
-            'EE_Organization_Config'                       => static function () {
1018
-                return EE_Config::instance()->organization;
1019
-            },
1020
-            'EE_Network_Core_Config'                       => static function () {
1021
-                return EE_Network_Config::instance()->core;
1022
-            },
1023
-            'EE_Environment_Config'                        => static function () {
1024
-                return EE_Config::instance()->environment;
1025
-            },
1026
-            'EED_Core_Rest_Api'                            => static function () {
1027
-                return EED_Core_Rest_Api::instance();
1028
-            },
1029
-            'WP_REST_Server'                               => static function () {
1030
-                return rest_get_server();
1031
-            },
1032
-            'EventEspresso\core\Psr4Autoloader'            => static function () {
1033
-                return EE_Psr4AutoloaderInit::psr4_loader();
1034
-            },
1035
-            'EE_Ticket_Selector_Config'                    => function () {
1036
-                return EE_Config::instance()->template_settings->EED_Ticket_Selector;
1037
-            },
1038
-        ];
1039
-    }
1040
-
1041
-
1042
-    /**
1043
-     * can be used for supplying alternate names for classes,
1044
-     * or for connecting interface names to instantiable classes
1045
-     *
1046
-     * @throws InvalidAliasException
1047
-     */
1048
-    protected function _register_core_aliases()
1049
-    {
1050
-        $aliases = [
1051
-            'CommandBusInterface'                                                          => 'EventEspresso\core\services\commands\CommandBusInterface',
1052
-            'EventEspresso\core\services\commands\CommandBusInterface'                     => 'EventEspresso\core\services\commands\CommandBus',
1053
-            'CommandHandlerManagerInterface'                                               => 'EventEspresso\core\services\commands\CommandHandlerManagerInterface',
1054
-            'EventEspresso\core\services\commands\CommandHandlerManagerInterface'          => 'EventEspresso\core\services\commands\CommandHandlerManager',
1055
-            'CapChecker'                                                                   => 'EventEspresso\core\services\commands\middleware\CapChecker',
1056
-            'AddActionHook'                                                                => 'EventEspresso\core\services\commands\middleware\AddActionHook',
1057
-            'CapabilitiesChecker'                                                          => 'EventEspresso\core\domain\services\capabilities\CapabilitiesChecker',
1058
-            'CapabilitiesCheckerInterface'                                                 => 'EventEspresso\core\domain\services\capabilities\CapabilitiesCheckerInterface',
1059
-            'EventEspresso\core\domain\services\capabilities\CapabilitiesCheckerInterface' => 'EventEspresso\core\domain\services\capabilities\CapabilitiesChecker',
1060
-            'CreateRegistrationService'                                                    => 'EventEspresso\core\domain\services\registration\CreateRegistrationService',
1061
-            'CreateRegistrationCommandHandler'                                             => 'EventEspresso\core\domain\services\commands\registration\CreateRegistrationCommand',
1062
-            'CopyRegistrationDetailsCommandHandler'                                        => 'EventEspresso\core\domain\services\commands\registration\CopyRegistrationDetailsCommand',
1063
-            'CopyRegistrationPaymentsCommandHandler'                                       => 'EventEspresso\core\domain\services\commands\registration\CopyRegistrationPaymentsCommand',
1064
-            'CancelRegistrationAndTicketLineItemCommandHandler'                            => 'EventEspresso\core\domain\services\commands\registration\CancelRegistrationAndTicketLineItemCommandHandler',
1065
-            'UpdateRegistrationAndTransactionAfterChangeCommandHandler'                    => 'EventEspresso\core\domain\services\commands\registration\UpdateRegistrationAndTransactionAfterChangeCommandHandler',
1066
-            'CreateTicketLineItemCommandHandler'                                           => 'EventEspresso\core\domain\services\commands\ticket\CreateTicketLineItemCommand',
1067
-            'CreateTransactionCommandHandler'                                              => 'EventEspresso\core\domain\services\commands\transaction\CreateTransactionCommandHandler',
1068
-            'CreateAttendeeCommandHandler'                                                 => 'EventEspresso\core\domain\services\commands\attendee\CreateAttendeeCommandHandler',
1069
-            'TableManager'                                                                 => 'EventEspresso\core\services\database\TableManager',
1070
-            'TableAnalysis'                                                                => 'EventEspresso\core\services\database\TableAnalysis',
1071
-            'EspressoShortcode'                                                            => 'EventEspresso\core\services\shortcodes\EspressoShortcode',
1072
-            'ShortcodeInterface'                                                           => 'EventEspresso\core\services\shortcodes\ShortcodeInterface',
1073
-            'EventEspresso\core\services\shortcodes\ShortcodeInterface'                    => 'EventEspresso\core\services\shortcodes\EspressoShortcode',
1074
-            'EventEspresso\core\services\cache\CacheStorageInterface'                      => 'EventEspresso\core\services\cache\TransientCacheStorage',
1075
-            'LoaderInterface'                                                              => 'EventEspresso\core\services\loaders\LoaderInterface',
1076
-            'EventEspresso\core\services\loaders\LoaderInterface'                          => 'EventEspresso\core\services\loaders\Loader',
1077
-            'CommandFactoryInterface'                                                      => 'EventEspresso\core\services\commands\CommandFactoryInterface',
1078
-            'EventEspresso\core\services\commands\CommandFactoryInterface'                 => 'EventEspresso\core\services\commands\CommandFactory',
1079
-            'EmailValidatorInterface'                                                      => 'EventEspresso\core\domain\services\validation\email\EmailValidatorInterface',
1080
-            'EventEspresso\core\domain\services\validation\email\EmailValidatorInterface'  => 'EventEspresso\core\domain\services\validation\email\EmailValidationService',
1081
-            'NoticeConverterInterface'                                                     => 'EventEspresso\core\services\notices\NoticeConverterInterface',
1082
-            'EventEspresso\core\services\notices\NoticeConverterInterface'                 => 'EventEspresso\core\services\notices\ConvertNoticesToEeErrors',
1083
-            'NoticesContainerInterface'                                                    => 'EventEspresso\core\services\notices\NoticesContainerInterface',
1084
-            'EventEspresso\core\services\notices\NoticesContainerInterface'                => 'EventEspresso\core\services\notices\NoticesContainer',
1085
-            'EventEspresso\core\services\request\RequestInterface'                         => 'EventEspresso\core\services\request\Request',
1086
-            'EventEspresso\core\services\request\ResponseInterface'                        => 'EventEspresso\core\services\request\Response',
1087
-            'EventEspresso\core\domain\DomainInterface'                                    => 'EventEspresso\core\domain\Domain',
1088
-            'Registration_Processor'                                                       => 'EE_Registration_Processor',
1089
-            'EventEspresso\core\services\assets\AssetManifestInterface'                    => 'EventEspresso\core\services\assets\AssetManifest',
1090
-        ];
1091
-        foreach ($aliases as $alias => $fqn) {
1092
-            if (is_array($fqn)) {
1093
-                foreach ($fqn as $class => $for_class) {
1094
-                    $this->class_cache->addAlias($class, $alias, $for_class);
1095
-                }
1096
-                continue;
1097
-            }
1098
-            $this->class_cache->addAlias($fqn, $alias);
1099
-        }
1100
-        if (! (defined('DOING_AJAX') && DOING_AJAX) && is_admin()) {
1101
-            $this->class_cache->addAlias(
1102
-                'EventEspresso\core\services\notices\ConvertNoticesToAdminNotices',
1103
-                'EventEspresso\core\services\notices\NoticeConverterInterface'
1104
-            );
1105
-        }
1106
-    }
1107
-
1108
-
1109
-    public function debug($for_class = '')
1110
-    {
1111
-        if (method_exists($this->class_cache, 'debug')) {
1112
-            $this->class_cache->debug($for_class);
1113
-        }
1114
-    }
1115
-
1116
-
1117
-    /**
1118
-     * This is used to reset the internal map and class_loaders to their original default state at the beginning of the
1119
-     * request Primarily used by unit tests.
1120
-     */
1121
-    public function reset()
1122
-    {
1123
-        $this->_register_core_class_loaders();
1124
-        $this->_register_core_dependencies();
1125
-    }
1126
-
1127
-
1128
-    /**
1129
-     * PLZ NOTE: a better name for this method would be is_alias()
1130
-     * because it returns TRUE if the provided fully qualified name IS an alias
1131
-     * WHY?
1132
-     * Because if a class is type hinting for a concretion,
1133
-     * then why would we need to find another class to supply it?
1134
-     * ie: if a class asks for `Fully/Qualified/Namespace/SpecificClassName`,
1135
-     * then give it an instance of `Fully/Qualified/Namespace/SpecificClassName`.
1136
-     * Don't go looking for some substitute.
1137
-     * Whereas if a class is type hinting for an interface...
1138
-     * then we need to find an actual class to use.
1139
-     * So the interface IS the alias for some other FQN,
1140
-     * and we need to find out if `Fully/Qualified/Namespace/SomeInterface`
1141
-     * represents some other class.
1142
-     *
1143
-     * @param string $fqn
1144
-     * @param string $for_class
1145
-     * @return bool
1146
-     * @deprecated 4.9.62.p
1147
-     */
1148
-    public function has_alias(string $fqn = '', string $for_class = ''): bool
1149
-    {
1150
-        return $this->isAlias($fqn, $for_class);
1151
-    }
1152
-
1153
-
1154
-    /**
1155
-     * PLZ NOTE: a better name for this method would be get_fqn_for_alias()
1156
-     * because it returns a FQN for provided alias if one exists, otherwise returns the original $alias
1157
-     * functions recursively, so that multiple aliases can be used to drill down to a FQN
1158
-     *  for example:
1159
-     *      if the following two entries were added to the _aliases array:
1160
-     *          array(
1161
-     *              'interface_alias'           => 'some\namespace\interface'
1162
-     *              'some\namespace\interface'  => 'some\namespace\classname'
1163
-     *          )
1164
-     *      then one could use EE_Registry::instance()->create( 'interface_alias' )
1165
-     *      to load an instance of 'some\namespace\classname'
1166
-     *
1167
-     * @param string $alias
1168
-     * @param string $for_class
1169
-     * @return string
1170
-     * @deprecated 4.9.62.p
1171
-     */
1172
-    public function get_alias(string $alias = '', string $for_class = ''): string
1173
-    {
1174
-        return $this->getFqnForAlias($alias, $for_class);
1175
-    }
23
+	/**
24
+	 * This means that the requested class dependency is not present in the dependency map
25
+	 */
26
+	const not_registered = 0;
27
+
28
+	/**
29
+	 * This instructs class loaders to ALWAYS return a newly instantiated object for the requested class.
30
+	 */
31
+	const load_new_object = 1;
32
+
33
+	/**
34
+	 * This instructs class loaders to return a previously instantiated and cached object for the requested class.
35
+	 * IF a previously instantiated object does not exist, a new one will be created and added to the cache.
36
+	 */
37
+	const load_from_cache = 2;
38
+
39
+	/**
40
+	 * When registering a dependency,
41
+	 * this indicates to keep any existing dependencies that already exist,
42
+	 * and simply discard any new dependencies declared in the incoming data
43
+	 */
44
+	const KEEP_EXISTING_DEPENDENCIES = 0;
45
+
46
+	/**
47
+	 * When registering a dependency,
48
+	 * this indicates to overwrite any existing dependencies that already exist using the incoming data
49
+	 */
50
+	const OVERWRITE_DEPENDENCIES = 1;
51
+
52
+	protected static ?EE_Dependency_Map $_instance = null;
53
+
54
+	private ClassInterfaceCache $class_cache;
55
+
56
+	protected ?RequestInterface $request = null;
57
+
58
+	protected ?LegacyRequestInterface $legacy_request = null;
59
+
60
+	protected ?ResponseInterface $response = null;
61
+
62
+	protected ?LoaderInterface $loader = null;
63
+
64
+	protected array $_dependency_map = [];
65
+
66
+	protected array $_class_loaders = [];
67
+
68
+
69
+	/**
70
+	 * EE_Dependency_Map constructor.
71
+	 *
72
+	 * @param ClassInterfaceCache $class_cache
73
+	 */
74
+	protected function __construct(ClassInterfaceCache $class_cache)
75
+	{
76
+		$this->class_cache = $class_cache;
77
+		do_action('EE_Dependency_Map____construct', $this);
78
+	}
79
+
80
+
81
+	/**
82
+	 * @return void
83
+	 * @throws InvalidAliasException
84
+	 */
85
+	public function initialize()
86
+	{
87
+		$this->_register_core_dependencies();
88
+		$this->_register_core_class_loaders();
89
+		$this->_register_core_aliases();
90
+	}
91
+
92
+
93
+	/**
94
+	 * @singleton method used to instantiate class object
95
+	 * @param ClassInterfaceCache|null $class_cache
96
+	 * @return EE_Dependency_Map
97
+	 */
98
+	public static function instance(ClassInterfaceCache $class_cache = null): EE_Dependency_Map
99
+	{
100
+		// check if class object is instantiated, and instantiated properly
101
+		if (
102
+			! EE_Dependency_Map::$_instance instanceof EE_Dependency_Map
103
+			&& $class_cache instanceof ClassInterfaceCache
104
+		) {
105
+			EE_Dependency_Map::$_instance = new EE_Dependency_Map($class_cache);
106
+		}
107
+		return EE_Dependency_Map::$_instance;
108
+	}
109
+
110
+
111
+	/**
112
+	 * @param RequestInterface $request
113
+	 */
114
+	public function setRequest(RequestInterface $request)
115
+	{
116
+		$this->request = $request;
117
+	}
118
+
119
+
120
+	/**
121
+	 * @param LegacyRequestInterface $legacy_request
122
+	 */
123
+	public function setLegacyRequest(LegacyRequestInterface $legacy_request)
124
+	{
125
+		$this->legacy_request = $legacy_request;
126
+	}
127
+
128
+
129
+	/**
130
+	 * @param ResponseInterface $response
131
+	 */
132
+	public function setResponse(ResponseInterface $response)
133
+	{
134
+		$this->response = $response;
135
+	}
136
+
137
+
138
+	/**
139
+	 * @param LoaderInterface $loader
140
+	 */
141
+	public function setLoader(LoaderInterface $loader)
142
+	{
143
+		$this->loader = $loader;
144
+	}
145
+
146
+
147
+	/**
148
+	 * @param string $class
149
+	 * @param array  $dependencies
150
+	 * @param int    $overwrite
151
+	 * @return bool
152
+	 */
153
+	public static function register_dependencies(
154
+		string $class,
155
+		array $dependencies,
156
+		int $overwrite = EE_Dependency_Map::KEEP_EXISTING_DEPENDENCIES
157
+	): bool {
158
+		return EE_Dependency_Map::$_instance->registerDependencies($class, $dependencies, $overwrite);
159
+	}
160
+
161
+
162
+	/**
163
+	 * Assigns an array of class names and corresponding load sources (new or cached)
164
+	 * to the class specified by the first parameter.
165
+	 * IMPORTANT !!!
166
+	 * The order of elements in the incoming $dependencies array MUST match
167
+	 * the order of the constructor parameters for the class in question.
168
+	 * This is especially important when overriding any existing dependencies that are registered.
169
+	 * the third parameter controls whether any duplicate dependencies are overwritten or not.
170
+	 *
171
+	 * @param string $class
172
+	 * @param array  $dependencies
173
+	 * @param int    $overwrite
174
+	 * @return bool
175
+	 */
176
+	public function registerDependencies(
177
+		string $class,
178
+		array $dependencies,
179
+		int $overwrite = EE_Dependency_Map::KEEP_EXISTING_DEPENDENCIES
180
+	): bool {
181
+		if (empty($dependencies)) {
182
+			return false;
183
+		}
184
+		$class      = trim($class, '\\');
185
+		$registered = false;
186
+		if (empty(EE_Dependency_Map::$_instance->_dependency_map[ $class ])) {
187
+			EE_Dependency_Map::$_instance->_dependency_map[ $class ] = [];
188
+		}
189
+		// we need to make sure that any aliases used when registering a dependency
190
+		// get resolved to the correct class name
191
+		foreach ($dependencies as $dependency => $load_source) {
192
+			$alias = EE_Dependency_Map::$_instance->getFqnForAlias($dependency);
193
+			if (
194
+				$overwrite === EE_Dependency_Map::OVERWRITE_DEPENDENCIES
195
+				|| ! isset(
196
+					EE_Dependency_Map::$_instance->_dependency_map[ $class ][ $dependency ],
197
+					EE_Dependency_Map::$_instance->_dependency_map[ $class ][ $alias ]
198
+				)
199
+			) {
200
+				unset($dependencies[ $dependency ]);
201
+				$dependencies[ $alias ] = $load_source;
202
+				$registered             = true;
203
+			}
204
+		}
205
+		// now add our two lists of dependencies together.
206
+		// using Union (+=) favours the arrays in precedence from left to right,
207
+		// so $dependencies is NOT overwritten because it is listed first
208
+		// ie: with A = B + C, entries in B take precedence over duplicate entries in C
209
+		// Union is way faster than array_merge() but should be used with caution...
210
+		// especially with numerically indexed arrays
211
+		$dependencies += EE_Dependency_Map::$_instance->_dependency_map[ $class ];
212
+		// now we need to ensure that the resulting dependencies
213
+		// array only has the entries that are required for the class
214
+		// so first count how many dependencies were originally registered for the class
215
+		$dependency_count = count(EE_Dependency_Map::$_instance->_dependency_map[ $class ]);
216
+		// if that count is non-zero (meaning dependencies were already registered)
217
+		EE_Dependency_Map::$_instance->_dependency_map[ $class ] = $dependency_count
218
+			// then truncate the  final array to match that count
219
+			? array_slice($dependencies, 0, $dependency_count)
220
+			// otherwise just take the incoming array because nothing previously existed
221
+			: $dependencies;
222
+		return $registered
223
+			|| count(EE_Dependency_Map::$_instance->_dependency_map[ $class ]) === count($dependencies);
224
+	}
225
+
226
+
227
+	/**
228
+	 * @param string          $class_name
229
+	 * @param callable|string $loader
230
+	 * @param bool            $overwrite
231
+	 * @return bool
232
+	 * @throws DomainException
233
+	 */
234
+	public static function register_class_loader(
235
+		string $class_name,
236
+		$loader = 'load_core',
237
+		bool $overwrite = false
238
+	): bool {
239
+		return EE_Dependency_Map::$_instance->registerClassLoader($class_name, $loader, $overwrite);
240
+	}
241
+
242
+
243
+	/**
244
+	 * @param string         $class_name
245
+	 * @param Closure|string $loader
246
+	 * @param bool           $overwrite
247
+	 * @return bool
248
+	 * @throws DomainException
249
+	 */
250
+	public function registerClassLoader(string $class_name, $loader = 'load_core', bool $overwrite = false): bool
251
+	{
252
+		if (! $loader instanceof Closure && strpos($class_name, '\\') !== false) {
253
+			throw new DomainException(
254
+				esc_html__('Don\'t use class loaders for FQCNs.', 'event_espresso')
255
+			);
256
+		}
257
+		// check that loader is callable or method starts with "load_" and exists in EE_Registry
258
+		if (
259
+			! is_callable($loader)
260
+			&& (
261
+				strpos($loader, 'load_') !== 0
262
+				|| ! method_exists('EE_Registry', $loader)
263
+			)
264
+		) {
265
+			throw new DomainException(
266
+				sprintf(
267
+					esc_html__(
268
+						'"%1$s" is not a valid loader method on EE_Registry.',
269
+						'event_espresso'
270
+					),
271
+					$loader
272
+				)
273
+			);
274
+		}
275
+		$class_name = EE_Dependency_Map::$_instance->getFqnForAlias($class_name);
276
+		if ($overwrite || ! isset(EE_Dependency_Map::$_instance->_class_loaders[ $class_name ])) {
277
+			EE_Dependency_Map::$_instance->_class_loaders[ $class_name ] = $loader;
278
+			return true;
279
+		}
280
+		return false;
281
+	}
282
+
283
+
284
+	/**
285
+	 * @return array
286
+	 */
287
+	public function dependency_map(): array
288
+	{
289
+		return $this->_dependency_map;
290
+	}
291
+
292
+
293
+	/**
294
+	 * returns TRUE if dependency map contains a listing for the provided class name
295
+	 *
296
+	 * @param string $class_name
297
+	 * @return boolean
298
+	 */
299
+	public function has(string $class_name = ''): bool
300
+	{
301
+		// all legacy models have the same dependencies
302
+		if (strpos($class_name, 'EEM_') === 0) {
303
+			$class_name = 'LEGACY_MODELS';
304
+		}
305
+		return isset($this->_dependency_map[ $class_name ]);
306
+	}
307
+
308
+
309
+	/**
310
+	 * returns TRUE if dependency map contains a listing for the provided class name AND dependency
311
+	 *
312
+	 * @param string $class_name
313
+	 * @param string $dependency
314
+	 * @return bool
315
+	 */
316
+	public function has_dependency_for_class(string $class_name = '', string $dependency = ''): bool
317
+	{
318
+		// all legacy models have the same dependencies
319
+		if (strpos($class_name, 'EEM_') === 0) {
320
+			$class_name = 'LEGACY_MODELS';
321
+		}
322
+		$dependency = $this->getFqnForAlias($dependency, $class_name);
323
+		return isset($this->_dependency_map[ $class_name ][ $dependency ]);
324
+	}
325
+
326
+
327
+	/**
328
+	 * returns loading strategy for whether a previously cached dependency should be loaded or a new instance returned
329
+	 *
330
+	 * @param string $class_name
331
+	 * @param string $dependency
332
+	 * @return int
333
+	 */
334
+	public function loading_strategy_for_class_dependency(string $class_name = '', string $dependency = ''): int
335
+	{
336
+		// all legacy models have the same dependencies
337
+		if (strpos($class_name, 'EEM_') === 0) {
338
+			$class_name = 'LEGACY_MODELS';
339
+		}
340
+		$dependency = $this->getFqnForAlias($dependency);
341
+		return $this->has_dependency_for_class($class_name, $dependency)
342
+			? $this->_dependency_map[ $class_name ][ $dependency ]
343
+			: EE_Dependency_Map::not_registered;
344
+	}
345
+
346
+
347
+	/**
348
+	 * @param string $class_name
349
+	 * @return string | Closure
350
+	 */
351
+	public function class_loader(string $class_name)
352
+	{
353
+		// all legacy models use load_model()
354
+		if (strpos($class_name, 'EEM_') === 0) {
355
+			return 'load_model';
356
+		}
357
+		// EE_CPT_*_Strategy classes like EE_CPT_Event_Strategy, EE_CPT_Venue_Strategy, etc
358
+		// perform strpos() first to avoid loading regex every time we load a class
359
+		if (
360
+			strpos($class_name, 'EE_CPT_') === 0
361
+			&& preg_match('/^EE_CPT_([a-zA-Z]+)_Strategy$/', $class_name)
362
+		) {
363
+			return 'load_core';
364
+		}
365
+		$class_name = $this->getFqnForAlias($class_name);
366
+		return $this->_class_loaders[ $class_name ] ?? '';
367
+	}
368
+
369
+
370
+	/**
371
+	 * @return array
372
+	 */
373
+	public function class_loaders(): array
374
+	{
375
+		return $this->_class_loaders;
376
+	}
377
+
378
+
379
+	/**
380
+	 * adds an alias for a classname
381
+	 *
382
+	 * @param string $fqcn      the class name that should be used (concrete class to replace interface)
383
+	 * @param string $alias     the class name that would be type hinted for (abstract parent or interface)
384
+	 * @param string $for_class the class that has the dependency (is type hinting for the interface)
385
+	 * @throws InvalidAliasException
386
+	 */
387
+	public function add_alias(string $fqcn, string $alias, string $for_class = '')
388
+	{
389
+		$this->class_cache->addAlias($fqcn, $alias, $for_class);
390
+	}
391
+
392
+
393
+	/**
394
+	 * Returns TRUE if the provided fully qualified name IS an alias
395
+	 * WHY?
396
+	 * Because if a class is type hinting for a concretion,
397
+	 * then why would we need to find another class to supply it?
398
+	 * ie: if a class asks for `Fully/Qualified/Namespace/SpecificClassName`,
399
+	 * then give it an instance of `Fully/Qualified/Namespace/SpecificClassName`.
400
+	 * Don't go looking for some substitute.
401
+	 * Whereas if a class is type hinting for an interface...
402
+	 * then we need to find an actual class to use.
403
+	 * So the interface IS the alias for some other FQN,
404
+	 * and we need to find out if `Fully/Qualified/Namespace/SomeInterface`
405
+	 * represents some other class.
406
+	 *
407
+	 * @param string $fqn
408
+	 * @param string $for_class
409
+	 * @return bool
410
+	 */
411
+	public function isAlias(string $fqn = '', string $for_class = ''): bool
412
+	{
413
+		return $this->class_cache->isAlias($fqn, $for_class);
414
+	}
415
+
416
+
417
+	/**
418
+	 * Returns a FQN for provided alias if one exists, otherwise returns the original $alias
419
+	 * functions recursively, so that multiple aliases can be used to drill down to a FQN
420
+	 *  for example:
421
+	 *      if the following two entries were added to the _aliases array:
422
+	 *          array(
423
+	 *              'interface_alias'           => 'some\namespace\interface'
424
+	 *              'some\namespace\interface'  => 'some\namespace\classname'
425
+	 *          )
426
+	 *      then one could use EE_Registry::instance()->create( 'interface_alias' )
427
+	 *      to load an instance of 'some\namespace\classname'
428
+	 *
429
+	 * @param string $alias
430
+	 * @param string $for_class
431
+	 * @return string
432
+	 */
433
+	public function getFqnForAlias(string $alias = '', string $for_class = ''): string
434
+	{
435
+		return $this->class_cache->getFqnForAlias($alias, $for_class);
436
+	}
437
+
438
+
439
+	/**
440
+	 * Registers the core dependencies and whether a previously instantiated object should be loaded from the cache,
441
+	 * if one exists, or whether a new object should be generated every time the requested class is loaded.
442
+	 * This is done by using the following class constants:
443
+	 *        EE_Dependency_Map::load_from_cache - loads previously instantiated object
444
+	 *        EE_Dependency_Map::load_new_object - generates a new object every time
445
+	 */
446
+	protected function _register_core_dependencies()
447
+	{
448
+		$this->_dependency_map = [
449
+			'EE_Admin'                                                                                                           => [
450
+				'EventEspresso\core\services\loaders\Loader'  => EE_Dependency_Map::load_from_cache,
451
+				'EventEspresso\core\services\request\Request' => EE_Dependency_Map::load_from_cache,
452
+			],
453
+			'EE_Maintenance_Mode'                                                                                                => [
454
+				'EventEspresso\core\services\loaders\Loader'  => EE_Dependency_Map::load_from_cache,
455
+				'EventEspresso\core\services\request\Request' => EE_Dependency_Map::load_from_cache,
456
+			],
457
+			'EE_Request_Handler'                                                                                                 => [
458
+				'EventEspresso\core\services\request\Request'  => EE_Dependency_Map::load_from_cache,
459
+				'EventEspresso\core\services\request\Response' => EE_Dependency_Map::load_from_cache,
460
+			],
461
+			'EE_System'                                                                                                          => [
462
+				'EventEspresso\core\services\loaders\Loader'  => EE_Dependency_Map::load_from_cache,
463
+				'EE_Maintenance_Mode'                         => EE_Dependency_Map::load_from_cache,
464
+				'EE_Registry'                                 => EE_Dependency_Map::load_from_cache,
465
+				'EventEspresso\core\services\request\Request' => EE_Dependency_Map::load_from_cache,
466
+				'EventEspresso\core\services\routing\Router'  => EE_Dependency_Map::load_from_cache,
467
+			],
468
+			'EE_Session'                                                                                                         => [
469
+				'EventEspresso\core\services\cache\TransientCacheStorage'  => EE_Dependency_Map::load_from_cache,
470
+				'EventEspresso\core\domain\values\session\SessionLifespan' => EE_Dependency_Map::load_from_cache,
471
+				'EventEspresso\core\services\request\Request'              => EE_Dependency_Map::load_from_cache,
472
+				'EventEspresso\core\services\session\SessionStartHandler'  => EE_Dependency_Map::load_from_cache,
473
+				'EventEspresso\core\services\encryption\Base64Encoder'     => EE_Dependency_Map::load_from_cache,
474
+			],
475
+			'EventEspresso\core\services\session\SessionStartHandler'                                                            => [
476
+				'EventEspresso\core\services\request\Request' => EE_Dependency_Map::load_from_cache,
477
+			],
478
+			'EE_Cart'                                                                                                            => [
479
+				'EE_Session' => EE_Dependency_Map::load_from_cache,
480
+			],
481
+			'EE_Messenger_Collection_Loader'                                                                                     => [
482
+				'EE_Messenger_Collection' => EE_Dependency_Map::load_new_object,
483
+			],
484
+			'EE_Message_Type_Collection_Loader'                                                                                  => [
485
+				'EE_Message_Type_Collection' => EE_Dependency_Map::load_new_object,
486
+			],
487
+			'EE_Message_Resource_Manager'                                                                                        => [
488
+				'EE_Messenger_Collection_Loader'    => EE_Dependency_Map::load_new_object,
489
+				'EE_Message_Type_Collection_Loader' => EE_Dependency_Map::load_new_object,
490
+				'EEM_Message_Template_Group'        => EE_Dependency_Map::load_from_cache,
491
+			],
492
+			'EE_Message_Factory'                                                                                                 => [
493
+				'EE_Message_Resource_Manager' => EE_Dependency_Map::load_from_cache,
494
+			],
495
+			'EE_messages'                                                                                                        => [
496
+				'EE_Message_Resource_Manager' => EE_Dependency_Map::load_from_cache,
497
+			],
498
+			'EE_Messages_Generator'                                                                                              => [
499
+				'EE_Messages_Queue'                    => EE_Dependency_Map::load_new_object,
500
+				'EE_Messages_Data_Handler_Collection'  => EE_Dependency_Map::load_new_object,
501
+				'EE_Message_Template_Group_Collection' => EE_Dependency_Map::load_new_object,
502
+				'EEH_Parse_Shortcodes'                 => EE_Dependency_Map::load_from_cache,
503
+			],
504
+			'EE_Messages_Processor'                                                                                              => [
505
+				'EE_Message_Resource_Manager' => EE_Dependency_Map::load_from_cache,
506
+			],
507
+			'EE_Messages_Queue'                                                                                                  => [
508
+				'EE_Message_Repository' => EE_Dependency_Map::load_new_object,
509
+			],
510
+			'EE_Messages_Template_Defaults'                                                                                      => [
511
+				'EEM_Message_Template_Group' => EE_Dependency_Map::load_from_cache,
512
+				'EEM_Message_Template'       => EE_Dependency_Map::load_from_cache,
513
+			],
514
+			'EE_Message_To_Generate_From_Request'                                                                                => [
515
+				'EE_Message_Resource_Manager'                 => EE_Dependency_Map::load_from_cache,
516
+				'EventEspresso\core\services\request\Request' => EE_Dependency_Map::load_from_cache,
517
+			],
518
+			'EventEspresso\core\services\commands\CommandBus'                                                                    => [
519
+				'EventEspresso\core\services\commands\CommandHandlerManager' => EE_Dependency_Map::load_from_cache,
520
+			],
521
+			'EventEspresso\services\commands\CommandHandler'                                                                     => [
522
+				'EE_Registry'         => EE_Dependency_Map::load_from_cache,
523
+				'CommandBusInterface' => EE_Dependency_Map::load_from_cache,
524
+			],
525
+			'EventEspresso\core\services\commands\CommandHandlerManager'                                                         => [
526
+				'EventEspresso\core\services\loaders\Loader' => EE_Dependency_Map::load_from_cache,
527
+			],
528
+			'EventEspresso\core\services\commands\CompositeCommandHandler'                                                       => [
529
+				'EventEspresso\core\services\commands\CommandBus'     => EE_Dependency_Map::load_from_cache,
530
+				'EventEspresso\core\services\commands\CommandFactory' => EE_Dependency_Map::load_from_cache,
531
+			],
532
+			'EventEspresso\core\services\commands\CommandFactory'                                                                => [
533
+				'EventEspresso\core\services\loaders\Loader' => EE_Dependency_Map::load_from_cache,
534
+			],
535
+			'EventEspresso\core\services\commands\middleware\CapChecker'                                                         => [
536
+				'EventEspresso\core\domain\services\capabilities\CapabilitiesChecker' => EE_Dependency_Map::load_from_cache,
537
+			],
538
+			'EventEspresso\core\domain\services\capabilities\CapabilitiesChecker'                                                => [
539
+				'EE_Capabilities' => EE_Dependency_Map::load_from_cache,
540
+			],
541
+			'EventEspresso\core\domain\services\capabilities\RegistrationsCapChecker'                                            => [
542
+				'EE_Capabilities' => EE_Dependency_Map::load_from_cache,
543
+			],
544
+			'EventEspresso\core\domain\services\commands\registration\CreateRegistrationCommandHandler'                          => [
545
+				'EventEspresso\core\domain\services\registration\CreateRegistrationService' => EE_Dependency_Map::load_from_cache,
546
+			],
547
+			'EventEspresso\core\domain\services\commands\registration\CopyRegistrationDetailsCommandHandler'                     => [
548
+				'EventEspresso\core\domain\services\registration\CopyRegistrationService' => EE_Dependency_Map::load_from_cache,
549
+			],
550
+			'EventEspresso\core\domain\services\commands\registration\CopyRegistrationPaymentsCommandHandler'                    => [
551
+				'EventEspresso\core\domain\services\registration\CopyRegistrationService' => EE_Dependency_Map::load_from_cache,
552
+			],
553
+			'EventEspresso\core\domain\services\commands\registration\CancelRegistrationAndTicketLineItemCommandHandler'         => [
554
+				'EventEspresso\core\domain\services\registration\CancelTicketLineItemService' => EE_Dependency_Map::load_from_cache,
555
+			],
556
+			'EventEspresso\core\domain\services\commands\registration\UpdateRegistrationAndTransactionAfterChangeCommandHandler' => [
557
+				'EventEspresso\core\domain\services\registration\UpdateRegistrationService' => EE_Dependency_Map::load_from_cache,
558
+			],
559
+			'EventEspresso\core\domain\services\commands\ticket\CreateTicketLineItemCommandHandler'                              => [
560
+				'EventEspresso\core\domain\services\ticket\CreateTicketLineItemService' => EE_Dependency_Map::load_from_cache,
561
+			],
562
+			'EventEspresso\core\domain\services\commands\ticket\CancelTicketLineItemCommandHandler'                              => [
563
+				'EventEspresso\core\domain\services\ticket\CancelTicketLineItemService' => EE_Dependency_Map::load_from_cache,
564
+			],
565
+			'EventEspresso\core\domain\services\registration\CancelRegistrationService'                                          => [
566
+				'EventEspresso\core\domain\services\ticket\CancelTicketLineItemService' => EE_Dependency_Map::load_from_cache,
567
+			],
568
+			'EventEspresso\core\domain\services\commands\attendee\CreateAttendeeCommandHandler'                                  => [
569
+				'EEM_Attendee' => EE_Dependency_Map::load_from_cache,
570
+			],
571
+			'EventEspresso\core\domain\values\session\SessionLifespan'                                                           => [
572
+				'EventEspresso\core\domain\values\session\SessionLifespanOption' => EE_Dependency_Map::load_from_cache,
573
+			],
574
+			'EventEspresso\caffeinated\admin\extend\registration_form\forms\SessionLifespanForm'                                 => [
575
+				'EventEspresso\core\domain\values\session\SessionLifespanOption' => EE_Dependency_Map::load_from_cache,
576
+			],
577
+			'EventEspresso\caffeinated\admin\extend\registration_form\forms\SessionLifespanFormHandler'                          => [
578
+				'EventEspresso\core\domain\values\session\SessionLifespanOption' => EE_Dependency_Map::load_from_cache,
579
+			],
580
+			'EventEspresso\core\services\database\TableManager'                                                                  => [
581
+				'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
582
+			],
583
+			'EE_Data_Migration_Class_Base'                                                                                       => [
584
+				'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
585
+				'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
586
+			],
587
+			'EE_DMS_Core_4_1_0'                                                                                                  => [
588
+				'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
589
+				'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
590
+			],
591
+			'EE_DMS_Core_4_2_0'                                                                                                  => [
592
+				'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
593
+				'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
594
+			],
595
+			'EE_DMS_Core_4_3_0'                                                                                                  => [
596
+				'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
597
+				'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
598
+			],
599
+			'EE_DMS_Core_4_4_0'                                                                                                  => [
600
+				'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
601
+				'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
602
+			],
603
+			'EE_DMS_Core_4_5_0'                                                                                                  => [
604
+				'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
605
+				'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
606
+			],
607
+			'EE_DMS_Core_4_6_0'                                                                                                  => [
608
+				'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
609
+				'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
610
+			],
611
+			'EE_DMS_Core_4_7_0'                                                                                                  => [
612
+				'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
613
+				'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
614
+			],
615
+			'EE_DMS_Core_4_8_0'                                                                                                  => [
616
+				'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
617
+				'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
618
+			],
619
+			'EE_DMS_Core_4_9_0'                                                                                                  => [
620
+				'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
621
+				'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
622
+			],
623
+			'EE_DMS_Core_4_10_0'                                                                                                 => [
624
+				'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
625
+				'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
626
+				'EE_DMS_Core_4_9_0'                                  => EE_Dependency_Map::load_from_cache,
627
+			],
628
+			'EE_DMS_Core_5_0_0'                                                                                                  => [
629
+				'EE_DMS_Core_4_10_0'                                 => EE_Dependency_Map::load_from_cache,
630
+				'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
631
+				'EventEspresso\core\services\database\TableManager'  => EE_Dependency_Map::load_from_cache,
632
+			],
633
+			'EventEspresso\core\services\assets\I18nRegistry'                                                                    => [
634
+				'EventEspresso\core\domain\Domain' => EE_Dependency_Map::load_from_cache,
635
+			],
636
+			'EventEspresso\core\services\assets\Registry'                                                                        => [
637
+				'EventEspresso\core\services\assets\AssetCollection' => EE_Dependency_Map::load_from_cache,
638
+				'EventEspresso\core\services\assets\AssetManifest'   => EE_Dependency_Map::load_from_cache,
639
+			],
640
+			'EventEspresso\core\domain\entities\shortcodes\EspressoCancelled'                                                    => [
641
+				'EventEspresso\core\services\cache\PostRelatedCacheManager' => EE_Dependency_Map::load_from_cache,
642
+			],
643
+			'EventEspresso\core\domain\entities\shortcodes\EspressoCheckout'                                                     => [
644
+				'EventEspresso\core\services\cache\PostRelatedCacheManager' => EE_Dependency_Map::load_from_cache,
645
+			],
646
+			'EventEspresso\core\domain\entities\shortcodes\EspressoEventAttendees'                                               => [
647
+				'EventEspresso\core\services\cache\PostRelatedCacheManager' => EE_Dependency_Map::load_from_cache,
648
+			],
649
+			'EventEspresso\core\domain\entities\shortcodes\EspressoEvents'                                                       => [
650
+				'EventEspresso\core\services\cache\PostRelatedCacheManager' => EE_Dependency_Map::load_from_cache,
651
+			],
652
+			'EventEspresso\core\domain\entities\shortcodes\EspressoThankYou'                                                     => [
653
+				'EventEspresso\core\services\cache\PostRelatedCacheManager' => EE_Dependency_Map::load_from_cache,
654
+			],
655
+			'EventEspresso\core\domain\entities\shortcodes\EspressoTicketSelector'                                               => [
656
+				'EventEspresso\core\services\cache\PostRelatedCacheManager' => EE_Dependency_Map::load_from_cache,
657
+			],
658
+			'EventEspresso\core\domain\entities\shortcodes\EspressoTxnPage'                                                      => [
659
+				'EventEspresso\core\services\cache\PostRelatedCacheManager' => EE_Dependency_Map::load_from_cache,
660
+			],
661
+			'EventEspresso\core\services\cache\BasicCacheManager'                                                                => [
662
+				'EventEspresso\core\services\cache\TransientCacheStorage' => EE_Dependency_Map::load_from_cache,
663
+			],
664
+			'EventEspresso\core\services\cache\PostRelatedCacheManager'                                                          => [
665
+				'EventEspresso\core\services\cache\TransientCacheStorage' => EE_Dependency_Map::load_from_cache,
666
+			],
667
+			'EventEspresso\core\domain\services\validation\email\EmailValidationService'                                         => [
668
+				'EE_Registration_Config'                     => EE_Dependency_Map::load_from_cache,
669
+				'EventEspresso\core\services\loaders\Loader' => EE_Dependency_Map::load_from_cache,
670
+			],
671
+			'EventEspresso\core\domain\values\EmailAddress'                                                                      => [
672
+				null,
673
+				'EventEspresso\core\domain\services\validation\email\EmailValidationService' => EE_Dependency_Map::load_from_cache,
674
+			],
675
+			'EventEspresso\core\services\orm\ModelFieldFactory'                                                                  => [
676
+				'EventEspresso\core\services\loaders\Loader' => EE_Dependency_Map::load_from_cache,
677
+			],
678
+			'LEGACY_MODELS'                                                                                                      => [
679
+				null,
680
+				'EventEspresso\core\services\database\ModelFieldFactory' => EE_Dependency_Map::load_from_cache,
681
+			],
682
+			'EE_Module_Request_Router'                                                                                           => [
683
+				'EventEspresso\core\services\request\Request' => EE_Dependency_Map::load_from_cache,
684
+			],
685
+			'EE_Registration_Processor'                                                                                          => [
686
+				'EventEspresso\core\services\request\Request' => EE_Dependency_Map::load_from_cache,
687
+			],
688
+			'EventEspresso\core\services\notifications\PersistentAdminNoticeManager'                                             => [
689
+				'EventEspresso\core\domain\services\capabilities\CapabilitiesChecker' => EE_Dependency_Map::load_from_cache,
690
+				'EventEspresso\core\services\request\Request'                         => EE_Dependency_Map::load_from_cache,
691
+			],
692
+			'EventEspresso\caffeinated\modules\recaptcha_invisible\InvisibleRecaptcha'                                           => [
693
+				'EE_Registration_Config' => EE_Dependency_Map::load_from_cache,
694
+				'EE_Session'             => EE_Dependency_Map::load_from_cache,
695
+			],
696
+			'EventEspresso\modules\ticket_selector\DisplayTicketSelector'                                                        => [
697
+				'EventEspresso\core\domain\entities\users\CurrentUser' => EE_Dependency_Map::load_from_cache,
698
+				'EventEspresso\core\services\request\Request'          => EE_Dependency_Map::load_from_cache,
699
+				'EE_Ticket_Selector_Config'                            => EE_Dependency_Map::load_from_cache,
700
+			],
701
+			'EventEspresso\modules\ticket_selector\ProcessTicketSelector'                                                        => [
702
+				'EE_Core_Config'                                                          => EE_Dependency_Map::load_from_cache,
703
+				'EventEspresso\core\services\request\Request'                             => EE_Dependency_Map::load_from_cache,
704
+				'EE_Session'                                                              => EE_Dependency_Map::load_from_cache,
705
+				'EEM_Ticket'                                                              => EE_Dependency_Map::load_from_cache,
706
+				'EventEspresso\modules\ticket_selector\TicketDatetimeAvailabilityTracker' => EE_Dependency_Map::load_from_cache,
707
+			],
708
+			'EventEspresso\modules\ticket_selector\ProcessTicketSelectorPostData'                                                => [
709
+				'EventEspresso\core\services\request\Request' => EE_Dependency_Map::load_from_cache,
710
+				'EEM_Event'                                   => EE_Dependency_Map::load_from_cache,
711
+			],
712
+			'EventEspresso\modules\ticket_selector\TicketDatetimeAvailabilityTracker'                                            => [
713
+				'EEM_Datetime' => EE_Dependency_Map::load_from_cache,
714
+			],
715
+			'EventEspresso\core\domain\entities\custom_post_types\CustomPostTypeDefinitions'                                     => [
716
+				'EE_Core_Config'                             => EE_Dependency_Map::load_from_cache,
717
+				'EventEspresso\core\services\loaders\Loader' => EE_Dependency_Map::load_from_cache,
718
+			],
719
+			'EventEspresso\core\domain\services\custom_post_types\RegisterCustomPostTypes'                                       => [
720
+				'EventEspresso\core\domain\entities\custom_post_types\CustomPostTypeDefinitions' => EE_Dependency_Map::load_from_cache,
721
+			],
722
+			'EventEspresso\core\domain\services\custom_post_types\RegisterCustomTaxonomies'                                      => [
723
+				'EventEspresso\core\domain\entities\custom_post_types\CustomTaxonomyDefinitions' => EE_Dependency_Map::load_from_cache,
724
+			],
725
+			'EE_CPT_Strategy'                                                                                                    => [
726
+				'EventEspresso\core\domain\entities\custom_post_types\CustomPostTypeDefinitions' => EE_Dependency_Map::load_from_cache,
727
+				'EventEspresso\core\domain\entities\custom_post_types\CustomTaxonomyDefinitions' => EE_Dependency_Map::load_from_cache,
728
+			],
729
+			'EventEspresso\core\services\loaders\ObjectIdentifier'                                                               => [
730
+				'EventEspresso\core\services\loaders\ClassInterfaceCache' => EE_Dependency_Map::load_from_cache,
731
+			],
732
+			'EventEspresso\core\CPTs\CptQueryModifier'                                                                           => [
733
+				null,
734
+				null,
735
+				null,
736
+				'EventEspresso\core\services\request\CurrentPage' => EE_Dependency_Map::load_from_cache,
737
+				'EventEspresso\core\services\request\Request'     => EE_Dependency_Map::load_from_cache,
738
+				'EventEspresso\core\services\loaders\Loader'      => EE_Dependency_Map::load_from_cache,
739
+			],
740
+			'EventEspresso\core\services\dependencies\DependencyResolver'                                                        => [
741
+				'EventEspresso\core\services\container\Mirror'            => EE_Dependency_Map::load_from_cache,
742
+				'EventEspresso\core\services\loaders\ClassInterfaceCache' => EE_Dependency_Map::load_from_cache,
743
+				'EE_Dependency_Map'                                       => EE_Dependency_Map::load_from_cache,
744
+			],
745
+			'EventEspresso\core\services\routing\RouteMatchSpecificationDependencyResolver'                                      => [
746
+				'EventEspresso\core\services\container\Mirror'            => EE_Dependency_Map::load_from_cache,
747
+				'EventEspresso\core\services\loaders\ClassInterfaceCache' => EE_Dependency_Map::load_from_cache,
748
+				'EE_Dependency_Map'                                       => EE_Dependency_Map::load_from_cache,
749
+			],
750
+			'EventEspresso\core\services\routing\RouteMatchSpecificationFactory'                                                 => [
751
+				'EventEspresso\core\services\routing\RouteMatchSpecificationDependencyResolver' => EE_Dependency_Map::load_from_cache,
752
+				'EventEspresso\core\services\loaders\Loader'                                    => EE_Dependency_Map::load_from_cache,
753
+			],
754
+			'EventEspresso\core\services\routing\RouteMatchSpecificationManager'                                                 => [
755
+				'EventEspresso\core\services\routing\RouteMatchSpecificationCollection' => EE_Dependency_Map::load_from_cache,
756
+				'EventEspresso\core\services\routing\RouteMatchSpecificationFactory'    => EE_Dependency_Map::load_from_cache,
757
+			],
758
+			'EventEspresso\core\services\request\files\FilesDataHandler'                                                         => [
759
+				'EventEspresso\core\services\request\Request' => EE_Dependency_Map::load_from_cache,
760
+			],
761
+			'EventEspresso\core\libraries\batch\BatchRequestProcessor'                                                           => [
762
+				'EventEspresso\core\services\loaders\Loader'  => EE_Dependency_Map::load_from_cache,
763
+				'EventEspresso\core\services\request\Request' => EE_Dependency_Map::load_from_cache,
764
+			],
765
+			'EventEspresso\core\domain\services\converters\RestApiSpoofer'                                                       => [
766
+				'WP_REST_Server'                                               => EE_Dependency_Map::load_from_cache,
767
+				'EED_Core_Rest_Api'                                            => EE_Dependency_Map::load_from_cache,
768
+				'EventEspresso\core\libraries\rest_api\controllers\model\Read' => EE_Dependency_Map::load_from_cache,
769
+				null,
770
+			],
771
+			'EventEspresso\core\services\routing\RouteHandler'                                                                   => [
772
+				'EventEspresso\core\domain\services\capabilities\CapabilitiesChecker' => EE_Dependency_Map::load_from_cache,
773
+				'EventEspresso\core\services\json\JsonDataNodeHandler'                => EE_Dependency_Map::load_from_cache,
774
+				'EventEspresso\core\services\loaders\Loader'                          => EE_Dependency_Map::load_from_cache,
775
+				'EventEspresso\core\services\request\Request'                         => EE_Dependency_Map::load_from_cache,
776
+				'EventEspresso\core\services\routing\RouteCollection'                 => EE_Dependency_Map::load_from_cache,
777
+			],
778
+			'EventEspresso\core\services\json\JsonDataNodeHandler'                                                               => [
779
+				'EventEspresso\core\services\json\JsonDataNodeValidator' => EE_Dependency_Map::load_from_cache,
780
+			],
781
+			'EventEspresso\core\services\routing\Router'                                                                         => [
782
+				'EE_Dependency_Map'                                => EE_Dependency_Map::load_from_cache,
783
+				'EventEspresso\core\services\loaders\Loader'       => EE_Dependency_Map::load_from_cache,
784
+				'EventEspresso\core\services\routing\RouteHandler' => EE_Dependency_Map::load_from_cache,
785
+			],
786
+			'EventEspresso\core\services\assets\AssetManifest'                                                                   => [
787
+				'EventEspresso\core\domain\Domain' => EE_Dependency_Map::load_from_cache,
788
+			],
789
+			'EventEspresso\core\services\assets\AssetManifestFactory'                                                            => [
790
+				'EventEspresso\core\services\loaders\Loader' => EE_Dependency_Map::load_from_cache,
791
+			],
792
+			'EventEspresso\core\services\assets\BaristaFactory'                                                                  => [
793
+				'EventEspresso\core\services\assets\AssetManifestFactory' => EE_Dependency_Map::load_from_cache,
794
+				'EventEspresso\core\services\loaders\Loader'              => EE_Dependency_Map::load_from_cache,
795
+			],
796
+			'EventEspresso\core\domain\services\capabilities\FeatureFlagsConfig' => [
797
+				'EventEspresso\core\domain\Domain'                                    => EE_Dependency_Map::load_from_cache,
798
+				'EventEspresso\core\services\json\JsonDataHandler'                    => EE_Dependency_Map::load_from_cache,
799
+			],
800
+			'EventEspresso\core\domain\services\capabilities\FeatureFlags'       => [
801
+				'EventEspresso\core\domain\services\capabilities\CapabilitiesChecker' => EE_Dependency_Map::load_from_cache,
802
+				'EventEspresso\core\domain\services\capabilities\FeatureFlagsConfig'  => EE_Dependency_Map::load_from_cache,
803
+			],
804
+			'EventEspresso\core\services\addon\AddonManager'                                                                     => [
805
+				'EventEspresso\core\services\addon\AddonCollection'              => EE_Dependency_Map::load_from_cache,
806
+				'EventEspresso\core\Psr4Autoloader'                              => EE_Dependency_Map::load_from_cache,
807
+				'EventEspresso\core\services\addon\api\v1\RegisterAddon'         => EE_Dependency_Map::load_from_cache,
808
+				'EventEspresso\core\services\addon\api\IncompatibleAddonHandler' => EE_Dependency_Map::load_from_cache,
809
+				'EventEspresso\core\services\addon\api\ThirdPartyPluginHandler'  => EE_Dependency_Map::load_from_cache,
810
+			],
811
+			'EventEspresso\core\services\addon\api\ThirdPartyPluginHandler'                                                      => [
812
+				'EventEspresso\core\services\request\Request' => EE_Dependency_Map::load_from_cache,
813
+			],
814
+			'EventEspresso\core\libraries\batch\JobHandlers\ExecuteBatchDeletion'                                                => [
815
+				'EventEspresso\core\services\orm\tree_traversal\NodeGroupDao' => EE_Dependency_Map::load_from_cache,
816
+			],
817
+			'EventEspresso\core\libraries\batch\JobHandlers\PreviewEventDeletion'                                                => [
818
+				'EventEspresso\core\services\orm\tree_traversal\NodeGroupDao' => EE_Dependency_Map::load_from_cache,
819
+			],
820
+			'EventEspresso\core\domain\services\admin\events\data\PreviewDeletion'                                               => [
821
+				'EventEspresso\core\services\orm\tree_traversal\NodeGroupDao' => EE_Dependency_Map::load_from_cache,
822
+				'EEM_Event'                                                   => EE_Dependency_Map::load_from_cache,
823
+				'EEM_Datetime'                                                => EE_Dependency_Map::load_from_cache,
824
+				'EEM_Registration'                                            => EE_Dependency_Map::load_from_cache,
825
+			],
826
+			'EventEspresso\core\domain\services\admin\events\data\ConfirmDeletion'                                               => [
827
+				'EventEspresso\core\services\orm\tree_traversal\NodeGroupDao' => EE_Dependency_Map::load_from_cache,
828
+			],
829
+			'EventEspresso\core\services\request\CurrentPage'                                                                    => [
830
+				'EE_CPT_Strategy'                             => EE_Dependency_Map::load_from_cache,
831
+				'EventEspresso\core\services\request\Request' => EE_Dependency_Map::load_from_cache,
832
+			],
833
+			'EventEspresso\core\services\shortcodes\LegacyShortcodesManager'                                                     => [
834
+				'EE_Registry'                                     => EE_Dependency_Map::load_from_cache,
835
+				'EventEspresso\core\services\request\CurrentPage' => EE_Dependency_Map::load_from_cache,
836
+			],
837
+			'EventEspresso\core\services\shortcodes\ShortcodesManager'                                                           => [
838
+				'EventEspresso\core\services\shortcodes\LegacyShortcodesManager' => EE_Dependency_Map::load_from_cache,
839
+				'EventEspresso\core\services\request\CurrentPage'                => EE_Dependency_Map::load_from_cache,
840
+			],
841
+			'EventEspresso\core\domain\entities\users\CurrentUser'                                                               => [
842
+				'EventEspresso\core\domain\entities\users\EventManagers' => EE_Dependency_Map::load_from_cache,
843
+			],
844
+			'EventEspresso\core\services\form\meta\InputTypes'                                                                   => [
845
+				'EventEspresso\core\services\form\meta\inputs\Block'    => EE_Dependency_Map::load_from_cache,
846
+				'EventEspresso\core\services\form\meta\inputs\Button'   => EE_Dependency_Map::load_from_cache,
847
+				'EventEspresso\core\services\form\meta\inputs\DateTime' => EE_Dependency_Map::load_from_cache,
848
+				'EventEspresso\core\services\form\meta\inputs\Input'    => EE_Dependency_Map::load_from_cache,
849
+				'EventEspresso\core\services\form\meta\inputs\Number'   => EE_Dependency_Map::load_from_cache,
850
+				'EventEspresso\core\services\form\meta\inputs\Phone'    => EE_Dependency_Map::load_from_cache,
851
+				'EventEspresso\core\services\form\meta\inputs\Select'   => EE_Dependency_Map::load_from_cache,
852
+				'EventEspresso\core\services\form\meta\inputs\Text'     => EE_Dependency_Map::load_from_cache,
853
+			],
854
+			'EventEspresso\core\domain\services\registration\form\v1\RegFormDependencyHandler'                                   => [
855
+				'EE_Dependency_Map' => EE_Dependency_Map::load_from_cache,
856
+			],
857
+			'EventEspresso\core\services\calculators\LineItemCalculator'                                                         => [
858
+				'EventEspresso\core\services\helpers\DecimalValues' => EE_Dependency_Map::load_from_cache,
859
+			],
860
+			'EventEspresso\core\services\helpers\DecimalValues'                                                                  => [
861
+				'EE_Currency_Config' => EE_Dependency_Map::load_from_cache,
862
+			],
863
+			'EE_Brewing_Regular'                                                                                                 => [
864
+				'EE_Dependency_Map'                                  => EE_Dependency_Map::load_from_cache,
865
+				'EventEspresso\core\services\loaders\Loader'         => EE_Dependency_Map::load_from_cache,
866
+				'EventEspresso\core\services\routing\RouteHandler'   => EE_Dependency_Map::load_from_cache,
867
+				'EventEspresso\core\services\database\TableAnalysis' => EE_Dependency_Map::load_from_cache,
868
+			],
869
+			'EventEspresso\core\domain\services\messages\MessageTemplateRequestData'                                             => [
870
+				'EventEspresso\core\services\request\Request' => EE_Dependency_Map::load_from_cache,
871
+			],
872
+			'EventEspresso\core\domain\services\messages\MessageTemplateValidator'                                               => [
873
+				'EventEspresso\core\services\loaders\Loader' => EE_Dependency_Map::load_from_cache,
874
+			],
875
+			'EventEspresso\core\domain\services\messages\MessageTemplateManager'                                                 => [
876
+				'EEM_Message_Template'                                                   => EE_Dependency_Map::load_from_cache,
877
+				'EEM_Message_Template_Group'                                             => EE_Dependency_Map::load_from_cache,
878
+				'EventEspresso\core\domain\services\messages\MessageTemplateRequestData' => EE_Dependency_Map::load_from_cache,
879
+				'EventEspresso\core\domain\services\messages\MessageTemplateValidator'   => EE_Dependency_Map::load_from_cache,
880
+				'EventEspresso\core\services\request\Request'                            => EE_Dependency_Map::load_from_cache,
881
+			],
882
+			'EventEspresso\core\services\request\sanitizers\RequestSanitizer'                                                    => [
883
+				'EventEspresso\core\domain\services\validation\email\strategies\Basic' => EE_Dependency_Map::load_from_cache,
884
+			],
885
+			'EE_CPT_Event_Strategy'                                                    => [
886
+				null,
887
+				null,
888
+				'EE_Template_Config' => EE_Dependency_Map::load_from_cache,
889
+			],
890
+		];
891
+	}
892
+
893
+
894
+	/**
895
+	 * Registers how core classes are loaded.
896
+	 * This can either be done by simply providing the name of one of the EE_Registry loader methods such as:
897
+	 *        'EE_Request_Handler' => 'load_core'
898
+	 *        'EE_Messages_Queue'  => 'load_lib'
899
+	 *        'EEH_Debug_Tools'    => 'load_helper'
900
+	 * or, if greater control is required, by providing a custom closure. For example:
901
+	 *        'Some_Class' => function () {
902
+	 *            return new Some_Class();
903
+	 *        },
904
+	 * This is required for instantiating dependencies
905
+	 * where an interface has been type hinted in a class constructor. For example:
906
+	 *        'Required_Interface' => function () {
907
+	 *            return new A_Class_That_Implements_Required_Interface();
908
+	 *        },
909
+	 */
910
+	protected function _register_core_class_loaders()
911
+	{
912
+		$this->_class_loaders = [
913
+			// load_core
914
+			'EE_Dependency_Map'                            => function () {
915
+				return $this;
916
+			},
917
+			'EE_Capabilities'                              => 'load_core',
918
+			'EE_Encryption'                                => 'load_core',
919
+			'EE_Front_Controller'                          => 'load_core',
920
+			'EE_Module_Request_Router'                     => 'load_core',
921
+			'EE_Registry'                                  => 'load_core',
922
+			'EE_Request'                                   => function () {
923
+				return $this->legacy_request;
924
+			},
925
+			'EventEspresso\core\services\request\Request'  => function () {
926
+				return $this->request;
927
+			},
928
+			'EventEspresso\core\services\request\Response' => function () {
929
+				return $this->response;
930
+			},
931
+			'EE_Base'                                      => 'load_core',
932
+			'EE_Request_Handler'                           => 'load_core',
933
+			'EE_Session'                                   => 'load_core',
934
+			'EE_Cron_Tasks'                                => 'load_core',
935
+			'EE_System'                                    => 'load_core',
936
+			'EE_Maintenance_Mode'                          => 'load_core',
937
+			'EE_Register_CPTs'                             => 'load_core',
938
+			'EE_Admin'                                     => 'load_core',
939
+			'EE_CPT_Strategy'                              => 'load_core',
940
+			// load_class
941
+			'EE_Registration_Processor'                    => 'load_class',
942
+			'EE_Transaction_Payments'                      => 'load_class',
943
+			'EE_Transaction_Processor'                     => 'load_class',
944
+			// load_lib
945
+			'EE_Message_Resource_Manager'                  => 'load_lib',
946
+			'EE_Message_Type_Collection'                   => 'load_lib',
947
+			'EE_Message_Type_Collection_Loader'            => 'load_lib',
948
+			'EE_Messenger_Collection'                      => 'load_lib',
949
+			'EE_Messenger_Collection_Loader'               => 'load_lib',
950
+			'EE_Messages_Processor'                        => 'load_lib',
951
+			'EE_Message_Repository'                        => 'load_lib',
952
+			'EE_Messages_Queue'                            => 'load_lib',
953
+			'EE_Messages_Data_Handler_Collection'          => 'load_lib',
954
+			'EE_Message_Template_Group_Collection'         => 'load_lib',
955
+			'EE_Payment_Method_Manager'                    => 'load_lib',
956
+			'EE_Payment_Processor'                         => 'load_core',
957
+			'EE_DMS_Core_4_1_0'                            => 'load_dms',
958
+			'EE_DMS_Core_4_2_0'                            => 'load_dms',
959
+			'EE_DMS_Core_4_3_0'                            => 'load_dms',
960
+			'EE_DMS_Core_4_5_0'                            => 'load_dms',
961
+			'EE_DMS_Core_4_6_0'                            => 'load_dms',
962
+			'EE_DMS_Core_4_7_0'                            => 'load_dms',
963
+			'EE_DMS_Core_4_8_0'                            => 'load_dms',
964
+			'EE_DMS_Core_4_9_0'                            => 'load_dms',
965
+			'EE_DMS_Core_4_10_0'                           => 'load_dms',
966
+			'EE_DMS_Core_5_0_0'                            => 'load_dms',
967
+			'EE_Messages_Generator'                        => static function () {
968
+				return EE_Registry::instance()->load_lib(
969
+					'Messages_Generator',
970
+					[],
971
+					false,
972
+					false
973
+				);
974
+			},
975
+			'EE_Messages_Template_Defaults'                => static function ($arguments = []) {
976
+				return EE_Registry::instance()->load_lib(
977
+					'Messages_Template_Defaults',
978
+					$arguments,
979
+					false,
980
+					false
981
+				);
982
+			},
983
+			// load_helper
984
+			'EEH_Parse_Shortcodes'                         => static function () {
985
+				if (EE_Registry::instance()->load_helper('Parse_Shortcodes')) {
986
+					return new EEH_Parse_Shortcodes();
987
+				}
988
+				return null;
989
+			},
990
+			'EE_Template_Config'                           => static function () {
991
+				return EE_Config::instance()->template_settings;
992
+			},
993
+			'EE_Currency_Config'                           => static function () {
994
+				return EE_Currency_Config::getCurrencyConfig();
995
+			},
996
+			'EE_Registration_Config'                       => static function () {
997
+				return EE_Config::instance()->registration;
998
+			},
999
+			'EE_Core_Config'                               => static function () {
1000
+				return EE_Config::instance()->core;
1001
+			},
1002
+			'EventEspresso\core\services\loaders\Loader'   => static function () {
1003
+				return LoaderFactory::getLoader();
1004
+			},
1005
+			'EE_Network_Config'                            => static function () {
1006
+				return EE_Network_Config::instance();
1007
+			},
1008
+			'EE_Config'                                    => static function () {
1009
+				return EE_Config::instance();
1010
+			},
1011
+			'EventEspresso\core\domain\Domain'             => static function () {
1012
+				return DomainFactory::getEventEspressoCoreDomain();
1013
+			},
1014
+			'EE_Admin_Config'                              => static function () {
1015
+				return EE_Config::instance()->admin;
1016
+			},
1017
+			'EE_Organization_Config'                       => static function () {
1018
+				return EE_Config::instance()->organization;
1019
+			},
1020
+			'EE_Network_Core_Config'                       => static function () {
1021
+				return EE_Network_Config::instance()->core;
1022
+			},
1023
+			'EE_Environment_Config'                        => static function () {
1024
+				return EE_Config::instance()->environment;
1025
+			},
1026
+			'EED_Core_Rest_Api'                            => static function () {
1027
+				return EED_Core_Rest_Api::instance();
1028
+			},
1029
+			'WP_REST_Server'                               => static function () {
1030
+				return rest_get_server();
1031
+			},
1032
+			'EventEspresso\core\Psr4Autoloader'            => static function () {
1033
+				return EE_Psr4AutoloaderInit::psr4_loader();
1034
+			},
1035
+			'EE_Ticket_Selector_Config'                    => function () {
1036
+				return EE_Config::instance()->template_settings->EED_Ticket_Selector;
1037
+			},
1038
+		];
1039
+	}
1040
+
1041
+
1042
+	/**
1043
+	 * can be used for supplying alternate names for classes,
1044
+	 * or for connecting interface names to instantiable classes
1045
+	 *
1046
+	 * @throws InvalidAliasException
1047
+	 */
1048
+	protected function _register_core_aliases()
1049
+	{
1050
+		$aliases = [
1051
+			'CommandBusInterface'                                                          => 'EventEspresso\core\services\commands\CommandBusInterface',
1052
+			'EventEspresso\core\services\commands\CommandBusInterface'                     => 'EventEspresso\core\services\commands\CommandBus',
1053
+			'CommandHandlerManagerInterface'                                               => 'EventEspresso\core\services\commands\CommandHandlerManagerInterface',
1054
+			'EventEspresso\core\services\commands\CommandHandlerManagerInterface'          => 'EventEspresso\core\services\commands\CommandHandlerManager',
1055
+			'CapChecker'                                                                   => 'EventEspresso\core\services\commands\middleware\CapChecker',
1056
+			'AddActionHook'                                                                => 'EventEspresso\core\services\commands\middleware\AddActionHook',
1057
+			'CapabilitiesChecker'                                                          => 'EventEspresso\core\domain\services\capabilities\CapabilitiesChecker',
1058
+			'CapabilitiesCheckerInterface'                                                 => 'EventEspresso\core\domain\services\capabilities\CapabilitiesCheckerInterface',
1059
+			'EventEspresso\core\domain\services\capabilities\CapabilitiesCheckerInterface' => 'EventEspresso\core\domain\services\capabilities\CapabilitiesChecker',
1060
+			'CreateRegistrationService'                                                    => 'EventEspresso\core\domain\services\registration\CreateRegistrationService',
1061
+			'CreateRegistrationCommandHandler'                                             => 'EventEspresso\core\domain\services\commands\registration\CreateRegistrationCommand',
1062
+			'CopyRegistrationDetailsCommandHandler'                                        => 'EventEspresso\core\domain\services\commands\registration\CopyRegistrationDetailsCommand',
1063
+			'CopyRegistrationPaymentsCommandHandler'                                       => 'EventEspresso\core\domain\services\commands\registration\CopyRegistrationPaymentsCommand',
1064
+			'CancelRegistrationAndTicketLineItemCommandHandler'                            => 'EventEspresso\core\domain\services\commands\registration\CancelRegistrationAndTicketLineItemCommandHandler',
1065
+			'UpdateRegistrationAndTransactionAfterChangeCommandHandler'                    => 'EventEspresso\core\domain\services\commands\registration\UpdateRegistrationAndTransactionAfterChangeCommandHandler',
1066
+			'CreateTicketLineItemCommandHandler'                                           => 'EventEspresso\core\domain\services\commands\ticket\CreateTicketLineItemCommand',
1067
+			'CreateTransactionCommandHandler'                                              => 'EventEspresso\core\domain\services\commands\transaction\CreateTransactionCommandHandler',
1068
+			'CreateAttendeeCommandHandler'                                                 => 'EventEspresso\core\domain\services\commands\attendee\CreateAttendeeCommandHandler',
1069
+			'TableManager'                                                                 => 'EventEspresso\core\services\database\TableManager',
1070
+			'TableAnalysis'                                                                => 'EventEspresso\core\services\database\TableAnalysis',
1071
+			'EspressoShortcode'                                                            => 'EventEspresso\core\services\shortcodes\EspressoShortcode',
1072
+			'ShortcodeInterface'                                                           => 'EventEspresso\core\services\shortcodes\ShortcodeInterface',
1073
+			'EventEspresso\core\services\shortcodes\ShortcodeInterface'                    => 'EventEspresso\core\services\shortcodes\EspressoShortcode',
1074
+			'EventEspresso\core\services\cache\CacheStorageInterface'                      => 'EventEspresso\core\services\cache\TransientCacheStorage',
1075
+			'LoaderInterface'                                                              => 'EventEspresso\core\services\loaders\LoaderInterface',
1076
+			'EventEspresso\core\services\loaders\LoaderInterface'                          => 'EventEspresso\core\services\loaders\Loader',
1077
+			'CommandFactoryInterface'                                                      => 'EventEspresso\core\services\commands\CommandFactoryInterface',
1078
+			'EventEspresso\core\services\commands\CommandFactoryInterface'                 => 'EventEspresso\core\services\commands\CommandFactory',
1079
+			'EmailValidatorInterface'                                                      => 'EventEspresso\core\domain\services\validation\email\EmailValidatorInterface',
1080
+			'EventEspresso\core\domain\services\validation\email\EmailValidatorInterface'  => 'EventEspresso\core\domain\services\validation\email\EmailValidationService',
1081
+			'NoticeConverterInterface'                                                     => 'EventEspresso\core\services\notices\NoticeConverterInterface',
1082
+			'EventEspresso\core\services\notices\NoticeConverterInterface'                 => 'EventEspresso\core\services\notices\ConvertNoticesToEeErrors',
1083
+			'NoticesContainerInterface'                                                    => 'EventEspresso\core\services\notices\NoticesContainerInterface',
1084
+			'EventEspresso\core\services\notices\NoticesContainerInterface'                => 'EventEspresso\core\services\notices\NoticesContainer',
1085
+			'EventEspresso\core\services\request\RequestInterface'                         => 'EventEspresso\core\services\request\Request',
1086
+			'EventEspresso\core\services\request\ResponseInterface'                        => 'EventEspresso\core\services\request\Response',
1087
+			'EventEspresso\core\domain\DomainInterface'                                    => 'EventEspresso\core\domain\Domain',
1088
+			'Registration_Processor'                                                       => 'EE_Registration_Processor',
1089
+			'EventEspresso\core\services\assets\AssetManifestInterface'                    => 'EventEspresso\core\services\assets\AssetManifest',
1090
+		];
1091
+		foreach ($aliases as $alias => $fqn) {
1092
+			if (is_array($fqn)) {
1093
+				foreach ($fqn as $class => $for_class) {
1094
+					$this->class_cache->addAlias($class, $alias, $for_class);
1095
+				}
1096
+				continue;
1097
+			}
1098
+			$this->class_cache->addAlias($fqn, $alias);
1099
+		}
1100
+		if (! (defined('DOING_AJAX') && DOING_AJAX) && is_admin()) {
1101
+			$this->class_cache->addAlias(
1102
+				'EventEspresso\core\services\notices\ConvertNoticesToAdminNotices',
1103
+				'EventEspresso\core\services\notices\NoticeConverterInterface'
1104
+			);
1105
+		}
1106
+	}
1107
+
1108
+
1109
+	public function debug($for_class = '')
1110
+	{
1111
+		if (method_exists($this->class_cache, 'debug')) {
1112
+			$this->class_cache->debug($for_class);
1113
+		}
1114
+	}
1115
+
1116
+
1117
+	/**
1118
+	 * This is used to reset the internal map and class_loaders to their original default state at the beginning of the
1119
+	 * request Primarily used by unit tests.
1120
+	 */
1121
+	public function reset()
1122
+	{
1123
+		$this->_register_core_class_loaders();
1124
+		$this->_register_core_dependencies();
1125
+	}
1126
+
1127
+
1128
+	/**
1129
+	 * PLZ NOTE: a better name for this method would be is_alias()
1130
+	 * because it returns TRUE if the provided fully qualified name IS an alias
1131
+	 * WHY?
1132
+	 * Because if a class is type hinting for a concretion,
1133
+	 * then why would we need to find another class to supply it?
1134
+	 * ie: if a class asks for `Fully/Qualified/Namespace/SpecificClassName`,
1135
+	 * then give it an instance of `Fully/Qualified/Namespace/SpecificClassName`.
1136
+	 * Don't go looking for some substitute.
1137
+	 * Whereas if a class is type hinting for an interface...
1138
+	 * then we need to find an actual class to use.
1139
+	 * So the interface IS the alias for some other FQN,
1140
+	 * and we need to find out if `Fully/Qualified/Namespace/SomeInterface`
1141
+	 * represents some other class.
1142
+	 *
1143
+	 * @param string $fqn
1144
+	 * @param string $for_class
1145
+	 * @return bool
1146
+	 * @deprecated 4.9.62.p
1147
+	 */
1148
+	public function has_alias(string $fqn = '', string $for_class = ''): bool
1149
+	{
1150
+		return $this->isAlias($fqn, $for_class);
1151
+	}
1152
+
1153
+
1154
+	/**
1155
+	 * PLZ NOTE: a better name for this method would be get_fqn_for_alias()
1156
+	 * because it returns a FQN for provided alias if one exists, otherwise returns the original $alias
1157
+	 * functions recursively, so that multiple aliases can be used to drill down to a FQN
1158
+	 *  for example:
1159
+	 *      if the following two entries were added to the _aliases array:
1160
+	 *          array(
1161
+	 *              'interface_alias'           => 'some\namespace\interface'
1162
+	 *              'some\namespace\interface'  => 'some\namespace\classname'
1163
+	 *          )
1164
+	 *      then one could use EE_Registry::instance()->create( 'interface_alias' )
1165
+	 *      to load an instance of 'some\namespace\classname'
1166
+	 *
1167
+	 * @param string $alias
1168
+	 * @param string $for_class
1169
+	 * @return string
1170
+	 * @deprecated 4.9.62.p
1171
+	 */
1172
+	public function get_alias(string $alias = '', string $for_class = ''): string
1173
+	{
1174
+		return $this->getFqnForAlias($alias, $for_class);
1175
+	}
1176 1176
 }
Please login to merge, or discard this patch.
core/db_models/EEM_Base.model.php 2 patches
Indentation   +6676 added lines, -6676 removed lines patch added patch discarded remove patch
@@ -39,6685 +39,6685 @@
 block discarded – undo
39 39
  */
40 40
 abstract class EEM_Base extends EE_Base implements ResettableInterface
41 41
 {
42
-    /**
43
-     * Flag to indicate whether the values provided to EEM_Base have already been prepared
44
-     * by the model object or not (ie, the model object has used the field's _prepare_for_set function on the values).
45
-     * They almost always WILL NOT, but it's not necessarily a requirement.
46
-     * For example, if you want to run EEM_Event::instance()->get_all(array(array('EVT_ID'=>$_GET['event_id'])));
47
-     *
48
-     * @var boolean
49
-     */
50
-    private $_values_already_prepared_by_model_object = 0;
51
-
52
-    /**
53
-     * when $_values_already_prepared_by_model_object equals this, we assume
54
-     * the data is just like form input that needs to have the model fields'
55
-     * prepare_for_set and prepare_for_use_in_db called on it
56
-     */
57
-    const not_prepared_by_model_object = 0;
58
-
59
-    /**
60
-     * when $_values_already_prepared_by_model_object equals this, we
61
-     * assume this value is coming from a model object and doesn't need to have
62
-     * prepare_for_set called on it, just prepare_for_use_in_db is used
63
-     */
64
-    const prepared_by_model_object = 1;
65
-
66
-    /**
67
-     * when $_values_already_prepared_by_model_object equals this, we assume
68
-     * the values are already to be used in the database (ie no processing is done
69
-     * on them by the model's fields)
70
-     */
71
-    const prepared_for_use_in_db = 2;
72
-
73
-
74
-    protected $singular_item = 'Item';
75
-
76
-    protected $plural_item   = 'Items';
77
-
78
-    /**
79
-     * @type EE_Table_Base[] $_tables array of EE_Table objects for defining which tables comprise this model.
80
-     */
81
-    protected $_tables;
82
-
83
-    /**
84
-     * with two levels: top-level has array keys which are database table aliases (ie, keys in _tables)
85
-     * and the value is an array. Each of those sub-arrays have keys of field names (eg 'ATT_ID', which should also be
86
-     * variable names on the model objects (eg, EE_Attendee), and the keys should be children of EE_Model_Field
87
-     *
88
-     * @var EE_Model_Field_Base[][] $_fields
89
-     */
90
-    protected $_fields;
91
-
92
-    /**
93
-     * array of different kinds of relations
94
-     *
95
-     * @var EE_Model_Relation_Base[] $_model_relations
96
-     */
97
-    protected $_model_relations = [];
98
-
99
-    /**
100
-     * @var EE_Index[] $_indexes
101
-     */
102
-    protected $_indexes = [];
103
-
104
-    /**
105
-     * Default strategy for getting where conditions on this model. This strategy is used to get default
106
-     * where conditions which are added to get_all, update, and delete queries. They can be overridden
107
-     * by setting the same columns as used in these queries in the query yourself.
108
-     *
109
-     * @var EE_Default_Where_Conditions
110
-     */
111
-    protected $_default_where_conditions_strategy;
112
-
113
-    /**
114
-     * Strategy for getting conditions on this model when 'default_where_conditions' equals 'minimum'.
115
-     * This is particularly useful when you want something between 'none' and 'default'
116
-     *
117
-     * @var EE_Default_Where_Conditions
118
-     */
119
-    protected $_minimum_where_conditions_strategy;
120
-
121
-    /**
122
-     * String describing how to find the "owner" of this model's objects.
123
-     * When there is a foreign key on this model to the wp_users table, this isn't needed.
124
-     * But when there isn't, this indicates which related model, or transiently-related model,
125
-     * has the foreign key to the wp_users table.
126
-     * Eg, for EEM_Registration this would be 'Event' because registrations are directly
127
-     * related to events, and events have a foreign key to wp_users.
128
-     * On EEM_Transaction, this would be 'Transaction.Event'
129
-     *
130
-     * @var string
131
-     */
132
-    protected $_model_chain_to_wp_user = '';
133
-
134
-    /**
135
-     * String describing how to find the model with a password controlling access to this model. This property has the
136
-     * same format as $_model_chain_to_wp_user. This is primarily used by the query param "exclude_protected".
137
-     * This value is the path of models to follow to arrive at the model with the password field.
138
-     * If it is an empty string, it means this model has the password field. If it is null, it means there is no
139
-     * model with a password that should affect reading this on the front-end.
140
-     * Eg this is an empty string for the Event model because it has a password.
141
-     * This is null for the Registration model, because its event's password has no bearing on whether
142
-     * you can read the registration or not on the front-end (it just depends on your capabilities.)
143
-     * This is 'Datetime.Event' on the Ticket model, because model queries for tickets that set "exclude_protected"
144
-     * should hide tickets for datetimes for events that have a password set.
145
-     *
146
-     * @var string |null
147
-     */
148
-    protected $model_chain_to_password = null;
149
-
150
-    /**
151
-     * This is a flag typically set by updates so that we don't load the where strategy on updates because updates
152
-     * don't need it (particularly CPT models)
153
-     *
154
-     * @var bool
155
-     */
156
-    protected $_ignore_where_strategy = false;
157
-
158
-    /**
159
-     * String used in caps relating to this model. Eg, if the caps relating to this
160
-     * model are 'ee_edit_events', 'ee_read_events', etc, it would be 'events'.
161
-     *
162
-     * @var string. If null it hasn't been initialized yet. If false then we
163
-     * have indicated capabilities don't apply to this
164
-     */
165
-    protected $_caps_slug = null;
166
-
167
-    /**
168
-     * 2d array where top-level keys are one of EEM_Base::valid_cap_contexts(),
169
-     * and next-level keys are capability names, and each's value is a
170
-     * EE_Default_Where_Condition. If the requester requests to apply caps to the query,
171
-     * they specify which context to use (ie, frontend, backend, edit or delete)
172
-     * and then each capability in the corresponding sub-array that they're missing
173
-     * adds the where conditions onto the query.
174
-     *
175
-     * @var array
176
-     */
177
-    protected $_cap_restrictions = [
178
-        self::caps_read       => [],
179
-        self::caps_read_admin => [],
180
-        self::caps_edit       => [],
181
-        self::caps_delete     => [],
182
-    ];
183
-
184
-    /**
185
-     * Array defining which cap restriction generators to use to create default
186
-     * cap restrictions to put in EEM_Base::_cap_restrictions.
187
-     * Array-keys are one of EEM_Base::valid_cap_contexts(), and values are a child of
188
-     * EE_Restriction_Generator_Base. If you don't want any cap restrictions generated
189
-     * automatically set this to false (not just null).
190
-     *
191
-     * @var EE_Restriction_Generator_Base[]
192
-     */
193
-    protected $_cap_restriction_generators = [];
194
-
195
-    /**
196
-     * constants used to categorize capability restrictions on EEM_Base::_caps_restrictions
197
-     */
198
-    const caps_read       = 'read';
199
-
200
-    const caps_read_admin = 'read_admin';
201
-
202
-    const caps_edit       = 'edit';
203
-
204
-    const caps_delete     = 'delete';
205
-
206
-    /**
207
-     * Keys are all the cap contexts (ie constants EEM_Base::_caps_*) and values are their 'action'
208
-     * as how they'd be used in capability names. Eg EEM_Base::caps_read ('read_frontend')
209
-     * maps to 'read' because when looking for relevant permissions we're going to use
210
-     * 'read' in teh capabilities names like 'ee_read_events' etc.
211
-     *
212
-     * @var array
213
-     */
214
-    protected $_cap_contexts_to_cap_action_map = [
215
-        self::caps_read       => 'read',
216
-        self::caps_read_admin => 'read',
217
-        self::caps_edit       => 'edit',
218
-        self::caps_delete     => 'delete',
219
-    ];
220
-
221
-    /**
222
-     * Timezone
223
-     * This gets set via the constructor so that we know what timezone incoming strings|timestamps are in when there
224
-     * are EE_Datetime_Fields in use.  This can also be used before a get to set what timezone you want strings coming
225
-     * out of the created objects.  NOT all EEM_Base child classes use this property but any that use a
226
-     * EE_Datetime_Field data type will have access to it.
227
-     *
228
-     * @var string
229
-     */
230
-    protected $_timezone;
231
-
232
-
233
-    /**
234
-     * This holds the id of the blog currently making the query.  Has no bearing on single site but is used for
235
-     * multisite.
236
-     *
237
-     * @var int
238
-     */
239
-    protected static $_model_query_blog_id;
240
-
241
-    /**
242
-     * A copy of _fields, except the array keys are the model names pointed to by
243
-     * the field
244
-     *
245
-     * @var EE_Model_Field_Base[]
246
-     */
247
-    private $_cache_foreign_key_to_fields = [];
248
-
249
-    /**
250
-     * Cached list of all the fields on the model, indexed by their name
251
-     *
252
-     * @var EE_Model_Field_Base[]
253
-     */
254
-    private $_cached_fields = null;
255
-
256
-    /**
257
-     * Cached list of all the fields on the model, except those that are
258
-     * marked as only pertinent to the database
259
-     *
260
-     * @var EE_Model_Field_Base[]
261
-     */
262
-    private $_cached_fields_non_db_only = null;
263
-
264
-    /**
265
-     * A cached reference to the primary key for quick lookup
266
-     *
267
-     * @var EE_Model_Field_Base
268
-     */
269
-    private $_primary_key_field = null;
270
-
271
-    /**
272
-     * Flag indicating whether this model has a primary key or not
273
-     *
274
-     * @var boolean
275
-     */
276
-    protected $_has_primary_key_field = null;
277
-
278
-    /**
279
-     * array in the format:  [ FK alias => full PK ]
280
-     * where keys are local column name aliases for foreign keys
281
-     * and values are the fully qualified column name for the primary key they represent
282
-     *  ex:
283
-     *      [ 'Event.EVT_wp_user' => 'WP_User.ID' ]
284
-     *
285
-     * @var array $foreign_key_aliases
286
-     */
287
-    protected $foreign_key_aliases = [];
288
-
289
-    /**
290
-     * Whether or not this model is based off a table in WP core only (CPTs should set
291
-     * this to FALSE, but if we were to make an EE_WP_Post model, it should set this to true).
292
-     * This should be true for models that deal with data that should exist independent of EE.
293
-     * For example, if the model can read and insert data that isn't used by EE, this should be true.
294
-     * It would be false, however, if you could guarantee the model would only interact with EE data,
295
-     * even if it uses a WP core table (eg event and venue models set this to false for that reason:
296
-     * they can only read and insert events and venues custom post types, not arbitrary post types)
297
-     *
298
-     * @var boolean
299
-     */
300
-    protected $_wp_core_model = false;
301
-
302
-    /**
303
-     * @var bool stores whether this model has a password field or not.
304
-     * null until initialized by hasPasswordField()
305
-     */
306
-    protected $has_password_field;
307
-
308
-    /**
309
-     * @var EE_Password_Field|null Automatically set when calling getPasswordField()
310
-     */
311
-    protected $password_field;
312
-
313
-    /**
314
-     *    List of valid operators that can be used for querying.
315
-     * The keys are all operators we'll accept, the values are the real SQL
316
-     * operators used
317
-     *
318
-     * @var array
319
-     */
320
-    protected $_valid_operators = [
321
-        '='           => '=',
322
-        '<='          => '<=',
323
-        '<'           => '<',
324
-        '>='          => '>=',
325
-        '>'           => '>',
326
-        '!='          => '!=',
327
-        'LIKE'        => 'LIKE',
328
-        'like'        => 'LIKE',
329
-        'NOT_LIKE'    => 'NOT LIKE',
330
-        'not_like'    => 'NOT LIKE',
331
-        'NOT LIKE'    => 'NOT LIKE',
332
-        'not like'    => 'NOT LIKE',
333
-        'IN'          => 'IN',
334
-        'in'          => 'IN',
335
-        'NOT_IN'      => 'NOT IN',
336
-        'not_in'      => 'NOT IN',
337
-        'NOT IN'      => 'NOT IN',
338
-        'not in'      => 'NOT IN',
339
-        'between'     => 'BETWEEN',
340
-        'BETWEEN'     => 'BETWEEN',
341
-        'IS_NOT_NULL' => 'IS NOT NULL',
342
-        'is_not_null' => 'IS NOT NULL',
343
-        'IS NOT NULL' => 'IS NOT NULL',
344
-        'is not null' => 'IS NOT NULL',
345
-        'IS_NULL'     => 'IS NULL',
346
-        'is_null'     => 'IS NULL',
347
-        'IS NULL'     => 'IS NULL',
348
-        'is null'     => 'IS NULL',
349
-        'REGEXP'      => 'REGEXP',
350
-        'regexp'      => 'REGEXP',
351
-        'NOT_REGEXP'  => 'NOT REGEXP',
352
-        'not_regexp'  => 'NOT REGEXP',
353
-        'NOT REGEXP'  => 'NOT REGEXP',
354
-        'not regexp'  => 'NOT REGEXP',
355
-    ];
356
-
357
-    /**
358
-     * operators that work like 'IN', accepting a comma-separated list of values inside brackets. Eg '(1,2,3)'
359
-     *
360
-     * @var array
361
-     */
362
-    protected $_in_style_operators = ['IN', 'NOT IN'];
363
-
364
-    /**
365
-     * operators that work like 'BETWEEN'.  Typically used for datetime calculations, i.e. "BETWEEN '12-1-2011' AND
366
-     * '12-31-2012'"
367
-     *
368
-     * @var array
369
-     */
370
-    protected $_between_style_operators = ['BETWEEN'];
371
-
372
-    /**
373
-     * Operators that work like SQL's like: input should be assumed to be a string, already prepared for a LIKE query.
374
-     *
375
-     * @var array
376
-     */
377
-    protected $_like_style_operators = ['LIKE', 'NOT LIKE'];
378
-
379
-    /**
380
-     * operators that are used for handling NUll and !NULL queries.  Typically used for when checking if a row exists
381
-     * on a join table.
382
-     *
383
-     * @var array
384
-     */
385
-    protected $_null_style_operators = ['IS NOT NULL', 'IS NULL'];
386
-
387
-    /**
388
-     * Allowed values for $query_params['order'] for ordering in queries
389
-     *
390
-     * @var array
391
-     */
392
-    protected $_allowed_order_values = ['asc', 'desc', 'ASC', 'DESC'];
393
-
394
-    /**
395
-     * When these are keys in a WHERE or HAVING clause, they are handled much differently
396
-     * than regular field names. It is assumed that their values are an array of WHERE conditions
397
-     *
398
-     * @var array
399
-     */
400
-    private $_logic_query_param_keys = ['not', 'and', 'or', 'NOT', 'AND', 'OR'];
401
-
402
-    /**
403
-     * Allowed keys in $query_params arrays passed into queries. Note that 0 is meant to always be a
404
-     * 'where', but 'where' clauses are so common that we thought we'd omit it
405
-     *
406
-     * @var array
407
-     */
408
-    private $_allowed_query_params = [
409
-        0,
410
-        'limit',
411
-        'order_by',
412
-        'group_by',
413
-        'having',
414
-        'force_join',
415
-        'order',
416
-        'on_join_limit',
417
-        'default_where_conditions',
418
-        'caps',
419
-        'extra_selects',
420
-        'exclude_protected',
421
-    ];
422
-
423
-    /**
424
-     * All the data types that can be used in $wpdb->prepare statements.
425
-     *
426
-     * @var array
427
-     */
428
-    private $_valid_wpdb_data_types = ['%d', '%s', '%f'];
429
-
430
-    /**
431
-     * @var EE_Registry $EE
432
-     */
433
-    protected $EE = null;
434
-
435
-
436
-    /**
437
-     * Property which, when set, will have this model echo out the next X queries to the page for debugging.
438
-     *
439
-     * @var int
440
-     */
441
-    protected $_show_next_x_db_queries = 0;
442
-
443
-    /**
444
-     * When using _get_all_wpdb_results, you can specify a custom selection. If you do so,
445
-     * it gets saved on this property as an instance of CustomSelects so those selections can be used in
446
-     * WHERE, GROUP_BY, etc.
447
-     *
448
-     * @var CustomSelects
449
-     */
450
-    protected $_custom_selections = [];
451
-
452
-    /**
453
-     * key => value Entity Map using  array( EEM_Base::$_model_query_blog_id => array( ID => model object ) )
454
-     * caches every model object we've fetched from the DB on this request
455
-     *
456
-     * @var array
457
-     */
458
-    protected $_entity_map;
459
-
460
-    /**
461
-     * @var LoaderInterface
462
-     */
463
-    protected static $loader;
464
-
465
-    /**
466
-     * @var Mirror
467
-     */
468
-    private static $mirror;
469
-
470
-
471
-    /**
472
-     * constant used to show EEM_Base has not yet verified the db on this http request
473
-     */
474
-    const db_verified_none = 0;
475
-
476
-    /**
477
-     * constant used to show EEM_Base has verified the EE core db on this http request,
478
-     * but not the addons' dbs
479
-     */
480
-    const db_verified_core = 1;
481
-
482
-    /**
483
-     * constant used to show EEM_Base has verified the addons' dbs (and implicitly
484
-     * the EE core db too)
485
-     */
486
-    const db_verified_addons = 2;
487
-
488
-    /**
489
-     * indicates whether an EEM_Base child has already re-verified the DB
490
-     * is ok (we don't want to do it repetitively). Should be set to one the constants
491
-     * looking like EEM_Base::db_verified_*
492
-     *
493
-     * @var int - 0 = none, 1 = core, 2 = addons
494
-     */
495
-    protected static $_db_verification_level = EEM_Base::db_verified_none;
496
-
497
-    /**
498
-     * @const constant for 'default_where_conditions' to apply default where conditions to ALL queried models
499
-     *        (eg, if retrieving registrations ordered by their datetimes, this will only return non-trashed
500
-     *        registrations for non-trashed tickets for non-trashed datetimes)
501
-     */
502
-    const default_where_conditions_all = 'all';
503
-
504
-    /**
505
-     * @const constant for 'default_where_conditions' to apply default where conditions to THIS model only, but
506
-     *        no other models which are joined to (eg, if retrieving registrations ordered by their datetimes, this will
507
-     *        return non-trashed registrations, regardless of the related datetimes and tickets' statuses).
508
-     *        It is preferred to use EEM_Base::default_where_conditions_minimum_others because, when joining to
509
-     *        models which share tables with other models, this can return data for the wrong model.
510
-     */
511
-    const default_where_conditions_this_only = 'this_model_only';
512
-
513
-    /**
514
-     * @const constant for 'default_where_conditions' to apply default where conditions to other models queried,
515
-     *        but not the current model (eg, if retrieving registrations ordered by their datetimes, this will
516
-     *        return all registrations related to non-trashed tickets and non-trashed datetimes)
517
-     */
518
-    const default_where_conditions_others_only = 'other_models_only';
519
-
520
-    /**
521
-     * @const constant for 'default_where_conditions' to apply minimum where conditions to all models queried.
522
-     *        For most models this the same as EEM_Base::default_where_conditions_none, except for models which share
523
-     *        their table with other models, like the Event and Venue models. For example, when querying for events
524
-     *        ordered by their venues' name, this will be sure to only return real events with associated real venues
525
-     *        (regardless of whether those events and venues are trashed)
526
-     *        In contrast, using EEM_Base::default_where_conditions_none would could return WP posts other than EE
527
-     *        events.
528
-     */
529
-    const default_where_conditions_minimum_all = 'minimum';
530
-
531
-    /**
532
-     * @const constant for 'default_where_conditions' to apply apply where conditions to other models, and full default
533
-     *        where conditions for the queried model (eg, when querying events ordered by venues' names, this will
534
-     *        return non-trashed events for any venues, regardless of whether those associated venues are trashed or
535
-     *        not)
536
-     */
537
-    const default_where_conditions_minimum_others = 'full_this_minimum_others';
538
-
539
-    /**
540
-     * @const constant for 'default_where_conditions' to NOT apply any where conditions. This should very rarely be
541
-     *        used, because when querying from a model which shares its table with another model (eg Events and Venues)
542
-     *        it's possible it will return table entries for other models. You should use
543
-     *        EEM_Base::default_where_conditions_minimum_all instead.
544
-     */
545
-    const default_where_conditions_none = 'none';
546
-
547
-
548
-    /**
549
-     * About all child constructors:
550
-     * they should define the _tables, _fields and _model_relations arrays.
551
-     * Should ALWAYS be called after child constructor.
552
-     * In order to make the child constructors to be as simple as possible, this parent constructor
553
-     * finalizes constructing all the object's attributes.
554
-     * Generally, rather than requiring a child to code
555
-     * $this->_tables = array(
556
-     *        'Event_Post_Table' => new EE_Table('Event_Post_Table','wp_posts')
557
-     *        ...);
558
-     *  (thus repeating itself in the array key and in the constructor of the new EE_Table,)
559
-     * each EE_Table has a function to set the table's alias after the constructor, using
560
-     * the array key ('Event_Post_Table'), instead of repeating it. The model fields and model relations
561
-     * do something similar.
562
-     *
563
-     * @param string|null $timezone
564
-     * @throws EE_Error
565
-     * @throws Exception
566
-     */
567
-    protected function __construct($timezone = '')
568
-    {
569
-        // check that the model has not been loaded too soon
570
-        if (! did_action('AHEE__EE_System__load_espresso_addons')) {
571
-            throw new EE_Error(
572
-                sprintf(
573
-                    esc_html__(
574
-                        'The %1$s model can not be loaded before the "AHEE__EE_System__load_espresso_addons" hook has been called. This gives other addons a chance to extend this model.',
575
-                        'event_espresso'
576
-                    ),
577
-                    get_class($this)
578
-                )
579
-            );
580
-        }
581
-        /**
582
-         * Set blogid for models to current blog. However we ONLY do this if $_model_query_blog_id is not already set.
583
-         */
584
-        if (empty(EEM_Base::$_model_query_blog_id)) {
585
-            EEM_Base::set_model_query_blog_id();
586
-        }
587
-        /**
588
-         * Filters the list of tables on a model. It is best to NOT use this directly and instead
589
-         * just use EE_Register_Model_Extension
590
-         *
591
-         * @var EE_Table_Base[] $_tables
592
-         */
593
-        $this->_tables = (array) apply_filters('FHEE__' . get_class($this) . '__construct__tables', $this->_tables);
594
-        foreach ($this->_tables as $table_alias => $table_obj) {
595
-            /** @var $table_obj EE_Table_Base */
596
-            $table_obj->_construct_finalize_with_alias($table_alias);
597
-            if ($table_obj instanceof EE_Secondary_Table) {
598
-                $table_obj->_construct_finalize_set_table_to_join_with($this->_get_main_table());
599
-            }
600
-        }
601
-        /**
602
-         * Filters the list of fields on a model. It is best to NOT use this directly and instead just use
603
-         * EE_Register_Model_Extension
604
-         *
605
-         * @param EE_Model_Field_Base[] $_fields
606
-         */
607
-        $this->_fields = (array) apply_filters('FHEE__' . get_class($this) . '__construct__fields', $this->_fields);
608
-        $this->_invalidate_field_caches();
609
-        foreach ($this->_fields as $table_alias => $fields_for_table) {
610
-            if (! array_key_exists($table_alias, $this->_tables)) {
611
-                throw new EE_Error(
612
-                    sprintf(
613
-                        esc_html__(
614
-                            "Table alias %s does not exist in EEM_Base child's _tables array. Only tables defined are %s",
615
-                            'event_espresso'
616
-                        ),
617
-                        $table_alias,
618
-                        implode(",", $this->_fields)
619
-                    )
620
-                );
621
-            }
622
-            foreach ($fields_for_table as $field_name => $field_obj) {
623
-                /** @var $field_obj EE_Model_Field_Base | EE_Primary_Key_Field_Base */
624
-                // primary key field base has a slightly different _construct_finalize
625
-                /** @var $field_obj EE_Model_Field_Base */
626
-                $field_obj->_construct_finalize($table_alias, $field_name, $this->get_this_model_name());
627
-            }
628
-        }
629
-        // everything is related to Extra_Meta
630
-        if (get_class($this) !== 'EEM_Extra_Meta') {
631
-            // make extra meta related to everything, but don't block deleting things just
632
-            // because they have related extra meta info. For now just orphan those extra meta
633
-            // in the future we should automatically delete them
634
-            $this->_model_relations['Extra_Meta'] = new EE_Has_Many_Any_Relation(false);
635
-        }
636
-        // and change logs
637
-        if (get_class($this) !== 'EEM_Change_Log') {
638
-            $this->_model_relations['Change_Log'] = new EE_Has_Many_Any_Relation(false);
639
-        }
640
-        /**
641
-         * Filters the list of relations on a model. It is best to NOT use this directly and instead just use
642
-         * EE_Register_Model_Extension
643
-         *
644
-         * @param EE_Model_Relation_Base[] $_model_relations
645
-         */
646
-        $this->_model_relations = (array) apply_filters(
647
-            'FHEE__' . get_class($this) . '__construct__model_relations',
648
-            $this->_model_relations
649
-        );
650
-        foreach ($this->_model_relations as $model_name => $relation_obj) {
651
-            /** @var $relation_obj EE_Model_Relation_Base */
652
-            $relation_obj->_construct_finalize_set_models($this->get_this_model_name(), $model_name);
653
-        }
654
-        foreach ($this->_indexes as $index_name => $index_obj) {
655
-            $index_obj->_construct_finalize($index_name, $this->get_this_model_name());
656
-        }
657
-        $this->set_timezone($timezone);
658
-        // finalize default where condition strategy, or set default
659
-        if (! $this->_default_where_conditions_strategy) {
660
-            // nothing was set during child constructor, so set default
661
-            $this->_default_where_conditions_strategy = new EE_Default_Where_Conditions();
662
-        }
663
-        $this->_default_where_conditions_strategy->_finalize_construct($this);
664
-        if (! $this->_minimum_where_conditions_strategy) {
665
-            // nothing was set during child constructor, so set default
666
-            $this->_minimum_where_conditions_strategy = new EE_Default_Where_Conditions();
667
-        }
668
-        $this->_minimum_where_conditions_strategy->_finalize_construct($this);
669
-        // if the cap slug hasn't been set, and we haven't set it to false on purpose
670
-        // to indicate to NOT set it, set it to the logical default
671
-        if ($this->_caps_slug === null) {
672
-            $this->_caps_slug = EEH_Inflector::pluralize_and_lower($this->get_this_model_name());
673
-        }
674
-        // initialize the standard cap restriction generators if none were specified by the child constructor
675
-        if (is_array($this->_cap_restriction_generators)) {
676
-            foreach ($this->cap_contexts_to_cap_action_map() as $cap_context => $action) {
677
-                if (! isset($this->_cap_restriction_generators[ $cap_context ])) {
678
-                    $this->_cap_restriction_generators[ $cap_context ] = apply_filters(
679
-                        'FHEE__EEM_Base___construct__standard_cap_restriction_generator',
680
-                        new EE_Restriction_Generator_Protected(),
681
-                        $cap_context,
682
-                        $this
683
-                    );
684
-                }
685
-            }
686
-        }
687
-        // if there are cap restriction generators, use them to make the default cap restrictions
688
-        if (is_array($this->_cap_restriction_generators)) {
689
-            foreach ($this->_cap_restriction_generators as $context => $generator_object) {
690
-                if (! $generator_object) {
691
-                    continue;
692
-                }
693
-                if (! $generator_object instanceof EE_Restriction_Generator_Base) {
694
-                    throw new EE_Error(
695
-                        sprintf(
696
-                            esc_html__(
697
-                                'Index "%1$s" in the model %2$s\'s _cap_restriction_generators is not a child of EE_Restriction_Generator_Base. It should be that or NULL.',
698
-                                'event_espresso'
699
-                            ),
700
-                            $context,
701
-                            $this->get_this_model_name()
702
-                        )
703
-                    );
704
-                }
705
-                $action = $this->cap_action_for_context($context);
706
-                if (! $generator_object->construction_finalized()) {
707
-                    $generator_object->_construct_finalize($this, $action);
708
-                }
709
-            }
710
-        }
711
-        do_action('AHEE__' . get_class($this) . '__construct__end');
712
-    }
713
-
714
-
715
-    /**
716
-     * @return LoaderInterface
717
-     * @throws InvalidArgumentException
718
-     * @throws InvalidDataTypeException
719
-     * @throws InvalidInterfaceException
720
-     */
721
-    protected static function getLoader(): LoaderInterface
722
-    {
723
-        if (! EEM_Base::$loader instanceof LoaderInterface) {
724
-            EEM_Base::$loader = LoaderFactory::getLoader();
725
-        }
726
-        return EEM_Base::$loader;
727
-    }
728
-
729
-
730
-    /**
731
-     * @return Mirror
732
-     * @since   5.0.0.p
733
-     */
734
-    private static function getMirror(): Mirror
735
-    {
736
-        if (! EEM_Base::$mirror instanceof Mirror) {
737
-            EEM_Base::$mirror = EEM_Base::getLoader()->getShared(Mirror::class);
738
-        }
739
-        return EEM_Base::$mirror;
740
-    }
741
-
742
-
743
-    /**
744
-     * @param string $model_class_Name
745
-     * @param string $timezone
746
-     * @return array
747
-     * @throws ReflectionException
748
-     * @since   5.0.0.p
749
-     */
750
-    private static function getModelArguments(string $model_class_Name, string $timezone): array
751
-    {
752
-        $arguments = [$timezone];
753
-        $params    = EEM_Base::getMirror()->getParameters($model_class_Name);
754
-        if (count($params) > 1) {
755
-            if ($params[1]->getName() === 'model_field_factory') {
756
-                $arguments = [
757
-                    $timezone,
758
-                    EEM_Base::getLoader()->getShared(ModelFieldFactory::class),
759
-                ];
760
-            } elseif ($model_class_Name === 'EEM_Form_Section') {
761
-                $arguments = [
762
-                    EEM_Base::getLoader()->getShared('EventEspresso\core\services\form\meta\FormStatus'),
763
-                    $timezone,
764
-                ];
765
-            } elseif ($model_class_Name === 'EEM_Form_Element') {
766
-                $arguments = [
767
-                    EEM_Base::getLoader()->getShared('EventEspresso\core\services\form\meta\FormStatus'),
768
-                    EEM_Base::getLoader()->getShared('EventEspresso\core\services\form\meta\InputTypes'),
769
-                    $timezone,
770
-                ];
771
-            }
772
-        }
773
-        return $arguments;
774
-    }
775
-
776
-
777
-    /**
778
-     * This function is a singleton method used to instantiate the Espresso_model object
779
-     *
780
-     * @param string|null $timezone   string representing the timezone we want to set for returned Date Time Strings
781
-     *                                (and any incoming timezone data that gets saved).
782
-     *                                Note this just sends the timezone info to the date time model field objects.
783
-     *                                Default is NULL
784
-     *                                (and will be assumed using the set timezone in the 'timezone_string' wp option)
785
-     * @return static (as in the concrete child class)
786
-     * @throws EE_Error
787
-     * @throws ReflectionException
788
-     */
789
-    public static function instance($timezone = '')
790
-    {
791
-        // check if instance of Espresso_model already exists
792
-        if (! static::$_instance instanceof static) {
793
-            $arguments = EEM_Base::getModelArguments(static::class, (string) $timezone);
794
-            $model     = new static(...$arguments);
795
-            EEM_Base::getLoader()->share(static::class, $model, $arguments);
796
-            static::$_instance = $model;
797
-        }
798
-        // we might have a timezone set, let set_timezone decide what to do with it
799
-        if ($timezone) {
800
-            static::$_instance->set_timezone($timezone);
801
-        }
802
-        // Espresso_model object
803
-        return static::$_instance;
804
-    }
805
-
806
-
807
-    /**
808
-     * resets the model and returns it
809
-     *
810
-     * @param string|null $timezone
811
-     * @return EEM_Base|null (if the model was already instantiated, returns it, with
812
-     * all its properties reset; if it wasn't instantiated, returns null)
813
-     * @throws EE_Error
814
-     * @throws ReflectionException
815
-     * @throws InvalidArgumentException
816
-     * @throws InvalidDataTypeException
817
-     * @throws InvalidInterfaceException
818
-     */
819
-    public static function reset($timezone = '')
820
-    {
821
-        if (! static::$_instance instanceof EEM_Base) {
822
-            return null;
823
-        }
824
-        // Let's NOT swap out the current instance for a new one
825
-        // because if someone has a reference to it, we can't remove their reference.
826
-        // It's best to keep using the same reference but change the original object instead,
827
-        // so reset all its properties to their original values as defined in the class.
828
-        $static_properties = EEM_Base::getMirror()->getStaticProperties(static::class);
829
-        foreach (EEM_Base::getMirror()->getDefaultProperties(static::class) as $property => $value) {
830
-            // don't set instance to null like it was originally,
831
-            // but it's static anyways, and we're ignoring static properties (for now at least)
832
-            if (! isset($static_properties[ $property ])) {
833
-                static::$_instance->{$property} = $value;
834
-            }
835
-        }
836
-        // and then directly call its constructor again, like we would if we were creating a new one
837
-        $arguments = EEM_Base::getModelArguments(static::class, (string) $timezone);
838
-        static::$_instance->__construct(...$arguments);
839
-        return self::instance();
840
-    }
841
-
842
-
843
-    /**
844
-     * Used to set the $_model_query_blog_id static property.
845
-     *
846
-     * @param int $blog_id  If provided then will set the blog_id for the models to this id.  If not provided then the
847
-     *                      value for get_current_blog_id() will be used.
848
-     */
849
-    public static function set_model_query_blog_id($blog_id = 0)
850
-    {
851
-        EEM_Base::$_model_query_blog_id = $blog_id > 0
852
-            ? (int) $blog_id
853
-            : get_current_blog_id();
854
-    }
855
-
856
-
857
-    /**
858
-     * Returns whatever is set as the internal $model_query_blog_id.
859
-     *
860
-     * @return int
861
-     */
862
-    public static function get_model_query_blog_id()
863
-    {
864
-        return EEM_Base::$_model_query_blog_id;
865
-    }
866
-
867
-
868
-    /**
869
-     * retrieve the status details from esp_status table as an array IF this model has the status table as a relation.
870
-     *
871
-     * @param boolean $translated return localized strings or JUST the array.
872
-     * @return array
873
-     * @throws EE_Error
874
-     * @throws InvalidArgumentException
875
-     * @throws InvalidDataTypeException
876
-     * @throws InvalidInterfaceException
877
-     * @throws ReflectionException
878
-     */
879
-    public function status_array($translated = false)
880
-    {
881
-        if (! array_key_exists('Status', $this->_model_relations)) {
882
-            return [];
883
-        }
884
-        $model_name   = $this->get_this_model_name();
885
-        $status_type  = str_replace(' ', '_', strtolower(str_replace('_', ' ', $model_name)));
886
-        $stati        = EEM_Status::instance()->get_all([['STS_type' => $status_type]]);
887
-        $status_array = [];
888
-        foreach ($stati as $status) {
889
-            $status_array[ $status->ID() ] = $status->get('STS_code');
890
-        }
891
-        return $translated
892
-            ? EEM_Status::instance()->localized_status($status_array, false, 'sentence')
893
-            : $status_array;
894
-    }
895
-
896
-
897
-    /**
898
-     * Gets all the EE_Base_Class objects which match the $query_params, by querying the DB.
899
-     *
900
-     * @param array $query_params             @see
901
-     *                                        https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
902
-     *                                        or if you have the development copy of EE you can view this at the path:
903
-     *                                        /docs/G--Model-System/model-query-params.md
904
-     * @return EE_Base_Class[]  *note that there is NO option to pass the output type. If you want results different
905
-     *                                        from EE_Base_Class[], use get_all_wpdb_results(). Array keys are object
906
-     *                                        IDs (if there is a primary key on the model. if not, numerically indexed)
907
-     *                                        Some full examples: get 10 transactions which have Scottish attendees:
908
-     *                                        EEM_Transaction::instance()->get_all( array( array(
909
-     *                                        'OR'=>array(
910
-     *                                        'Registration.Attendee.ATT_fname'=>array('like','Mc%'),
911
-     *                                        'Registration.Attendee.ATT_fname*other'=>array('like','Mac%')
912
-     *                                        )
913
-     *                                        ),
914
-     *                                        'limit'=>10,
915
-     *                                        'group_by'=>'TXN_ID'
916
-     *                                        ));
917
-     *                                        get all the answers to the question titled "shirt size" for event with id
918
-     *                                        12, ordered by their answer EEM_Answer::instance()->get_all(array( array(
919
-     *                                        'Question.QST_display_text'=>'shirt size',
920
-     *                                        'Registration.Event.EVT_ID'=>12
921
-     *                                        ),
922
-     *                                        'order_by'=>array('ANS_value'=>'ASC')
923
-     *                                        ));
924
-     * @throws EE_Error
925
-     * @throws ReflectionException
926
-     */
927
-    public function get_all($query_params = [])
928
-    {
929
-        if (
930
-            isset($query_params['limit'])
931
-            && ! isset($query_params['group_by'])
932
-        ) {
933
-            $query_params['group_by'] = array_keys($this->get_combined_primary_key_fields());
934
-        }
935
-        return $this->_create_objects($this->_get_all_wpdb_results($query_params));
936
-    }
937
-
938
-
939
-    /**
940
-     * Modifies the query parameters so we only get back model objects
941
-     * that "belong" to the current user
942
-     *
943
-     * @param array $query_params @see
944
-     *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
945
-     * @return array @see
946
-     *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
947
-     * @throws ReflectionException
948
-     * @throws ReflectionException
949
-     */
950
-    public function alter_query_params_to_only_include_mine($query_params = [])
951
-    {
952
-        $wp_user_field_name = $this->wp_user_field_name();
953
-        if ($wp_user_field_name) {
954
-            $query_params[0][ $wp_user_field_name ] = get_current_user_id();
955
-        }
956
-        return $query_params;
957
-    }
958
-
959
-
960
-    /**
961
-     * Returns the name of the field's name that points to the WP_User table
962
-     *  on this model (or follows the _model_chain_to_wp_user and uses that model's
963
-     * foreign key to the WP_User table)
964
-     *
965
-     * @return string|boolean string on success, boolean false when there is no
966
-     * foreign key to the WP_User table
967
-     * @throws ReflectionException
968
-     * @throws ReflectionException
969
-     */
970
-    public function wp_user_field_name()
971
-    {
972
-        try {
973
-            if (! empty($this->_model_chain_to_wp_user)) {
974
-                $models_to_follow_to_wp_users = explode('.', $this->_model_chain_to_wp_user);
975
-                $last_model_name              = end($models_to_follow_to_wp_users);
976
-                $model_with_fk_to_wp_users    = EE_Registry::instance()->load_model($last_model_name);
977
-                $model_chain_to_wp_user       = $this->_model_chain_to_wp_user . '.';
978
-            } else {
979
-                $model_with_fk_to_wp_users = $this;
980
-                $model_chain_to_wp_user    = '';
981
-            }
982
-            $wp_user_field = $model_with_fk_to_wp_users->get_foreign_key_to('WP_User');
983
-            return $model_chain_to_wp_user . $wp_user_field->get_name();
984
-        } catch (EE_Error $e) {
985
-            return false;
986
-        }
987
-    }
988
-
989
-
990
-    /**
991
-     * Returns the _model_chain_to_wp_user string, which indicates which related model
992
-     * (or transiently-related model) has a foreign key to the wp_users table;
993
-     * useful for finding if model objects of this type are 'owned' by the current user.
994
-     * This is an empty string when the foreign key is on this model and when it isn't,
995
-     * but is only non-empty when this model's ownership is indicated by a RELATED model
996
-     * (or transiently-related model)
997
-     *
998
-     * @return string
999
-     */
1000
-    public function model_chain_to_wp_user()
1001
-    {
1002
-        return $this->_model_chain_to_wp_user;
1003
-    }
1004
-
1005
-
1006
-    /**
1007
-     * Whether this model is 'owned' by a specific wordpress user (even indirectly,
1008
-     * like how registrations don't have a foreign key to wp_users, but the
1009
-     * events they are for are), or is unrelated to wp users.
1010
-     * generally available
1011
-     *
1012
-     * @return boolean
1013
-     */
1014
-    public function is_owned()
1015
-    {
1016
-        if ($this->model_chain_to_wp_user()) {
1017
-            return true;
1018
-        }
1019
-        try {
1020
-            $this->get_foreign_key_to('WP_User');
1021
-            return true;
1022
-        } catch (EE_Error $e) {
1023
-            return false;
1024
-        }
1025
-    }
1026
-
1027
-
1028
-    /**
1029
-     * Used internally to get WPDB results, because other functions, besides get_all, may want to do some queries, but
1030
-     * may want to preserve the WPDB results (eg, update, which first queries to make sure we have all the tables on
1031
-     * the model)
1032
-     *
1033
-     * @param array  $query_params      @see
1034
-     *                                  https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1035
-     * @param string $output            ARRAY_A, OBJECT_K, etc. Just like
1036
-     * @param mixed  $columns_to_select What columns to select. By default, we select all columns specified by the
1037
-     *                                  fields on the model, and the models we joined to in the query. However, you can
1038
-     *                                  override this and set the select to "*", or a specific column name, like
1039
-     *                                  "ATT_ID", etc. If you would like to use these custom selections in WHERE,
1040
-     *                                  GROUP_BY, or HAVING clauses, you must instead provide an array. Array keys are
1041
-     *                                  the aliases used to refer to this selection, and values are to be
1042
-     *                                  numerically-indexed arrays, where 0 is the selection and 1 is the data type.
1043
-     *                                  Eg, array('count'=>array('COUNT(REG_ID)','%d'))
1044
-     * @return array | stdClass[] like results of $wpdb->get_results($sql,OBJECT), (ie, output type is OBJECT)
1045
-     * @throws EE_Error
1046
-     * @throws InvalidArgumentException
1047
-     * @throws ReflectionException
1048
-     */
1049
-    protected function _get_all_wpdb_results($query_params = [], $output = ARRAY_A, $columns_to_select = null)
1050
-    {
1051
-        $this->_custom_selections = $this->getCustomSelection($query_params, $columns_to_select);
1052
-        $model_query_info         = $this->_create_model_query_info_carrier($query_params);
1053
-        $select_expressions       = $columns_to_select === null
1054
-            ? $this->_construct_default_select_sql($model_query_info)
1055
-            : '';
1056
-        if ($this->_custom_selections instanceof CustomSelects) {
1057
-            $custom_expressions = $this->_custom_selections->columnsToSelectExpression();
1058
-            $select_expressions .= $select_expressions
1059
-                ? ', ' . $custom_expressions
1060
-                : $custom_expressions;
1061
-        }
1062
-
1063
-        $SQL = "SELECT $select_expressions " . $this->_construct_2nd_half_of_select_query($model_query_info);
1064
-        return $this->_do_wpdb_query('get_results', [$SQL, $output]);
1065
-    }
1066
-
1067
-
1068
-    /**
1069
-     * Get a CustomSelects object if the $query_params or $columns_to_select allows for it.
1070
-     * Note: $query_params['extra_selects'] will always override any $columns_to_select values. It is the preferred
1071
-     * method of including extra select information.
1072
-     *
1073
-     * @param array             $query_params
1074
-     * @param null|array|string $columns_to_select
1075
-     * @return null|CustomSelects
1076
-     * @throws InvalidArgumentException
1077
-     */
1078
-    protected function getCustomSelection(array $query_params, $columns_to_select = null)
1079
-    {
1080
-        if (! isset($query_params['extra_selects']) && $columns_to_select === null) {
1081
-            return null;
1082
-        }
1083
-        $selects = $query_params['extra_selects'] ?? $columns_to_select;
1084
-        $selects = is_string($selects)
1085
-            ? explode(',', $selects)
1086
-            : $selects;
1087
-        return new CustomSelects($selects);
1088
-    }
1089
-
1090
-
1091
-    /**
1092
-     * Gets an array of rows from the database just like $wpdb->get_results would,
1093
-     * but you can use the model query params to more easily
1094
-     * take care of joins, field preparation etc.
1095
-     *
1096
-     * @param array  $query_params      @see
1097
-     *                                  https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1098
-     * @param string $output            ARRAY_A, OBJECT_K, etc. Just like
1099
-     * @param mixed  $columns_to_select , What columns to select. By default, we select all columns specified by the
1100
-     *                                  fields on the model, and the models we joined to in the query. However, you can
1101
-     *                                  override this and set the select to "*", or a specific column name, like
1102
-     *                                  "ATT_ID", etc. If you would like to use these custom selections in WHERE,
1103
-     *                                  GROUP_BY, or HAVING clauses, you must instead provide an array. Array keys are
1104
-     *                                  the aliases used to refer to this selection, and values are to be
1105
-     *                                  numerically-indexed arrays, where 0 is the selection and 1 is the data type.
1106
-     *                                  Eg, array('count'=>array('COUNT(REG_ID)','%d'))
1107
-     * @return array|stdClass[] like results of $wpdb->get_results($sql,OBJECT), (ie, output type is OBJECT)
1108
-     * @throws EE_Error
1109
-     * @throws ReflectionException
1110
-     */
1111
-    public function get_all_wpdb_results($query_params = [], $output = ARRAY_A, $columns_to_select = null)
1112
-    {
1113
-        return $this->_get_all_wpdb_results($query_params, $output, $columns_to_select);
1114
-    }
1115
-
1116
-
1117
-    /**
1118
-     * For creating a custom select statement
1119
-     *
1120
-     * @param mixed $columns_to_select either a string to be inserted directly as the select statement,
1121
-     *                                 or an array where keys are aliases, and values are arrays where 0=>the selection
1122
-     *                                 SQL, and 1=>is the datatype
1123
-     * @return string
1124
-     * @throws EE_Error
1125
-     */
1126
-    private function _construct_select_from_input($columns_to_select)
1127
-    {
1128
-        if (is_array($columns_to_select)) {
1129
-            $select_sql_array = [];
1130
-            foreach ($columns_to_select as $alias => $selection_and_datatype) {
1131
-                if (! is_array($selection_and_datatype) || ! isset($selection_and_datatype[1])) {
1132
-                    throw new EE_Error(
1133
-                        sprintf(
1134
-                            esc_html__(
1135
-                                "Custom selection %s (alias %s) needs to be an array like array('COUNT(REG_ID)','%%d')",
1136
-                                'event_espresso'
1137
-                            ),
1138
-                            $selection_and_datatype,
1139
-                            $alias
1140
-                        )
1141
-                    );
1142
-                }
1143
-                if (! in_array($selection_and_datatype[1], $this->_valid_wpdb_data_types, true)) {
1144
-                    throw new EE_Error(
1145
-                        sprintf(
1146
-                            esc_html__(
1147
-                                "Datatype %s (for selection '%s' and alias '%s') is not a valid wpdb datatype (eg %%s)",
1148
-                                'event_espresso'
1149
-                            ),
1150
-                            $selection_and_datatype[1],
1151
-                            $selection_and_datatype[0],
1152
-                            $alias,
1153
-                            implode(', ', $this->_valid_wpdb_data_types)
1154
-                        )
1155
-                    );
1156
-                }
1157
-                $select_sql_array[] = "{$selection_and_datatype[0]} AS $alias";
1158
-            }
1159
-            $columns_to_select_string = implode(', ', $select_sql_array);
1160
-        } else {
1161
-            $columns_to_select_string = $columns_to_select;
1162
-        }
1163
-        return $columns_to_select_string;
1164
-    }
1165
-
1166
-
1167
-    /**
1168
-     * Convenient wrapper for getting the primary key field's name. Eg, on Registration, this would be 'REG_ID'
1169
-     *
1170
-     * @return string
1171
-     * @throws EE_Error
1172
-     */
1173
-    public function primary_key_name()
1174
-    {
1175
-        return $this->get_primary_key_field()->get_name();
1176
-    }
1177
-
1178
-
1179
-    /**
1180
-     * Gets a single item for this model from the DB, given only its ID (or null if none is found).
1181
-     * If there is no primary key on this model, $id is treated as primary key string
1182
-     *
1183
-     * @param mixed $id int or string, depending on the type of the model's primary key
1184
-     * @return EE_Base_Class|mixed|null
1185
-     * @throws EE_Error
1186
-     * @throws ReflectionException
1187
-     */
1188
-    public function get_one_by_ID($id)
1189
-    {
1190
-        // if no id is passed, just return null
1191
-        if (empty($id)) {
1192
-            return null;
1193
-        }
1194
-        if ($this->get_from_entity_map($id)) {
1195
-            return $this->get_from_entity_map($id);
1196
-        }
1197
-        $model_object = $this->get_one(
1198
-            $this->alter_query_params_to_restrict_by_ID(
1199
-                $id,
1200
-                ['default_where_conditions' => EEM_Base::default_where_conditions_minimum_all]
1201
-            )
1202
-        );
1203
-        $className    = $this->_get_class_name();
1204
-        if ($model_object instanceof $className) {
1205
-            // make sure valid objects get added to the entity map
1206
-            // so that the next call to this method doesn't trigger another trip to the db
1207
-            $this->add_to_entity_map($model_object);
1208
-        }
1209
-        return $model_object;
1210
-    }
1211
-
1212
-
1213
-    /**
1214
-     * Alters query parameters to only get items with this ID are returned.
1215
-     * Takes into account that the ID might be a string produced by EEM_Base::get_index_primary_key_string(),
1216
-     * or could just be a simple primary key ID
1217
-     *
1218
-     * @param int   $id
1219
-     * @param array $query_params
1220
-     * @return array of normal query params, @see
1221
-     *               https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1222
-     * @throws EE_Error
1223
-     */
1224
-    public function alter_query_params_to_restrict_by_ID($id, $query_params = [])
1225
-    {
1226
-        if (! isset($query_params[0])) {
1227
-            $query_params[0] = [];
1228
-        }
1229
-        $conditions_from_id = $this->parse_index_primary_key_string($id);
1230
-        if ($conditions_from_id === null) {
1231
-            $query_params[0][ $this->primary_key_name() ] = $id;
1232
-        } else {
1233
-            // no primary key, so the $id must be from the get_index_primary_key_string()
1234
-            $query_params[0] = array_replace_recursive($query_params[0], $this->parse_index_primary_key_string($id));
1235
-        }
1236
-        return $query_params;
1237
-    }
1238
-
1239
-
1240
-    /**
1241
-     * Gets a single item for this model from the DB, given the $query_params. Only returns a single class, not an
1242
-     * array. If no item is found, null is returned.
1243
-     *
1244
-     * @param array $query_params like EEM_Base's $query_params variable.
1245
-     * @return EE_Base_Class|EE_Soft_Delete_Base_Class|NULL
1246
-     * @throws EE_Error
1247
-     * @throws ReflectionException
1248
-     */
1249
-    public function get_one($query_params = [])
1250
-    {
1251
-        if (! is_array($query_params)) {
1252
-            EE_Error::doing_it_wrong(
1253
-                'EEM_Base::get_one',
1254
-                sprintf(
1255
-                    esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1256
-                    gettype($query_params)
1257
-                ),
1258
-                '4.6.0'
1259
-            );
1260
-            $query_params = [];
1261
-        }
1262
-        $query_params['limit'] = 1;
1263
-        $items                 = $this->get_all($query_params);
1264
-        if (empty($items)) {
1265
-            return null;
1266
-        }
1267
-        return array_shift($items);
1268
-    }
1269
-
1270
-
1271
-    /**
1272
-     * Returns the next x number of items in sequence from the given value as
1273
-     * found in the database matching the given query conditions.
1274
-     *
1275
-     * @param mixed $current_field_value    Value used for the reference point.
1276
-     * @param null  $field_to_order_by      What field is used for the
1277
-     *                                      reference point.
1278
-     * @param int   $limit                  How many to return.
1279
-     * @param array $query_params           Extra conditions on the query.
1280
-     * @param null  $columns_to_select      If left null, then an array of
1281
-     *                                      EE_Base_Class objects is returned,
1282
-     *                                      otherwise you can indicate just the
1283
-     *                                      columns you want returned.
1284
-     * @return EE_Base_Class[]|array
1285
-     * @throws EE_Error
1286
-     * @throws ReflectionException
1287
-     */
1288
-    public function next_x(
1289
-        $current_field_value,
1290
-        $field_to_order_by = null,
1291
-        $limit = 1,
1292
-        $query_params = [],
1293
-        $columns_to_select = null
1294
-    ) {
1295
-        return $this->_get_consecutive(
1296
-            $current_field_value,
1297
-            '>',
1298
-            $field_to_order_by,
1299
-            $limit,
1300
-            $query_params,
1301
-            $columns_to_select
1302
-        );
1303
-    }
1304
-
1305
-
1306
-    /**
1307
-     * Returns the previous x number of items in sequence from the given value
1308
-     * as found in the database matching the given query conditions.
1309
-     *
1310
-     * @param mixed $current_field_value    Value used for the reference point.
1311
-     * @param null  $field_to_order_by      What field is used for the
1312
-     *                                      reference point.
1313
-     * @param int   $limit                  How many to return.
1314
-     * @param array $query_params           Extra conditions on the query.
1315
-     * @param null  $columns_to_select      If left null, then an array of
1316
-     *                                      EE_Base_Class objects is returned,
1317
-     *                                      otherwise you can indicate just the
1318
-     *                                      columns you want returned.
1319
-     * @return EE_Base_Class[]|array
1320
-     * @throws EE_Error
1321
-     * @throws ReflectionException
1322
-     */
1323
-    public function previous_x(
1324
-        $current_field_value,
1325
-        $field_to_order_by = null,
1326
-        $limit = 1,
1327
-        $query_params = [],
1328
-        $columns_to_select = null
1329
-    ) {
1330
-        return $this->_get_consecutive(
1331
-            $current_field_value,
1332
-            '<',
1333
-            $field_to_order_by,
1334
-            $limit,
1335
-            $query_params,
1336
-            $columns_to_select
1337
-        );
1338
-    }
1339
-
1340
-
1341
-    /**
1342
-     * Returns the next item in sequence from the given value as found in the
1343
-     * database matching the given query conditions.
1344
-     *
1345
-     * @param mixed $current_field_value    Value used for the reference point.
1346
-     * @param null  $field_to_order_by      What field is used for the
1347
-     *                                      reference point.
1348
-     * @param array $query_params           Extra conditions on the query.
1349
-     * @param null  $columns_to_select      If left null, then an EE_Base_Class
1350
-     *                                      object is returned, otherwise you
1351
-     *                                      can indicate just the columns you
1352
-     *                                      want and a single array indexed by
1353
-     *                                      the columns will be returned.
1354
-     * @return EE_Base_Class|null|array()
1355
-     * @throws EE_Error
1356
-     * @throws ReflectionException
1357
-     */
1358
-    public function next(
1359
-        $current_field_value,
1360
-        $field_to_order_by = null,
1361
-        $query_params = [],
1362
-        $columns_to_select = null
1363
-    ) {
1364
-        $results = $this->_get_consecutive(
1365
-            $current_field_value,
1366
-            '>',
1367
-            $field_to_order_by,
1368
-            1,
1369
-            $query_params,
1370
-            $columns_to_select
1371
-        );
1372
-        return empty($results)
1373
-            ? null
1374
-            : reset($results);
1375
-    }
1376
-
1377
-
1378
-    /**
1379
-     * Returns the previous item in sequence from the given value as found in
1380
-     * the database matching the given query conditions.
1381
-     *
1382
-     * @param mixed $current_field_value    Value used for the reference point.
1383
-     * @param null  $field_to_order_by      What field is used for the
1384
-     *                                      reference point.
1385
-     * @param array $query_params           Extra conditions on the query.
1386
-     * @param null  $columns_to_select      If left null, then an EE_Base_Class
1387
-     *                                      object is returned, otherwise you
1388
-     *                                      can indicate just the columns you
1389
-     *                                      want and a single array indexed by
1390
-     *                                      the columns will be returned.
1391
-     * @return EE_Base_Class|null|array()
1392
-     * @throws EE_Error
1393
-     * @throws ReflectionException
1394
-     */
1395
-    public function previous(
1396
-        $current_field_value,
1397
-        $field_to_order_by = null,
1398
-        $query_params = [],
1399
-        $columns_to_select = null
1400
-    ) {
1401
-        $results = $this->_get_consecutive(
1402
-            $current_field_value,
1403
-            '<',
1404
-            $field_to_order_by,
1405
-            1,
1406
-            $query_params,
1407
-            $columns_to_select
1408
-        );
1409
-        return empty($results)
1410
-            ? null
1411
-            : reset($results);
1412
-    }
1413
-
1414
-
1415
-    /**
1416
-     * Returns the a consecutive number of items in sequence from the given
1417
-     * value as found in the database matching the given query conditions.
1418
-     *
1419
-     * @param mixed  $current_field_value   Value used for the reference point.
1420
-     * @param string $operand               What operand is used for the sequence.
1421
-     * @param string $field_to_order_by     What field is used for the reference point.
1422
-     * @param int    $limit                 How many to return.
1423
-     * @param array  $query_params          Extra conditions on the query.
1424
-     * @param null   $columns_to_select     If left null, then an array of EE_Base_Class objects is returned,
1425
-     *                                      otherwise you can indicate just the columns you want returned.
1426
-     * @return EE_Base_Class[]|array
1427
-     * @throws EE_Error
1428
-     * @throws ReflectionException
1429
-     */
1430
-    protected function _get_consecutive(
1431
-        $current_field_value,
1432
-        $operand = '>',
1433
-        $field_to_order_by = null,
1434
-        $limit = 1,
1435
-        $query_params = [],
1436
-        $columns_to_select = null
1437
-    ) {
1438
-        // if $field_to_order_by is empty then let's assume we're ordering by the primary key.
1439
-        if (empty($field_to_order_by)) {
1440
-            if ($this->has_primary_key_field()) {
1441
-                $field_to_order_by = $this->get_primary_key_field()->get_name();
1442
-            } else {
1443
-                if (WP_DEBUG) {
1444
-                    throw new EE_Error(
1445
-                        esc_html__(
1446
-                            'EEM_Base::_get_consecutive() has been called with no $field_to_order_by argument and there is no primary key on the field.  Please provide the field you would like to use as the base for retrieving the next item(s).',
1447
-                            'event_espresso'
1448
-                        )
1449
-                    );
1450
-                }
1451
-                EE_Error::add_error(
1452
-                    esc_html__('There was an error with the query.', 'event_espresso'),
1453
-                    __FILE__,
1454
-                    __FUNCTION__,
1455
-                    __LINE__
1456
-                );
1457
-                return [];
1458
-            }
1459
-        }
1460
-        if (! is_array($query_params)) {
1461
-            EE_Error::doing_it_wrong(
1462
-                'EEM_Base::_get_consecutive',
1463
-                sprintf(
1464
-                    esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1465
-                    gettype($query_params)
1466
-                ),
1467
-                '4.6.0'
1468
-            );
1469
-            $query_params = [];
1470
-        }
1471
-        // let's add the where query param for consecutive look up.
1472
-        $query_params[0][ $field_to_order_by ] = [$operand, $current_field_value];
1473
-        $query_params['limit']                 = $limit;
1474
-        // set direction
1475
-        $incoming_orderby         = isset($query_params['order_by'])
1476
-            ? (array) $query_params['order_by']
1477
-            : [];
1478
-        $query_params['order_by'] = $operand === '>'
1479
-            ? [$field_to_order_by => 'ASC'] + $incoming_orderby
1480
-            : [$field_to_order_by => 'DESC'] + $incoming_orderby;
1481
-        // if $columns_to_select is empty then that means we're returning EE_Base_Class objects
1482
-        if (empty($columns_to_select)) {
1483
-            return $this->get_all($query_params);
1484
-        }
1485
-        // getting just the fields
1486
-        return $this->_get_all_wpdb_results($query_params, ARRAY_A, $columns_to_select);
1487
-    }
1488
-
1489
-
1490
-    /**
1491
-     * This sets the _timezone property after model object has been instantiated.
1492
-     *
1493
-     * @param string|null $timezone valid PHP DateTimeZone timezone string
1494
-     * @throws Exception
1495
-     */
1496
-    public function set_timezone(?string $timezone = '')
1497
-    {
1498
-        if (! $timezone) {
1499
-            return;
1500
-        }
1501
-        $this->_timezone = $timezone;
1502
-        // note we need to loop through relations and set the timezone on those objects as well.
1503
-        foreach ($this->_model_relations as $relation) {
1504
-            $relation->set_timezone($timezone);
1505
-        }
1506
-        // and finally we do the same for any datetime fields
1507
-        foreach ($this->_fields as $field) {
1508
-            if ($field instanceof EE_Datetime_Field) {
1509
-                $field->set_timezone($timezone);
1510
-            }
1511
-        }
1512
-    }
1513
-
1514
-
1515
-    /**
1516
-     * This just returns whatever is set for the current timezone.
1517
-     *
1518
-     * @access public
1519
-     * @return string
1520
-     * @throws Exception
1521
-     */
1522
-    public function get_timezone()
1523
-    {
1524
-        // first validate if timezone is set.  If not, then let's set it be whatever is set on the model fields.
1525
-        if (empty($this->_timezone)) {
1526
-            foreach ($this->_fields as $field) {
1527
-                if ($field instanceof EE_Datetime_Field) {
1528
-                    $this->set_timezone($field->get_timezone());
1529
-                    break;
1530
-                }
1531
-            }
1532
-        }
1533
-        // if timezone STILL empty then return the default timezone for the site.
1534
-        if (empty($this->_timezone)) {
1535
-            $this->set_timezone(EEH_DTT_Helper::get_timezone());
1536
-        }
1537
-        return $this->_timezone;
1538
-    }
1539
-
1540
-
1541
-    /**
1542
-     * This returns the date formats set for the given field name and also ensures that
1543
-     * $this->_timezone property is set correctly.
1544
-     *
1545
-     * @param string $field_name The name of the field the formats are being retrieved for.
1546
-     * @param bool   $pretty     Whether to return the pretty formats (true) or not (false).
1547
-     * @return array formats in an array with the date format first, and the time format last.
1548
-     * @throws EE_Error   If the given field_name is not of the EE_Datetime_Field type.
1549
-     * @since 4.6.x
1550
-     */
1551
-    public function get_formats_for($field_name, $pretty = false)
1552
-    {
1553
-        $field_settings = $this->field_settings_for($field_name);
1554
-        // if not a valid EE_Datetime_Field then throw error
1555
-        if (! $field_settings instanceof EE_Datetime_Field) {
1556
-            throw new EE_Error(
1557
-                sprintf(
1558
-                    esc_html__(
1559
-                        'The field sent into EEM_Base::get_formats_for (%s) is not registered as a EE_Datetime_Field. Please check the spelling and make sure you are submitting the right field name to retrieve date_formats for.',
1560
-                        'event_espresso'
1561
-                    ),
1562
-                    $field_name
1563
-                )
1564
-            );
1565
-        }
1566
-        // while we are here, let's make sure the timezone internally in EEM_Base matches what is stored on
1567
-        // the field.
1568
-        $this->_timezone = $field_settings->get_timezone();
1569
-        return [$field_settings->get_date_format($pretty), $field_settings->get_time_format($pretty)];
1570
-    }
1571
-
1572
-
1573
-    /**
1574
-     * This returns the current time in a format setup for a query on this model.
1575
-     * Usage of this method makes it easier to setup queries against EE_Datetime_Field columns because
1576
-     * it will return:
1577
-     *  - a formatted string in the timezone and format currently set on the EE_Datetime_Field for the given field for
1578
-     *  NOW
1579
-     *  - or a unix timestamp (equivalent to time())
1580
-     * Note: When requesting a formatted string, if the date or time format doesn't include seconds, for example,
1581
-     * the time returned, because it uses that format, will also NOT include seconds. For this reason, if you want
1582
-     * the time returned to be the current time down to the exact second, set $timestamp to true.
1583
-     *
1584
-     * @param string $field_name       The field the current time is needed for.
1585
-     * @param bool   $timestamp        True means to return a unix timestamp. Otherwise a
1586
-     *                                 formatted string matching the set format for the field in the set timezone will
1587
-     *                                 be returned.
1588
-     * @param string $what             Whether to return the string in just the time format, the date format, or both.
1589
-     * @return int|string  If the given field_name is not of the EE_Datetime_Field type, then an EE_Error
1590
-     *                                 exception is triggered.
1591
-     * @throws EE_Error    If the given field_name is not of the EE_Datetime_Field type.
1592
-     * @throws Exception
1593
-     * @since 4.6.x
1594
-     */
1595
-    public function current_time_for_query($field_name, $timestamp = false, $what = 'both')
1596
-    {
1597
-        $formats  = $this->get_formats_for($field_name);
1598
-        $DateTime = new DateTime("now", new DateTimeZone($this->_timezone));
1599
-        if ($timestamp) {
1600
-            return $DateTime->format('U');
1601
-        }
1602
-        // not returning timestamp, so return formatted string in timezone.
1603
-        switch ($what) {
1604
-            case 'time':
1605
-                return $DateTime->format($formats[1]);
1606
-            case 'date':
1607
-                return $DateTime->format($formats[0]);
1608
-            default:
1609
-                return $DateTime->format(implode(' ', $formats));
1610
-        }
1611
-    }
1612
-
1613
-
1614
-    /**
1615
-     * This receives a time string for a given field and ensures
1616
-     * that it is set up to match what the internal settings for the model are.
1617
-     * Returns a DateTime object.
1618
-     * Note: a gotcha for when you send in unix timestamp.  Remember a unix timestamp is already timezone agnostic,
1619
-     * (functionally the equivalent of UTC+0).
1620
-     * So when you send it in, whatever timezone string you include is ignored.
1621
-     *
1622
-     * @param string      $field_name      The field being setup.
1623
-     * @param string      $timestring      The date time string being used.
1624
-     * @param string      $incoming_format The format for the time string.
1625
-     * @param string|null $timezone_string By default, it is assumed the incoming time string is in timezone for
1626
-     *                                     the blog.  If this is not the case, then it can be specified here.  If
1627
-     *                                     incoming format is
1628
-     *                                     'U', this is ignored.
1629
-     * @return DbSafeDateTime
1630
-     * @throws EE_Error
1631
-     * @throws Exception
1632
-     */
1633
-    public function convert_datetime_for_query(
1634
-        string $field_name,
1635
-        string $timestring,
1636
-        string $incoming_format,
1637
-        ?string $timezone_string = ''
1638
-    ): DbSafeDateTime {
1639
-        // just using this to ensure the timezone is set correctly internally
1640
-        $this->get_formats_for($field_name);
1641
-        // load EEH_DTT_Helper
1642
-        $timezone_string     = ! empty($timezone_string) ? $timezone_string : EEH_DTT_Helper::get_timezone();
1643
-        $incomingDateTime = date_create_from_format($incoming_format, $timestring, new DateTimeZone($timezone_string));
1644
-        EEH_DTT_Helper::setTimezone($incomingDateTime, new DateTimeZone($this->_timezone));
1645
-        return DbSafeDateTime::createFromDateTime($incomingDateTime);
1646
-    }
1647
-
1648
-
1649
-    /**
1650
-     * Gets all the tables comprising this model. Array keys are the table aliases, and values are EE_Table objects
1651
-     *
1652
-     * @return EE_Table_Base[]
1653
-     */
1654
-    public function get_tables()
1655
-    {
1656
-        return $this->_tables;
1657
-    }
1658
-
1659
-
1660
-    /**
1661
-     * Updates all the database entries (in each table for this model) according to $fields_n_values and optionally
1662
-     * also updates all the model objects, where the criteria expressed in $query_params are met..
1663
-     * Also note: if this model has multiple tables, this update verifies all the secondary tables have an entry for
1664
-     * each row (in the primary table) we're trying to update; if not, it inserts an entry in the secondary table. Eg:
1665
-     * if our model has 2 tables: wp_posts (primary), and wp_esp_event (secondary). Let's say we are trying to update a
1666
-     * model object with EVT_ID = 1
1667
-     * (which means where wp_posts has ID = 1, because wp_posts.ID is the primary key's column), which exists, but
1668
-     * there is no entry in wp_esp_event for this entry in wp_posts. So, this update script will insert a row into
1669
-     * wp_esp_event, using any available parameters from $fields_n_values (eg, if "EVT_limit" => 40 is in
1670
-     * $fields_n_values, the new entry in wp_esp_event will set EVT_limit = 40, and use default for other columns which
1671
-     * are not specified)
1672
-     *
1673
-     * @param array   $fields_n_values         keys are model fields (exactly like keys in EEM_Base::_fields, NOT db
1674
-     *                                         columns!), values are strings, ints, floats, and maybe arrays if they
1675
-     *                                         are to be serialized. Basically, the values are what you'd expect to be
1676
-     *                                         values on the model, NOT necessarily what's in the DB. For example, if
1677
-     *                                         we wanted to update only the TXN_details on any Transactions where its
1678
-     *                                         ID=34, we'd use this method as follows:
1679
-     *                                         EEM_Transaction::instance()->update(
1680
-     *                                         array('TXN_details'=>array('detail1'=>'monkey','detail2'=>'banana'),
1681
-     *                                         array(array('TXN_ID'=>34)));
1682
-     * @param array   $query_params            @see
1683
-     *                                         https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1684
-     *                                         Eg, consider updating Question's QST_admin_label field is of type
1685
-     *                                         Simple_HTML. If you use this function to update that field to $new_value
1686
-     *                                         = (note replace 8's with appropriate opening and closing tags in the
1687
-     *                                         following example)"8script8alert('I hack all');8/script88b8boom
1688
-     *                                         baby8/b8", then if you set $values_already_prepared_by_model_object to
1689
-     *                                         TRUE, it is assumed that you've already called
1690
-     *                                         EE_Simple_HTML_Field->prepare_for_set($new_value), which removes the
1691
-     *                                         malicious javascript. However, if
1692
-     *                                         $values_already_prepared_by_model_object is left as FALSE, then
1693
-     *                                         EE_Simple_HTML_Field->prepare_for_set($new_value) will be called on it,
1694
-     *                                         and every other field, before insertion. We provide this parameter
1695
-     *                                         because model objects perform their prepare_for_set function on all
1696
-     *                                         their values, and so don't need to be called again (and in many cases,
1697
-     *                                         shouldn't be called again. Eg: if we escape HTML characters in the
1698
-     *                                         prepare_for_set method...)
1699
-     * @param boolean $keep_model_objs_in_sync if TRUE, makes sure we ALSO update model objects
1700
-     *                                         in this model's entity map according to $fields_n_values that match
1701
-     *                                         $query_params. This obviously has some overhead, so you can disable it
1702
-     *                                         by setting this to FALSE, but be aware that model objects being used
1703
-     *                                         could get out-of-sync with the database
1704
-     * @return int how many rows got updated or FALSE if something went wrong with the query (wp returns FALSE or num
1705
-     *                                         rows affected which *could* include 0 which DOES NOT mean the query was
1706
-     *                                         bad)
1707
-     * @throws EE_Error
1708
-     * @throws ReflectionException
1709
-     */
1710
-    public function update($fields_n_values, $query_params, $keep_model_objs_in_sync = true)
1711
-    {
1712
-        if (! is_array($query_params)) {
1713
-            EE_Error::doing_it_wrong(
1714
-                'EEM_Base::update',
1715
-                sprintf(
1716
-                    esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1717
-                    gettype($query_params)
1718
-                ),
1719
-                '4.6.0'
1720
-            );
1721
-            $query_params = [];
1722
-        }
1723
-        /**
1724
-         * Action called before a model update call has been made.
1725
-         *
1726
-         * @param EEM_Base $model
1727
-         * @param array    $fields_n_values the updated fields and their new values
1728
-         * @param array    $query_params    @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1729
-         */
1730
-        do_action('AHEE__EEM_Base__update__begin', $this, $fields_n_values, $query_params);
1731
-        /**
1732
-         * Filters the fields about to be updated given the query parameters. You can provide the
1733
-         * $query_params to $this->get_all() to find exactly which records will be updated
1734
-         *
1735
-         * @param array    $fields_n_values fields and their new values
1736
-         * @param EEM_Base $model           the model being queried
1737
-         * @param array    $query_params    @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1738
-         */
1739
-        $fields_n_values = (array) apply_filters(
1740
-            'FHEE__EEM_Base__update__fields_n_values',
1741
-            $fields_n_values,
1742
-            $this,
1743
-            $query_params
1744
-        );
1745
-        // need to verify that, for any entry we want to update, there are entries in each secondary table.
1746
-        // to do that, for each table, verify that it's PK isn't null.
1747
-        $tables = $this->get_tables();
1748
-        // and if the other tables don't have a row for each table-to-be-updated, we'll insert one with whatever values available in the current update query
1749
-        // NOTE: we should make this code more efficient by NOT querying twice
1750
-        // before the real update, but that needs to first go through ALPHA testing
1751
-        // as it's dangerous. says Mike August 8 2014
1752
-        // we want to make sure the default_where strategy is ignored
1753
-        $this->_ignore_where_strategy = true;
1754
-        $wpdb_select_results          = $this->_get_all_wpdb_results($query_params);
1755
-        foreach ($wpdb_select_results as $wpdb_result) {
1756
-            // type cast stdClass as array
1757
-            $wpdb_result = (array) $wpdb_result;
1758
-            // get the model object's PK, as we'll want this if we need to insert a row into secondary tables
1759
-            if ($this->has_primary_key_field()) {
1760
-                $main_table_pk_value = $wpdb_result[ $this->get_primary_key_field()->get_qualified_column() ];
1761
-            } else {
1762
-                // if there's no primary key, we basically can't support having a 2nd table on the model (we could but it would be lots of work)
1763
-                $main_table_pk_value = null;
1764
-            }
1765
-            // if there are more than 1 tables, we'll want to verify that each table for this model has an entry in the other tables
1766
-            // and if the other tables don't have a row for each table-to-be-updated, we'll insert one with whatever values available in the current update query
1767
-            if (count($tables) > 1) {
1768
-                // foreach matching row in the DB, ensure that each table's PK isn't null. If so, there must not be an entry
1769
-                // in that table, and so we'll want to insert one
1770
-                foreach ($tables as $table_obj) {
1771
-                    $this_table_pk_column = $table_obj->get_fully_qualified_pk_column();
1772
-                    // if there is no private key for this table on the results, it means there's no entry
1773
-                    // in this table, right? so insert a row in the current table, using any fields available
1774
-                    if (
1775
-                        ! (array_key_exists($this_table_pk_column, $wpdb_result)
1776
-                           && $wpdb_result[ $this_table_pk_column ])
1777
-                    ) {
1778
-                        $success = $this->_insert_into_specific_table(
1779
-                            $table_obj,
1780
-                            $fields_n_values,
1781
-                            $main_table_pk_value
1782
-                        );
1783
-                        // if we died here, report the error
1784
-                        if (! $success) {
1785
-                            return false;
1786
-                        }
1787
-                    }
1788
-                }
1789
-            }
1790
-            //              //and now check that if we have cached any models by that ID on the model, that
1791
-            //              //they also get updated properly
1792
-            //              $model_object = $this->get_from_entity_map( $main_table_pk_value );
1793
-            //              if( $model_object ){
1794
-            //                  foreach( $fields_n_values as $field => $value ){
1795
-            //                      $model_object->set($field, $value);
1796
-            // let's make sure default_where strategy is followed now
1797
-            $this->_ignore_where_strategy = false;
1798
-        }
1799
-        // if we want to keep model objects in sync, AND
1800
-        // if this wasn't called from a model object (to update itself)
1801
-        // then we want to make sure we keep all the existing
1802
-        // model objects in sync with the db
1803
-        if ($keep_model_objs_in_sync && ! $this->_values_already_prepared_by_model_object) {
1804
-            if ($this->has_primary_key_field()) {
1805
-                $model_objs_affected_ids = $this->get_col($query_params);
1806
-            } else {
1807
-                // we need to select a bunch of columns and then combine them into the the "index primary key string"s
1808
-                $models_affected_key_columns = $this->_get_all_wpdb_results($query_params, ARRAY_A);
1809
-                $model_objs_affected_ids     = [];
1810
-                foreach ($models_affected_key_columns as $row) {
1811
-                    $combined_index_key                             = $this->get_index_primary_key_string($row);
1812
-                    $model_objs_affected_ids[ $combined_index_key ] = $combined_index_key;
1813
-                }
1814
-            }
1815
-            if (! $model_objs_affected_ids) {
1816
-                // wait wait wait- if nothing was affected let's stop here
1817
-                return 0;
1818
-            }
1819
-            foreach ($model_objs_affected_ids as $id) {
1820
-                $model_obj_in_entity_map = $this->get_from_entity_map($id);
1821
-                if ($model_obj_in_entity_map) {
1822
-                    foreach ($fields_n_values as $field => $new_value) {
1823
-                        $model_obj_in_entity_map->set($field, $new_value);
1824
-                    }
1825
-                }
1826
-            }
1827
-            // if there is a primary key on this model, we can now do a slight optimization
1828
-            if ($this->has_primary_key_field()) {
1829
-                // we already know what we want to update. So let's make the query simpler so it's a little more efficient
1830
-                $query_params = [
1831
-                    [$this->primary_key_name() => ['IN', $model_objs_affected_ids]],
1832
-                    'limit'                    => count($model_objs_affected_ids),
1833
-                    'default_where_conditions' => EEM_Base::default_where_conditions_none,
1834
-                ];
1835
-            }
1836
-        }
1837
-        $model_query_info = $this->_create_model_query_info_carrier($query_params);
1838
-
1839
-        // note: the following query doesn't use _construct_2nd_half_of_select_query()
1840
-        // because it doesn't accept LIMIT, ORDER BY, etc.
1841
-        $rows_affected = $this->_do_wpdb_query(
1842
-            'query',
1843
-            [
1844
-                "UPDATE " . $model_query_info->get_full_join_sql()
1845
-                . " SET " . $this->_construct_update_sql($fields_n_values)
1846
-                . $model_query_info->get_where_sql(),
1847
-            ]
1848
-        );
1849
-
1850
-        /**
1851
-         * Action called after a model update call has been made.
1852
-         *
1853
-         * @param EEM_Base $model
1854
-         * @param array    $fields_n_values the updated fields and their new values
1855
-         * @param array    $query_params    @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1856
-         * @param int      $rows_affected
1857
-         */
1858
-        do_action('AHEE__EEM_Base__update__end', $this, $fields_n_values, $query_params, $rows_affected);
1859
-        return $rows_affected;// how many supposedly got updated
1860
-    }
1861
-
1862
-
1863
-    /**
1864
-     * Analogous to $wpdb->get_col, returns a 1-dimensional array where teh values
1865
-     * are teh values of the field specified (or by default the primary key field)
1866
-     * that matched the query params. Note that you should pass the name of the
1867
-     * model FIELD, not the database table's column name.
1868
-     *
1869
-     * @param array  $query_params @see
1870
-     *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1871
-     * @param string $field_to_select
1872
-     * @return array just like $wpdb->get_col()
1873
-     * @throws EE_Error
1874
-     * @throws ReflectionException
1875
-     */
1876
-    public function get_col($query_params = [], $field_to_select = null)
1877
-    {
1878
-        if ($field_to_select) {
1879
-            $field = $this->field_settings_for($field_to_select);
1880
-        } elseif ($this->has_primary_key_field()) {
1881
-            $field = $this->get_primary_key_field();
1882
-        } else {
1883
-            $field_settings = $this->field_settings();
1884
-            // no primary key, just grab the first column
1885
-            $field = reset($field_settings);
1886
-            // don't need this array now
1887
-            unset($field_settings);
1888
-        }
1889
-        $model_query_info   = $this->_create_model_query_info_carrier($query_params);
1890
-        $select_expressions = $field->get_qualified_column();
1891
-        $SQL                =
1892
-            "SELECT $select_expressions " . $this->_construct_2nd_half_of_select_query($model_query_info);
1893
-        return $this->_do_wpdb_query('get_col', [$SQL]);
1894
-    }
1895
-
1896
-
1897
-    /**
1898
-     * Returns a single column value for a single row from the database
1899
-     *
1900
-     * @param array  $query_params    @see
1901
-     *                                https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1902
-     * @param string $field_to_select @see EEM_Base::get_col()
1903
-     * @return string
1904
-     * @throws EE_Error
1905
-     * @throws ReflectionException
1906
-     */
1907
-    public function get_var($query_params = [], $field_to_select = null)
1908
-    {
1909
-        $query_params['limit'] = 1;
1910
-        $col                   = $this->get_col($query_params, $field_to_select);
1911
-        if (! empty($col)) {
1912
-            return reset($col);
1913
-        }
1914
-        return null;
1915
-    }
1916
-
1917
-
1918
-    /**
1919
-     * Makes the SQL for after "UPDATE table_X inner join table_Y..." and before "...WHERE". Eg "Question.name='party
1920
-     * time?', Question.desc='what do you think?',..." Values are filtered through wpdb->prepare to avoid against SQL
1921
-     * injection, but currently no further filtering is done
1922
-     *
1923
-     * @param array $fields_n_values array keys are field names on this model, and values are what those fields should
1924
-     *                               be updated to in the DB
1925
-     * @return string of SQL
1926
-     * @throws EE_Error
1927
-     * @global      $wpdb
1928
-     */
1929
-    public function _construct_update_sql($fields_n_values)
1930
-    {
1931
-        /** @type WPDB $wpdb */
1932
-        global $wpdb;
1933
-        $cols_n_values = [];
1934
-        foreach ($fields_n_values as $field_name => $value) {
1935
-            $field_obj = $this->field_settings_for($field_name);
1936
-            // if the value is NULL, we want to assign the value to that.
1937
-            // wpdb->prepare doesn't really handle that properly
1938
-            $prepared_value  = $this->_prepare_value_or_use_default($field_obj, $fields_n_values);
1939
-            $value_sql       = $prepared_value === null
1940
-                ? 'NULL'
1941
-                : $wpdb->prepare($field_obj->get_wpdb_data_type(), $prepared_value);
1942
-            $cols_n_values[] = $field_obj->get_qualified_column() . "=" . $value_sql;
1943
-        }
1944
-        return implode(",", $cols_n_values);
1945
-    }
1946
-
1947
-
1948
-    /**
1949
-     * Deletes a single row from the DB given the model object's primary key value. (eg, EE_Attendee->ID()'s value).
1950
-     * Performs a HARD delete, meaning the database row should always be removed,
1951
-     * not just have a flag field on it switched
1952
-     * Wrapper for EEM_Base::delete_permanently()
1953
-     *
1954
-     * @param mixed   $id
1955
-     * @param boolean $allow_blocking
1956
-     * @return int the number of rows deleted
1957
-     * @throws EE_Error
1958
-     * @throws ReflectionException
1959
-     */
1960
-    public function delete_permanently_by_ID($id, $allow_blocking = true)
1961
-    {
1962
-        return $this->delete_permanently(
1963
-            [
1964
-                [$this->get_primary_key_field()->get_name() => $id],
1965
-                'limit' => 1,
1966
-            ],
1967
-            $allow_blocking
1968
-        );
1969
-    }
1970
-
1971
-
1972
-    /**
1973
-     * Deletes a single row from the DB given the model object's primary key value. (eg, EE_Attendee->ID()'s value).
1974
-     * Wrapper for EEM_Base::delete()
1975
-     *
1976
-     * @param mixed   $id
1977
-     * @param boolean $allow_blocking
1978
-     * @return int the number of rows deleted
1979
-     * @throws EE_Error
1980
-     * @throws ReflectionException
1981
-     */
1982
-    public function delete_by_ID($id, $allow_blocking = true)
1983
-    {
1984
-        return $this->delete(
1985
-            [
1986
-                [$this->get_primary_key_field()->get_name() => $id],
1987
-                'limit' => 1,
1988
-            ],
1989
-            $allow_blocking
1990
-        );
1991
-    }
1992
-
1993
-
1994
-    /**
1995
-     * Identical to delete_permanently, but does a "soft" delete if possible,
1996
-     * meaning if the model has a field that indicates its been "trashed" or
1997
-     * "soft deleted", we will just set that instead of actually deleting the rows.
1998
-     *
1999
-     * @param array   $query_params
2000
-     * @param boolean $allow_blocking
2001
-     * @return int how many rows got deleted
2002
-     * @throws EE_Error
2003
-     * @throws ReflectionException
2004
-     * @see EEM_Base::delete_permanently
2005
-     */
2006
-    public function delete($query_params, $allow_blocking = true)
2007
-    {
2008
-        return $this->delete_permanently($query_params, $allow_blocking);
2009
-    }
2010
-
2011
-
2012
-    /**
2013
-     * Deletes the model objects that meet the query params. Note: this method is overridden
2014
-     * in EEM_Soft_Delete_Base so that soft-deleted model objects are instead only flagged
2015
-     * as archived, not actually deleted
2016
-     *
2017
-     * @param array   $query_params   @see
2018
-     *                                https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2019
-     * @param boolean $allow_blocking if TRUE, matched objects will only be deleted if there is no related model info
2020
-     *                                that blocks it (ie, there' sno other data that depends on this data); if false,
2021
-     *                                deletes regardless of other objects which may depend on it. Its generally
2022
-     *                                advisable to always leave this as TRUE, otherwise you could easily corrupt your
2023
-     *                                DB
2024
-     * @return int how many rows got deleted
2025
-     * @throws EE_Error
2026
-     * @throws ReflectionException
2027
-     */
2028
-    public function delete_permanently($query_params, $allow_blocking = true)
2029
-    {
2030
-        /**
2031
-         * Action called just before performing a real deletion query. You can use the
2032
-         * model and its $query_params to find exactly which items will be deleted
2033
-         *
2034
-         * @param EEM_Base $model
2035
-         * @param array    $query_params   @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2036
-         * @param boolean  $allow_blocking whether or not to allow related model objects
2037
-         *                                 to block (prevent) this deletion
2038
-         */
2039
-        do_action('AHEE__EEM_Base__delete__begin', $this, $query_params, $allow_blocking);
2040
-        // some MySQL databases may be running safe mode, which may restrict
2041
-        // deletion if there is no KEY column used in the WHERE statement of a deletion.
2042
-        // to get around this, we first do a SELECT, get all the IDs, and then run another query
2043
-        // to delete them
2044
-        $items_for_deletion           = $this->_get_all_wpdb_results($query_params);
2045
-        $columns_and_ids_for_deleting = $this->_get_ids_for_delete($items_for_deletion, $allow_blocking);
2046
-        $deletion_where_query_part    = $this->_build_query_part_for_deleting_from_columns_and_values(
2047
-            $columns_and_ids_for_deleting
2048
-        );
2049
-        /**
2050
-         * Allows client code to act on the items being deleted before the query is actually executed.
2051
-         *
2052
-         * @param EEM_Base $this                            The model instance being acted on.
2053
-         * @param array    $query_params                    The incoming array of query parameters influencing what gets deleted.
2054
-         * @param bool     $allow_blocking                  @see param description in method phpdoc block.
2055
-         * @param array    $columns_and_ids_for_deleting    An array indicating what entities will get removed as
2056
-         *                                                  derived from the incoming query parameters.
2057
-         * @see details on the structure of this array in the phpdocs
2058
-         *                                                  for the `_get_ids_for_delete_method`
2059
-         */
2060
-        do_action(
2061
-            'AHEE__EEM_Base__delete__before_query',
2062
-            $this,
2063
-            $query_params,
2064
-            $allow_blocking,
2065
-            $columns_and_ids_for_deleting
2066
-        );
2067
-        if ($deletion_where_query_part) {
2068
-            $model_query_info = $this->_create_model_query_info_carrier($query_params);
2069
-            $table_aliases    = array_keys($this->_tables);
2070
-            $SQL              = "DELETE "
2071
-                                . implode(", ", $table_aliases)
2072
-                                . " FROM "
2073
-                                . $model_query_info->get_full_join_sql()
2074
-                                . " WHERE "
2075
-                                . $deletion_where_query_part;
2076
-            $rows_deleted     = $this->_do_wpdb_query('query', [$SQL]);
2077
-        } else {
2078
-            $rows_deleted = 0;
2079
-        }
2080
-
2081
-        // Next, make sure those items are removed from the entity map; if they could be put into it at all; and if
2082
-        // there was no error with the delete query.
2083
-        if (
2084
-            $this->has_primary_key_field()
2085
-            && $rows_deleted !== false
2086
-            && isset($columns_and_ids_for_deleting[ $this->get_primary_key_field()->get_qualified_column() ])
2087
-        ) {
2088
-            $ids_for_removal = $columns_and_ids_for_deleting[ $this->get_primary_key_field()->get_qualified_column() ];
2089
-            foreach ($ids_for_removal as $id) {
2090
-                if (isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])) {
2091
-                    unset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ]);
2092
-                }
2093
-            }
2094
-
2095
-            // delete any extra meta attached to the deleted entities but ONLY if this model is not an instance of
2096
-            // `EEM_Extra_Meta`.  In other words we want to prevent recursion on EEM_Extra_Meta::delete_permanently calls
2097
-            // unnecessarily.  It's very unlikely that users will have assigned Extra Meta to Extra Meta
2098
-            // (although it is possible).
2099
-            // Note this can be skipped by using the provided filter and returning false.
2100
-            if (
2101
-                apply_filters(
2102
-                    'FHEE__EEM_Base__delete_permanently__dont_delete_extra_meta_for_extra_meta',
2103
-                    ! $this instanceof EEM_Extra_Meta,
2104
-                    $this
2105
-                )
2106
-            ) {
2107
-                EEM_Extra_Meta::instance()->delete_permanently(
2108
-                    [
2109
-                        0 => [
2110
-                            'EXM_type' => $this->get_this_model_name(),
2111
-                            'OBJ_ID'   => [
2112
-                                'IN',
2113
-                                $ids_for_removal,
2114
-                            ],
2115
-                        ],
2116
-                    ]
2117
-                );
2118
-            }
2119
-        }
2120
-
2121
-        /**
2122
-         * Action called just after performing a real deletion query. Although at this point the
2123
-         * items should have been deleted
2124
-         *
2125
-         * @param EEM_Base $model
2126
-         * @param array    $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2127
-         * @param int      $rows_deleted
2128
-         */
2129
-        do_action('AHEE__EEM_Base__delete__end', $this, $query_params, $rows_deleted, $columns_and_ids_for_deleting);
2130
-        return $rows_deleted;// how many supposedly got deleted
2131
-    }
2132
-
2133
-
2134
-    /**
2135
-     * Checks all the relations that throw error messages when there are blocking related objects
2136
-     * for related model objects. If there are any related model objects on those relations,
2137
-     * adds an EE_Error, and return true
2138
-     *
2139
-     * @param EE_Base_Class|int $this_model_obj_or_id
2140
-     * @param EE_Base_Class     $ignore_this_model_obj a model object like 'EE_Event', or 'EE_Term_Taxonomy', which
2141
-     *                                                 should be ignored when determining whether there are related
2142
-     *                                                 model objects which block this model object's deletion. Useful
2143
-     *                                                 if you know A is related to B and are considering deleting A,
2144
-     *                                                 but want to see if A has any other objects blocking its deletion
2145
-     *                                                 before removing the relation between A and B
2146
-     * @return boolean
2147
-     * @throws EE_Error
2148
-     * @throws ReflectionException
2149
-     */
2150
-    public function delete_is_blocked_by_related_models($this_model_obj_or_id, $ignore_this_model_obj = null)
2151
-    {
2152
-        // first, if $ignore_this_model_obj was supplied, get its model
2153
-        $ignored_model = $ignore_this_model_obj instanceof EE_Base_Class
2154
-            ? $ignore_this_model_obj->get_model()
2155
-            : null;
2156
-        // now check all the relations of $this_model_obj_or_id and see if there
2157
-        // are any related model objects blocking it?
2158
-        $is_blocked = false;
2159
-        foreach ($this->_model_relations as $relation_name => $relation_obj) {
2160
-            if ($relation_obj->block_delete_if_related_models_exist()) {
2161
-                // if $ignore_this_model_obj was supplied, then for the query
2162
-                // on that model needs to be told to ignore $ignore_this_model_obj
2163
-                if ($ignored_model && $relation_name === $ignored_model->get_this_model_name()) {
2164
-                    $related_model_objects = $relation_obj->get_all_related(
2165
-                        $this_model_obj_or_id,
2166
-                        [
2167
-                            [
2168
-                                $ignored_model->get_primary_key_field()->get_name() => [
2169
-                                    '!=',
2170
-                                    $ignore_this_model_obj->ID(),
2171
-                                ],
2172
-                            ],
2173
-                        ]
2174
-                    );
2175
-                } else {
2176
-                    $related_model_objects = $relation_obj->get_all_related($this_model_obj_or_id);
2177
-                }
2178
-                if ($related_model_objects) {
2179
-                    EE_Error::add_error($relation_obj->get_deletion_error_message(), __FILE__, __FUNCTION__, __LINE__);
2180
-                    $is_blocked = true;
2181
-                }
2182
-            }
2183
-        }
2184
-        return $is_blocked;
2185
-    }
2186
-
2187
-
2188
-    /**
2189
-     * Builds the columns and values for items to delete from the incoming $row_results_for_deleting array.
2190
-     *
2191
-     * @param array $row_results_for_deleting
2192
-     * @param bool  $allow_blocking
2193
-     * @return array   The shape of this array depends on whether the model `has_primary_key_field` or not.  If the
2194
-     *                              model DOES have a primary_key_field, then the array will be a simple single
2195
-     *                              dimension array where the key is the fully qualified primary key column and the
2196
-     *                              value is an array of ids that will be deleted. Example: array('Event.EVT_ID' =>
2197
-     *                              array( 1,2,3)) If the model DOES NOT have a primary_key_field, then the array will
2198
-     *                              be a two dimensional array where each element is a group of columns and values that
2199
-     *                              get deleted. Example: array(
2200
-     *                              0 => array(
2201
-     *                              'Term_Relationship.object_id' => 1
2202
-     *                              'Term_Relationship.term_taxonomy_id' => 5
2203
-     *                              ),
2204
-     *                              1 => array(
2205
-     *                              'Term_Relationship.object_id' => 1
2206
-     *                              'Term_Relationship.term_taxonomy_id' => 6
2207
-     *                              )
2208
-     *                              )
2209
-     * @throws EE_Error
2210
-     * @throws ReflectionException
2211
-     */
2212
-    protected function _get_ids_for_delete(array $row_results_for_deleting, $allow_blocking = true)
2213
-    {
2214
-        $ids_to_delete_indexed_by_column = [];
2215
-        if ($this->has_primary_key_field()) {
2216
-            $primary_table = $this->_get_main_table();
2217
-            // following lines are commented out because the variables were not being used
2218
-            // not deleting because unsure if calls were intentionally causing side effects
2219
-            // $primary_table_pk_field          =
2220
-            //     $this->get_field_by_column($primary_table->get_fully_qualified_pk_column());
2221
-            // $other_tables                    = $this->_get_other_tables();
2222
-            $ids_to_delete_indexed_by_column = $query = [];
2223
-            foreach ($row_results_for_deleting as $item_to_delete) {
2224
-                // before we mark this item for deletion,
2225
-                // make sure there's no related entities blocking its deletion (if we're checking)
2226
-                if (
2227
-                    $allow_blocking
2228
-                    && $this->delete_is_blocked_by_related_models(
2229
-                        $item_to_delete[ $primary_table->get_fully_qualified_pk_column() ]
2230
-                    )
2231
-                ) {
2232
-                    continue;
2233
-                }
2234
-                // primary table deletes
2235
-                if (isset($item_to_delete[ $primary_table->get_fully_qualified_pk_column() ])) {
2236
-                    $ids_to_delete_indexed_by_column[ $primary_table->get_fully_qualified_pk_column() ][] =
2237
-                        $item_to_delete[ $primary_table->get_fully_qualified_pk_column() ];
2238
-                }
2239
-            }
2240
-        } elseif (count($this->get_combined_primary_key_fields()) > 1) {
2241
-            $fields = $this->get_combined_primary_key_fields();
2242
-            foreach ($row_results_for_deleting as $item_to_delete) {
2243
-                $ids_to_delete_indexed_by_column_for_row = [];
2244
-                foreach ($fields as $cpk_field) {
2245
-                    if ($cpk_field instanceof EE_Model_Field_Base) {
2246
-                        $ids_to_delete_indexed_by_column_for_row[ $cpk_field->get_qualified_column() ] =
2247
-                            $item_to_delete[ $cpk_field->get_qualified_column() ];
2248
-                    }
2249
-                }
2250
-                $ids_to_delete_indexed_by_column[] = $ids_to_delete_indexed_by_column_for_row;
2251
-            }
2252
-        } else {
2253
-            // so there's no primary key and no combined key...
2254
-            // sorry, can't help you
2255
-            throw new EE_Error(
2256
-                sprintf(
2257
-                    esc_html__(
2258
-                        "Cannot delete objects of type %s because there is no primary key NOR combined key",
2259
-                        "event_espresso"
2260
-                    ),
2261
-                    get_class($this)
2262
-                )
2263
-            );
2264
-        }
2265
-        return $ids_to_delete_indexed_by_column;
2266
-    }
2267
-
2268
-
2269
-    /**
2270
-     * This receives an array of columns and values set to be deleted (as prepared by _get_ids_for_delete) and prepares
2271
-     * the corresponding query_part for the query performing the delete.
2272
-     *
2273
-     * @param array $ids_to_delete_indexed_by_column @see _get_ids_for_delete for how this array might be shaped.
2274
-     * @return string
2275
-     * @throws EE_Error
2276
-     */
2277
-    protected function _build_query_part_for_deleting_from_columns_and_values(array $ids_to_delete_indexed_by_column)
2278
-    {
2279
-        $query_part = '';
2280
-        if (empty($ids_to_delete_indexed_by_column)) {
2281
-            return $query_part;
2282
-        } elseif ($this->has_primary_key_field()) {
2283
-            $query = [];
2284
-            foreach ($ids_to_delete_indexed_by_column as $column => $ids) {
2285
-                $query[] = $column . ' IN' . $this->_construct_in_value($ids, $this->_primary_key_field);
2286
-            }
2287
-            $query_part = ! empty($query)
2288
-                ? implode(' AND ', $query)
2289
-                : $query_part;
2290
-        } elseif (count($this->get_combined_primary_key_fields()) > 1) {
2291
-            $ways_to_identify_a_row = [];
2292
-            foreach ($ids_to_delete_indexed_by_column as $ids_to_delete_indexed_by_column_for_each_row) {
2293
-                $values_for_each_combined_primary_key_for_a_row = [];
2294
-                foreach ($ids_to_delete_indexed_by_column_for_each_row as $column => $id) {
2295
-                    $values_for_each_combined_primary_key_for_a_row[] = $column . '=' . $id;
2296
-                }
2297
-                $ways_to_identify_a_row[] = '('
2298
-                                            . implode(' AND ', $values_for_each_combined_primary_key_for_a_row)
2299
-                                            . ')';
2300
-            }
2301
-            $query_part = implode(' OR ', $ways_to_identify_a_row);
2302
-        }
2303
-        return $query_part;
2304
-    }
2305
-
2306
-
2307
-    /**
2308
-     * Gets the model field by the fully qualified name
2309
-     *
2310
-     * @param string $qualified_column_name eg 'Event_CPT.post_name' or $field_obj->get_qualified_column()
2311
-     * @return EE_Model_Field_Base
2312
-     * @throws EE_Error
2313
-     * @throws EE_Error
2314
-     */
2315
-    public function get_field_by_column($qualified_column_name)
2316
-    {
2317
-        foreach ($this->field_settings(true) as $field_name => $field_obj) {
2318
-            if ($field_obj->get_qualified_column() === $qualified_column_name) {
2319
-                return $field_obj;
2320
-            }
2321
-        }
2322
-        throw new EE_Error(
2323
-            sprintf(
2324
-                esc_html__('Could not find a field on the model "%1$s" for qualified column "%2$s"', 'event_espresso'),
2325
-                $this->get_this_model_name(),
2326
-                $qualified_column_name
2327
-            )
2328
-        );
2329
-    }
2330
-
2331
-
2332
-    /**
2333
-     * Count all the rows that match criteria the model query params.
2334
-     * If $field_to_count isn't provided, the model's primary key is used. Otherwise, we count by field_to_count's
2335
-     * column
2336
-     *
2337
-     * @param array  $query_params   @see
2338
-     *                               https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2339
-     * @param string $field_to_count field on model to count by (not column name)
2340
-     * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2341
-     *                               that by the setting $distinct to TRUE;
2342
-     * @return int
2343
-     * @throws EE_Error
2344
-     * @throws ReflectionException
2345
-     */
2346
-    public function count($query_params = [], $field_to_count = null, $distinct = false)
2347
-    {
2348
-        $model_query_info = $this->_create_model_query_info_carrier($query_params);
2349
-        if ($field_to_count) {
2350
-            $field_obj       = $this->field_settings_for($field_to_count);
2351
-            $column_to_count = $field_obj->get_qualified_column();
2352
-        } elseif ($this->has_primary_key_field()) {
2353
-            $pk_field_obj    = $this->get_primary_key_field();
2354
-            $column_to_count = $pk_field_obj->get_qualified_column();
2355
-        } else {
2356
-            // there's no primary key
2357
-            // if we're counting distinct items, and there's no primary key,
2358
-            // we need to list out the columns for distinction;
2359
-            // otherwise we can just use star
2360
-            if ($distinct) {
2361
-                $columns_to_use = [];
2362
-                foreach ($this->get_combined_primary_key_fields() as $field_obj) {
2363
-                    $columns_to_use[] = $field_obj->get_qualified_column();
2364
-                }
2365
-                $column_to_count = implode(',', $columns_to_use);
2366
-            } else {
2367
-                $column_to_count = '*';
2368
-            }
2369
-        }
2370
-        $column_to_count = $distinct
2371
-            ? "DISTINCT " . $column_to_count
2372
-            : $column_to_count;
2373
-        $SQL             =
2374
-            "SELECT COUNT(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2375
-        return (int) $this->_do_wpdb_query('get_var', [$SQL]);
2376
-    }
2377
-
2378
-
2379
-    /**
2380
-     * Sums up the value of the $field_to_sum (defaults to the primary key, which isn't terribly useful)
2381
-     *
2382
-     * @param array  $query_params @see
2383
-     *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2384
-     * @param string $field_to_sum name of field (array key in $_fields array)
2385
-     * @return float
2386
-     * @throws EE_Error
2387
-     * @throws ReflectionException
2388
-     */
2389
-    public function sum($query_params, $field_to_sum = null)
2390
-    {
2391
-        $model_query_info = $this->_create_model_query_info_carrier($query_params);
2392
-        if ($field_to_sum) {
2393
-            $field_obj = $this->field_settings_for($field_to_sum);
2394
-        } else {
2395
-            $field_obj = $this->get_primary_key_field();
2396
-        }
2397
-        $column_to_count = $field_obj->get_qualified_column();
2398
-        $SQL             =
2399
-            "SELECT SUM(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2400
-        $return_value    = $this->_do_wpdb_query('get_var', [$SQL]);
2401
-        $data_type       = $field_obj->get_wpdb_data_type();
2402
-        if ($data_type === '%d' || $data_type === '%s') {
2403
-            return (float) $return_value;
2404
-        }
2405
-        // must be %f
2406
-        return (float) $return_value;
2407
-    }
2408
-
2409
-
2410
-    /**
2411
-     * Just calls the specified method on $wpdb with the given arguments
2412
-     * Consolidates a little extra error handling code
2413
-     *
2414
-     * @param string $wpdb_method
2415
-     * @param array  $arguments_to_provide
2416
-     * @return mixed
2417
-     * @throws EE_Error
2418
-     * @global wpdb  $wpdb
2419
-     */
2420
-    protected function _do_wpdb_query($wpdb_method, $arguments_to_provide)
2421
-    {
2422
-        // if we're in maintenance mode level 2, DON'T run any queries
2423
-        // because level 2 indicates the database needs updating and
2424
-        // is probably out of sync with the code
2425
-        if (DbStatus::isOffline()) {
2426
-            throw new RuntimeException(
2427
-                esc_html__(
2428
-                    "Event Espresso Level 2 Maintenance mode is active. That means EE can not run ANY database queries until the necessary migration scripts have run which will take EE out of maintenance mode level 2. Please inform support of this error.",
2429
-                    "event_espresso"
2430
-                )
2431
-            );
2432
-        }
2433
-        /** @type WPDB $wpdb */
2434
-        global $wpdb;
2435
-        if (! method_exists($wpdb, $wpdb_method)) {
2436
-            throw new DomainException(
2437
-                sprintf(
2438
-                    esc_html__(
2439
-                        'There is no method named "%s" on Wordpress\' $wpdb object',
2440
-                        'event_espresso'
2441
-                    ),
2442
-                    $wpdb_method
2443
-                )
2444
-            );
2445
-        }
2446
-        $old_show_errors_value = $wpdb->show_errors;
2447
-        if (WP_DEBUG) {
2448
-            $wpdb->show_errors(false);
2449
-        }
2450
-        $result = $this->_process_wpdb_query($wpdb_method, $arguments_to_provide);
2451
-        $this->show_db_query_if_previously_requested($wpdb->last_query);
2452
-        if (WP_DEBUG) {
2453
-            $wpdb->show_errors($old_show_errors_value);
2454
-            if (! empty($wpdb->last_error)) {
2455
-                throw new EE_Error(sprintf(esc_html__('WPDB Error: "%s"', 'event_espresso'), $wpdb->last_error));
2456
-            }
2457
-            if ($result === false) {
2458
-                throw new EE_Error(
2459
-                    sprintf(
2460
-                        esc_html__(
2461
-                            'WPDB Error occurred, but no error message was logged by wpdb! The wpdb method called was "%1$s" and the arguments were "%2$s"',
2462
-                            'event_espresso'
2463
-                        ),
2464
-                        $wpdb_method,
2465
-                        var_export($arguments_to_provide, true)
2466
-                    )
2467
-                );
2468
-            }
2469
-        } elseif ($result === false) {
2470
-            EE_Error::add_error(
2471
-                sprintf(
2472
-                    esc_html__(
2473
-                        'A database error has occurred. Turn on WP_DEBUG for more information.||A database error occurred doing wpdb method "%1$s", with arguments "%2$s". The error was "%3$s"',
2474
-                        'event_espresso'
2475
-                    ),
2476
-                    $wpdb_method,
2477
-                    var_export($arguments_to_provide, true),
2478
-                    $wpdb->last_error
2479
-                ),
2480
-                __FILE__,
2481
-                __FUNCTION__,
2482
-                __LINE__
2483
-            );
2484
-        }
2485
-        return $result;
2486
-    }
2487
-
2488
-
2489
-    /**
2490
-     * Attempts to run the indicated WPDB method with the provided arguments,
2491
-     * and if there's an error tries to verify the DB is correct. Uses
2492
-     * the static property EEM_Base::$_db_verification_level to determine whether
2493
-     * we should try to fix the EE core db, the addons, or just give up
2494
-     *
2495
-     * @param string $wpdb_method
2496
-     * @param array  $arguments_to_provide
2497
-     * @return mixed
2498
-     */
2499
-    private function _process_wpdb_query($wpdb_method, $arguments_to_provide)
2500
-    {
2501
-        /** @type WPDB $wpdb */
2502
-        global $wpdb;
2503
-        $wpdb->last_error = null;
2504
-        $result           = call_user_func_array([$wpdb, $wpdb_method], $arguments_to_provide);
2505
-        // was there an error running the query? but we don't care on new activations
2506
-        // (we're going to setup the DB anyway on new activations)
2507
-        if (
2508
-            ($result === false || ! empty($wpdb->last_error))
2509
-            && EE_System::instance()->detect_req_type() !== EE_System::req_type_new_activation
2510
-        ) {
2511
-            switch (EEM_Base::$_db_verification_level) {
2512
-                case EEM_Base::db_verified_none:
2513
-                    // let's double-check core's DB
2514
-                    $error_message = $this->_verify_core_db($wpdb_method, $arguments_to_provide);
2515
-                    break;
2516
-                case EEM_Base::db_verified_core:
2517
-                    // STILL NO LOVE?? verify all the addons too. Maybe they need to be fixed
2518
-                    $error_message = $this->_verify_addons_db($wpdb_method, $arguments_to_provide);
2519
-                    break;
2520
-                case EEM_Base::db_verified_addons:
2521
-                    // ummmm... you in trouble
2522
-                    return $result;
2523
-            }
2524
-            if (! empty($error_message)) {
2525
-                EE_Log::instance()->log(__FILE__, __FUNCTION__, $error_message, 'error');
2526
-                trigger_error($error_message);
2527
-            }
2528
-            return $this->_process_wpdb_query($wpdb_method, $arguments_to_provide);
2529
-        }
2530
-        return $result;
2531
-    }
2532
-
2533
-
2534
-    /**
2535
-     * Verifies the EE core database is up-to-date and records that we've done it on
2536
-     * EEM_Base::$_db_verification_level
2537
-     *
2538
-     * @param string $wpdb_method
2539
-     * @param array  $arguments_to_provide
2540
-     * @return string
2541
-     * @throws EE_Error
2542
-     * @throws ReflectionException
2543
-     */
2544
-    private function _verify_core_db($wpdb_method, $arguments_to_provide)
2545
-    {
2546
-        /** @type WPDB $wpdb */
2547
-        global $wpdb;
2548
-        // ok remember that we've already attempted fixing the core db, in case the problem persists
2549
-        EEM_Base::$_db_verification_level = EEM_Base::db_verified_core;
2550
-        $error_message                    = sprintf(
2551
-            esc_html__(
2552
-                'WPDB Error "%1$s" while running wpdb method "%2$s" with arguments %3$s. Automatically attempting to fix EE Core DB',
2553
-                'event_espresso'
2554
-            ),
2555
-            $wpdb->last_error,
2556
-            $wpdb_method,
2557
-            wp_json_encode($arguments_to_provide)
2558
-        );
2559
-        EE_System::instance()->initialize_db_if_no_migrations_required(false, true);
2560
-        return $error_message;
2561
-    }
2562
-
2563
-
2564
-    /**
2565
-     * Verifies the EE addons' database is up-to-date and records that we've done it on
2566
-     * EEM_Base::$_db_verification_level
2567
-     *
2568
-     * @param $wpdb_method
2569
-     * @param $arguments_to_provide
2570
-     * @return string
2571
-     * @throws EE_Error
2572
-     * @throws ReflectionException
2573
-     */
2574
-    private function _verify_addons_db($wpdb_method, $arguments_to_provide)
2575
-    {
2576
-        /** @type WPDB $wpdb */
2577
-        global $wpdb;
2578
-        // ok remember that we've already attempted fixing the addons dbs, in case the problem persists
2579
-        EEM_Base::$_db_verification_level = EEM_Base::db_verified_addons;
2580
-        $error_message                    = sprintf(
2581
-            esc_html__(
2582
-                'WPDB AGAIN: Error "%1$s" while running the same method and arguments as before. Automatically attempting to fix EE Addons DB',
2583
-                'event_espresso'
2584
-            ),
2585
-            $wpdb->last_error,
2586
-            $wpdb_method,
2587
-            wp_json_encode($arguments_to_provide)
2588
-        );
2589
-        EE_System::instance()->initialize_addons();
2590
-        return $error_message;
2591
-    }
2592
-
2593
-
2594
-    /**
2595
-     * In order to avoid repeating this code for the get_all, sum, and count functions, put the code parts
2596
-     * that are identical in here. Returns a string of SQL of everything in a SELECT query except the beginning
2597
-     * SELECT clause, eg " FROM wp_posts AS Event INNER JOIN ... WHERE ... ORDER BY ... LIMIT ... GROUP BY ... HAVING
2598
-     * ..."
2599
-     *
2600
-     * @param EE_Model_Query_Info_Carrier $model_query_info
2601
-     * @return string
2602
-     */
2603
-    private function _construct_2nd_half_of_select_query(EE_Model_Query_Info_Carrier $model_query_info)
2604
-    {
2605
-        return " FROM " . $model_query_info->get_full_join_sql() .
2606
-               $model_query_info->get_where_sql() .
2607
-               $model_query_info->get_group_by_sql() .
2608
-               $model_query_info->get_having_sql() .
2609
-               $model_query_info->get_order_by_sql() .
2610
-               $model_query_info->get_limit_sql();
2611
-    }
2612
-
2613
-
2614
-    /**
2615
-     * Set to easily debug the next X queries ran from this model.
2616
-     *
2617
-     * @param int $count
2618
-     */
2619
-    public function show_next_x_db_queries($count = 1)
2620
-    {
2621
-        $this->_show_next_x_db_queries = $count;
2622
-    }
2623
-
2624
-
2625
-    /**
2626
-     * @param $sql_query
2627
-     */
2628
-    public function show_db_query_if_previously_requested($sql_query)
2629
-    {
2630
-        if ($this->_show_next_x_db_queries > 0) {
2631
-            $left = is_admin() ? '12rem' : '2rem';
2632
-            echo "
42
+	/**
43
+	 * Flag to indicate whether the values provided to EEM_Base have already been prepared
44
+	 * by the model object or not (ie, the model object has used the field's _prepare_for_set function on the values).
45
+	 * They almost always WILL NOT, but it's not necessarily a requirement.
46
+	 * For example, if you want to run EEM_Event::instance()->get_all(array(array('EVT_ID'=>$_GET['event_id'])));
47
+	 *
48
+	 * @var boolean
49
+	 */
50
+	private $_values_already_prepared_by_model_object = 0;
51
+
52
+	/**
53
+	 * when $_values_already_prepared_by_model_object equals this, we assume
54
+	 * the data is just like form input that needs to have the model fields'
55
+	 * prepare_for_set and prepare_for_use_in_db called on it
56
+	 */
57
+	const not_prepared_by_model_object = 0;
58
+
59
+	/**
60
+	 * when $_values_already_prepared_by_model_object equals this, we
61
+	 * assume this value is coming from a model object and doesn't need to have
62
+	 * prepare_for_set called on it, just prepare_for_use_in_db is used
63
+	 */
64
+	const prepared_by_model_object = 1;
65
+
66
+	/**
67
+	 * when $_values_already_prepared_by_model_object equals this, we assume
68
+	 * the values are already to be used in the database (ie no processing is done
69
+	 * on them by the model's fields)
70
+	 */
71
+	const prepared_for_use_in_db = 2;
72
+
73
+
74
+	protected $singular_item = 'Item';
75
+
76
+	protected $plural_item   = 'Items';
77
+
78
+	/**
79
+	 * @type EE_Table_Base[] $_tables array of EE_Table objects for defining which tables comprise this model.
80
+	 */
81
+	protected $_tables;
82
+
83
+	/**
84
+	 * with two levels: top-level has array keys which are database table aliases (ie, keys in _tables)
85
+	 * and the value is an array. Each of those sub-arrays have keys of field names (eg 'ATT_ID', which should also be
86
+	 * variable names on the model objects (eg, EE_Attendee), and the keys should be children of EE_Model_Field
87
+	 *
88
+	 * @var EE_Model_Field_Base[][] $_fields
89
+	 */
90
+	protected $_fields;
91
+
92
+	/**
93
+	 * array of different kinds of relations
94
+	 *
95
+	 * @var EE_Model_Relation_Base[] $_model_relations
96
+	 */
97
+	protected $_model_relations = [];
98
+
99
+	/**
100
+	 * @var EE_Index[] $_indexes
101
+	 */
102
+	protected $_indexes = [];
103
+
104
+	/**
105
+	 * Default strategy for getting where conditions on this model. This strategy is used to get default
106
+	 * where conditions which are added to get_all, update, and delete queries. They can be overridden
107
+	 * by setting the same columns as used in these queries in the query yourself.
108
+	 *
109
+	 * @var EE_Default_Where_Conditions
110
+	 */
111
+	protected $_default_where_conditions_strategy;
112
+
113
+	/**
114
+	 * Strategy for getting conditions on this model when 'default_where_conditions' equals 'minimum'.
115
+	 * This is particularly useful when you want something between 'none' and 'default'
116
+	 *
117
+	 * @var EE_Default_Where_Conditions
118
+	 */
119
+	protected $_minimum_where_conditions_strategy;
120
+
121
+	/**
122
+	 * String describing how to find the "owner" of this model's objects.
123
+	 * When there is a foreign key on this model to the wp_users table, this isn't needed.
124
+	 * But when there isn't, this indicates which related model, or transiently-related model,
125
+	 * has the foreign key to the wp_users table.
126
+	 * Eg, for EEM_Registration this would be 'Event' because registrations are directly
127
+	 * related to events, and events have a foreign key to wp_users.
128
+	 * On EEM_Transaction, this would be 'Transaction.Event'
129
+	 *
130
+	 * @var string
131
+	 */
132
+	protected $_model_chain_to_wp_user = '';
133
+
134
+	/**
135
+	 * String describing how to find the model with a password controlling access to this model. This property has the
136
+	 * same format as $_model_chain_to_wp_user. This is primarily used by the query param "exclude_protected".
137
+	 * This value is the path of models to follow to arrive at the model with the password field.
138
+	 * If it is an empty string, it means this model has the password field. If it is null, it means there is no
139
+	 * model with a password that should affect reading this on the front-end.
140
+	 * Eg this is an empty string for the Event model because it has a password.
141
+	 * This is null for the Registration model, because its event's password has no bearing on whether
142
+	 * you can read the registration or not on the front-end (it just depends on your capabilities.)
143
+	 * This is 'Datetime.Event' on the Ticket model, because model queries for tickets that set "exclude_protected"
144
+	 * should hide tickets for datetimes for events that have a password set.
145
+	 *
146
+	 * @var string |null
147
+	 */
148
+	protected $model_chain_to_password = null;
149
+
150
+	/**
151
+	 * This is a flag typically set by updates so that we don't load the where strategy on updates because updates
152
+	 * don't need it (particularly CPT models)
153
+	 *
154
+	 * @var bool
155
+	 */
156
+	protected $_ignore_where_strategy = false;
157
+
158
+	/**
159
+	 * String used in caps relating to this model. Eg, if the caps relating to this
160
+	 * model are 'ee_edit_events', 'ee_read_events', etc, it would be 'events'.
161
+	 *
162
+	 * @var string. If null it hasn't been initialized yet. If false then we
163
+	 * have indicated capabilities don't apply to this
164
+	 */
165
+	protected $_caps_slug = null;
166
+
167
+	/**
168
+	 * 2d array where top-level keys are one of EEM_Base::valid_cap_contexts(),
169
+	 * and next-level keys are capability names, and each's value is a
170
+	 * EE_Default_Where_Condition. If the requester requests to apply caps to the query,
171
+	 * they specify which context to use (ie, frontend, backend, edit or delete)
172
+	 * and then each capability in the corresponding sub-array that they're missing
173
+	 * adds the where conditions onto the query.
174
+	 *
175
+	 * @var array
176
+	 */
177
+	protected $_cap_restrictions = [
178
+		self::caps_read       => [],
179
+		self::caps_read_admin => [],
180
+		self::caps_edit       => [],
181
+		self::caps_delete     => [],
182
+	];
183
+
184
+	/**
185
+	 * Array defining which cap restriction generators to use to create default
186
+	 * cap restrictions to put in EEM_Base::_cap_restrictions.
187
+	 * Array-keys are one of EEM_Base::valid_cap_contexts(), and values are a child of
188
+	 * EE_Restriction_Generator_Base. If you don't want any cap restrictions generated
189
+	 * automatically set this to false (not just null).
190
+	 *
191
+	 * @var EE_Restriction_Generator_Base[]
192
+	 */
193
+	protected $_cap_restriction_generators = [];
194
+
195
+	/**
196
+	 * constants used to categorize capability restrictions on EEM_Base::_caps_restrictions
197
+	 */
198
+	const caps_read       = 'read';
199
+
200
+	const caps_read_admin = 'read_admin';
201
+
202
+	const caps_edit       = 'edit';
203
+
204
+	const caps_delete     = 'delete';
205
+
206
+	/**
207
+	 * Keys are all the cap contexts (ie constants EEM_Base::_caps_*) and values are their 'action'
208
+	 * as how they'd be used in capability names. Eg EEM_Base::caps_read ('read_frontend')
209
+	 * maps to 'read' because when looking for relevant permissions we're going to use
210
+	 * 'read' in teh capabilities names like 'ee_read_events' etc.
211
+	 *
212
+	 * @var array
213
+	 */
214
+	protected $_cap_contexts_to_cap_action_map = [
215
+		self::caps_read       => 'read',
216
+		self::caps_read_admin => 'read',
217
+		self::caps_edit       => 'edit',
218
+		self::caps_delete     => 'delete',
219
+	];
220
+
221
+	/**
222
+	 * Timezone
223
+	 * This gets set via the constructor so that we know what timezone incoming strings|timestamps are in when there
224
+	 * are EE_Datetime_Fields in use.  This can also be used before a get to set what timezone you want strings coming
225
+	 * out of the created objects.  NOT all EEM_Base child classes use this property but any that use a
226
+	 * EE_Datetime_Field data type will have access to it.
227
+	 *
228
+	 * @var string
229
+	 */
230
+	protected $_timezone;
231
+
232
+
233
+	/**
234
+	 * This holds the id of the blog currently making the query.  Has no bearing on single site but is used for
235
+	 * multisite.
236
+	 *
237
+	 * @var int
238
+	 */
239
+	protected static $_model_query_blog_id;
240
+
241
+	/**
242
+	 * A copy of _fields, except the array keys are the model names pointed to by
243
+	 * the field
244
+	 *
245
+	 * @var EE_Model_Field_Base[]
246
+	 */
247
+	private $_cache_foreign_key_to_fields = [];
248
+
249
+	/**
250
+	 * Cached list of all the fields on the model, indexed by their name
251
+	 *
252
+	 * @var EE_Model_Field_Base[]
253
+	 */
254
+	private $_cached_fields = null;
255
+
256
+	/**
257
+	 * Cached list of all the fields on the model, except those that are
258
+	 * marked as only pertinent to the database
259
+	 *
260
+	 * @var EE_Model_Field_Base[]
261
+	 */
262
+	private $_cached_fields_non_db_only = null;
263
+
264
+	/**
265
+	 * A cached reference to the primary key for quick lookup
266
+	 *
267
+	 * @var EE_Model_Field_Base
268
+	 */
269
+	private $_primary_key_field = null;
270
+
271
+	/**
272
+	 * Flag indicating whether this model has a primary key or not
273
+	 *
274
+	 * @var boolean
275
+	 */
276
+	protected $_has_primary_key_field = null;
277
+
278
+	/**
279
+	 * array in the format:  [ FK alias => full PK ]
280
+	 * where keys are local column name aliases for foreign keys
281
+	 * and values are the fully qualified column name for the primary key they represent
282
+	 *  ex:
283
+	 *      [ 'Event.EVT_wp_user' => 'WP_User.ID' ]
284
+	 *
285
+	 * @var array $foreign_key_aliases
286
+	 */
287
+	protected $foreign_key_aliases = [];
288
+
289
+	/**
290
+	 * Whether or not this model is based off a table in WP core only (CPTs should set
291
+	 * this to FALSE, but if we were to make an EE_WP_Post model, it should set this to true).
292
+	 * This should be true for models that deal with data that should exist independent of EE.
293
+	 * For example, if the model can read and insert data that isn't used by EE, this should be true.
294
+	 * It would be false, however, if you could guarantee the model would only interact with EE data,
295
+	 * even if it uses a WP core table (eg event and venue models set this to false for that reason:
296
+	 * they can only read and insert events and venues custom post types, not arbitrary post types)
297
+	 *
298
+	 * @var boolean
299
+	 */
300
+	protected $_wp_core_model = false;
301
+
302
+	/**
303
+	 * @var bool stores whether this model has a password field or not.
304
+	 * null until initialized by hasPasswordField()
305
+	 */
306
+	protected $has_password_field;
307
+
308
+	/**
309
+	 * @var EE_Password_Field|null Automatically set when calling getPasswordField()
310
+	 */
311
+	protected $password_field;
312
+
313
+	/**
314
+	 *    List of valid operators that can be used for querying.
315
+	 * The keys are all operators we'll accept, the values are the real SQL
316
+	 * operators used
317
+	 *
318
+	 * @var array
319
+	 */
320
+	protected $_valid_operators = [
321
+		'='           => '=',
322
+		'<='          => '<=',
323
+		'<'           => '<',
324
+		'>='          => '>=',
325
+		'>'           => '>',
326
+		'!='          => '!=',
327
+		'LIKE'        => 'LIKE',
328
+		'like'        => 'LIKE',
329
+		'NOT_LIKE'    => 'NOT LIKE',
330
+		'not_like'    => 'NOT LIKE',
331
+		'NOT LIKE'    => 'NOT LIKE',
332
+		'not like'    => 'NOT LIKE',
333
+		'IN'          => 'IN',
334
+		'in'          => 'IN',
335
+		'NOT_IN'      => 'NOT IN',
336
+		'not_in'      => 'NOT IN',
337
+		'NOT IN'      => 'NOT IN',
338
+		'not in'      => 'NOT IN',
339
+		'between'     => 'BETWEEN',
340
+		'BETWEEN'     => 'BETWEEN',
341
+		'IS_NOT_NULL' => 'IS NOT NULL',
342
+		'is_not_null' => 'IS NOT NULL',
343
+		'IS NOT NULL' => 'IS NOT NULL',
344
+		'is not null' => 'IS NOT NULL',
345
+		'IS_NULL'     => 'IS NULL',
346
+		'is_null'     => 'IS NULL',
347
+		'IS NULL'     => 'IS NULL',
348
+		'is null'     => 'IS NULL',
349
+		'REGEXP'      => 'REGEXP',
350
+		'regexp'      => 'REGEXP',
351
+		'NOT_REGEXP'  => 'NOT REGEXP',
352
+		'not_regexp'  => 'NOT REGEXP',
353
+		'NOT REGEXP'  => 'NOT REGEXP',
354
+		'not regexp'  => 'NOT REGEXP',
355
+	];
356
+
357
+	/**
358
+	 * operators that work like 'IN', accepting a comma-separated list of values inside brackets. Eg '(1,2,3)'
359
+	 *
360
+	 * @var array
361
+	 */
362
+	protected $_in_style_operators = ['IN', 'NOT IN'];
363
+
364
+	/**
365
+	 * operators that work like 'BETWEEN'.  Typically used for datetime calculations, i.e. "BETWEEN '12-1-2011' AND
366
+	 * '12-31-2012'"
367
+	 *
368
+	 * @var array
369
+	 */
370
+	protected $_between_style_operators = ['BETWEEN'];
371
+
372
+	/**
373
+	 * Operators that work like SQL's like: input should be assumed to be a string, already prepared for a LIKE query.
374
+	 *
375
+	 * @var array
376
+	 */
377
+	protected $_like_style_operators = ['LIKE', 'NOT LIKE'];
378
+
379
+	/**
380
+	 * operators that are used for handling NUll and !NULL queries.  Typically used for when checking if a row exists
381
+	 * on a join table.
382
+	 *
383
+	 * @var array
384
+	 */
385
+	protected $_null_style_operators = ['IS NOT NULL', 'IS NULL'];
386
+
387
+	/**
388
+	 * Allowed values for $query_params['order'] for ordering in queries
389
+	 *
390
+	 * @var array
391
+	 */
392
+	protected $_allowed_order_values = ['asc', 'desc', 'ASC', 'DESC'];
393
+
394
+	/**
395
+	 * When these are keys in a WHERE or HAVING clause, they are handled much differently
396
+	 * than regular field names. It is assumed that their values are an array of WHERE conditions
397
+	 *
398
+	 * @var array
399
+	 */
400
+	private $_logic_query_param_keys = ['not', 'and', 'or', 'NOT', 'AND', 'OR'];
401
+
402
+	/**
403
+	 * Allowed keys in $query_params arrays passed into queries. Note that 0 is meant to always be a
404
+	 * 'where', but 'where' clauses are so common that we thought we'd omit it
405
+	 *
406
+	 * @var array
407
+	 */
408
+	private $_allowed_query_params = [
409
+		0,
410
+		'limit',
411
+		'order_by',
412
+		'group_by',
413
+		'having',
414
+		'force_join',
415
+		'order',
416
+		'on_join_limit',
417
+		'default_where_conditions',
418
+		'caps',
419
+		'extra_selects',
420
+		'exclude_protected',
421
+	];
422
+
423
+	/**
424
+	 * All the data types that can be used in $wpdb->prepare statements.
425
+	 *
426
+	 * @var array
427
+	 */
428
+	private $_valid_wpdb_data_types = ['%d', '%s', '%f'];
429
+
430
+	/**
431
+	 * @var EE_Registry $EE
432
+	 */
433
+	protected $EE = null;
434
+
435
+
436
+	/**
437
+	 * Property which, when set, will have this model echo out the next X queries to the page for debugging.
438
+	 *
439
+	 * @var int
440
+	 */
441
+	protected $_show_next_x_db_queries = 0;
442
+
443
+	/**
444
+	 * When using _get_all_wpdb_results, you can specify a custom selection. If you do so,
445
+	 * it gets saved on this property as an instance of CustomSelects so those selections can be used in
446
+	 * WHERE, GROUP_BY, etc.
447
+	 *
448
+	 * @var CustomSelects
449
+	 */
450
+	protected $_custom_selections = [];
451
+
452
+	/**
453
+	 * key => value Entity Map using  array( EEM_Base::$_model_query_blog_id => array( ID => model object ) )
454
+	 * caches every model object we've fetched from the DB on this request
455
+	 *
456
+	 * @var array
457
+	 */
458
+	protected $_entity_map;
459
+
460
+	/**
461
+	 * @var LoaderInterface
462
+	 */
463
+	protected static $loader;
464
+
465
+	/**
466
+	 * @var Mirror
467
+	 */
468
+	private static $mirror;
469
+
470
+
471
+	/**
472
+	 * constant used to show EEM_Base has not yet verified the db on this http request
473
+	 */
474
+	const db_verified_none = 0;
475
+
476
+	/**
477
+	 * constant used to show EEM_Base has verified the EE core db on this http request,
478
+	 * but not the addons' dbs
479
+	 */
480
+	const db_verified_core = 1;
481
+
482
+	/**
483
+	 * constant used to show EEM_Base has verified the addons' dbs (and implicitly
484
+	 * the EE core db too)
485
+	 */
486
+	const db_verified_addons = 2;
487
+
488
+	/**
489
+	 * indicates whether an EEM_Base child has already re-verified the DB
490
+	 * is ok (we don't want to do it repetitively). Should be set to one the constants
491
+	 * looking like EEM_Base::db_verified_*
492
+	 *
493
+	 * @var int - 0 = none, 1 = core, 2 = addons
494
+	 */
495
+	protected static $_db_verification_level = EEM_Base::db_verified_none;
496
+
497
+	/**
498
+	 * @const constant for 'default_where_conditions' to apply default where conditions to ALL queried models
499
+	 *        (eg, if retrieving registrations ordered by their datetimes, this will only return non-trashed
500
+	 *        registrations for non-trashed tickets for non-trashed datetimes)
501
+	 */
502
+	const default_where_conditions_all = 'all';
503
+
504
+	/**
505
+	 * @const constant for 'default_where_conditions' to apply default where conditions to THIS model only, but
506
+	 *        no other models which are joined to (eg, if retrieving registrations ordered by their datetimes, this will
507
+	 *        return non-trashed registrations, regardless of the related datetimes and tickets' statuses).
508
+	 *        It is preferred to use EEM_Base::default_where_conditions_minimum_others because, when joining to
509
+	 *        models which share tables with other models, this can return data for the wrong model.
510
+	 */
511
+	const default_where_conditions_this_only = 'this_model_only';
512
+
513
+	/**
514
+	 * @const constant for 'default_where_conditions' to apply default where conditions to other models queried,
515
+	 *        but not the current model (eg, if retrieving registrations ordered by their datetimes, this will
516
+	 *        return all registrations related to non-trashed tickets and non-trashed datetimes)
517
+	 */
518
+	const default_where_conditions_others_only = 'other_models_only';
519
+
520
+	/**
521
+	 * @const constant for 'default_where_conditions' to apply minimum where conditions to all models queried.
522
+	 *        For most models this the same as EEM_Base::default_where_conditions_none, except for models which share
523
+	 *        their table with other models, like the Event and Venue models. For example, when querying for events
524
+	 *        ordered by their venues' name, this will be sure to only return real events with associated real venues
525
+	 *        (regardless of whether those events and venues are trashed)
526
+	 *        In contrast, using EEM_Base::default_where_conditions_none would could return WP posts other than EE
527
+	 *        events.
528
+	 */
529
+	const default_where_conditions_minimum_all = 'minimum';
530
+
531
+	/**
532
+	 * @const constant for 'default_where_conditions' to apply apply where conditions to other models, and full default
533
+	 *        where conditions for the queried model (eg, when querying events ordered by venues' names, this will
534
+	 *        return non-trashed events for any venues, regardless of whether those associated venues are trashed or
535
+	 *        not)
536
+	 */
537
+	const default_where_conditions_minimum_others = 'full_this_minimum_others';
538
+
539
+	/**
540
+	 * @const constant for 'default_where_conditions' to NOT apply any where conditions. This should very rarely be
541
+	 *        used, because when querying from a model which shares its table with another model (eg Events and Venues)
542
+	 *        it's possible it will return table entries for other models. You should use
543
+	 *        EEM_Base::default_where_conditions_minimum_all instead.
544
+	 */
545
+	const default_where_conditions_none = 'none';
546
+
547
+
548
+	/**
549
+	 * About all child constructors:
550
+	 * they should define the _tables, _fields and _model_relations arrays.
551
+	 * Should ALWAYS be called after child constructor.
552
+	 * In order to make the child constructors to be as simple as possible, this parent constructor
553
+	 * finalizes constructing all the object's attributes.
554
+	 * Generally, rather than requiring a child to code
555
+	 * $this->_tables = array(
556
+	 *        'Event_Post_Table' => new EE_Table('Event_Post_Table','wp_posts')
557
+	 *        ...);
558
+	 *  (thus repeating itself in the array key and in the constructor of the new EE_Table,)
559
+	 * each EE_Table has a function to set the table's alias after the constructor, using
560
+	 * the array key ('Event_Post_Table'), instead of repeating it. The model fields and model relations
561
+	 * do something similar.
562
+	 *
563
+	 * @param string|null $timezone
564
+	 * @throws EE_Error
565
+	 * @throws Exception
566
+	 */
567
+	protected function __construct($timezone = '')
568
+	{
569
+		// check that the model has not been loaded too soon
570
+		if (! did_action('AHEE__EE_System__load_espresso_addons')) {
571
+			throw new EE_Error(
572
+				sprintf(
573
+					esc_html__(
574
+						'The %1$s model can not be loaded before the "AHEE__EE_System__load_espresso_addons" hook has been called. This gives other addons a chance to extend this model.',
575
+						'event_espresso'
576
+					),
577
+					get_class($this)
578
+				)
579
+			);
580
+		}
581
+		/**
582
+		 * Set blogid for models to current blog. However we ONLY do this if $_model_query_blog_id is not already set.
583
+		 */
584
+		if (empty(EEM_Base::$_model_query_blog_id)) {
585
+			EEM_Base::set_model_query_blog_id();
586
+		}
587
+		/**
588
+		 * Filters the list of tables on a model. It is best to NOT use this directly and instead
589
+		 * just use EE_Register_Model_Extension
590
+		 *
591
+		 * @var EE_Table_Base[] $_tables
592
+		 */
593
+		$this->_tables = (array) apply_filters('FHEE__' . get_class($this) . '__construct__tables', $this->_tables);
594
+		foreach ($this->_tables as $table_alias => $table_obj) {
595
+			/** @var $table_obj EE_Table_Base */
596
+			$table_obj->_construct_finalize_with_alias($table_alias);
597
+			if ($table_obj instanceof EE_Secondary_Table) {
598
+				$table_obj->_construct_finalize_set_table_to_join_with($this->_get_main_table());
599
+			}
600
+		}
601
+		/**
602
+		 * Filters the list of fields on a model. It is best to NOT use this directly and instead just use
603
+		 * EE_Register_Model_Extension
604
+		 *
605
+		 * @param EE_Model_Field_Base[] $_fields
606
+		 */
607
+		$this->_fields = (array) apply_filters('FHEE__' . get_class($this) . '__construct__fields', $this->_fields);
608
+		$this->_invalidate_field_caches();
609
+		foreach ($this->_fields as $table_alias => $fields_for_table) {
610
+			if (! array_key_exists($table_alias, $this->_tables)) {
611
+				throw new EE_Error(
612
+					sprintf(
613
+						esc_html__(
614
+							"Table alias %s does not exist in EEM_Base child's _tables array. Only tables defined are %s",
615
+							'event_espresso'
616
+						),
617
+						$table_alias,
618
+						implode(",", $this->_fields)
619
+					)
620
+				);
621
+			}
622
+			foreach ($fields_for_table as $field_name => $field_obj) {
623
+				/** @var $field_obj EE_Model_Field_Base | EE_Primary_Key_Field_Base */
624
+				// primary key field base has a slightly different _construct_finalize
625
+				/** @var $field_obj EE_Model_Field_Base */
626
+				$field_obj->_construct_finalize($table_alias, $field_name, $this->get_this_model_name());
627
+			}
628
+		}
629
+		// everything is related to Extra_Meta
630
+		if (get_class($this) !== 'EEM_Extra_Meta') {
631
+			// make extra meta related to everything, but don't block deleting things just
632
+			// because they have related extra meta info. For now just orphan those extra meta
633
+			// in the future we should automatically delete them
634
+			$this->_model_relations['Extra_Meta'] = new EE_Has_Many_Any_Relation(false);
635
+		}
636
+		// and change logs
637
+		if (get_class($this) !== 'EEM_Change_Log') {
638
+			$this->_model_relations['Change_Log'] = new EE_Has_Many_Any_Relation(false);
639
+		}
640
+		/**
641
+		 * Filters the list of relations on a model. It is best to NOT use this directly and instead just use
642
+		 * EE_Register_Model_Extension
643
+		 *
644
+		 * @param EE_Model_Relation_Base[] $_model_relations
645
+		 */
646
+		$this->_model_relations = (array) apply_filters(
647
+			'FHEE__' . get_class($this) . '__construct__model_relations',
648
+			$this->_model_relations
649
+		);
650
+		foreach ($this->_model_relations as $model_name => $relation_obj) {
651
+			/** @var $relation_obj EE_Model_Relation_Base */
652
+			$relation_obj->_construct_finalize_set_models($this->get_this_model_name(), $model_name);
653
+		}
654
+		foreach ($this->_indexes as $index_name => $index_obj) {
655
+			$index_obj->_construct_finalize($index_name, $this->get_this_model_name());
656
+		}
657
+		$this->set_timezone($timezone);
658
+		// finalize default where condition strategy, or set default
659
+		if (! $this->_default_where_conditions_strategy) {
660
+			// nothing was set during child constructor, so set default
661
+			$this->_default_where_conditions_strategy = new EE_Default_Where_Conditions();
662
+		}
663
+		$this->_default_where_conditions_strategy->_finalize_construct($this);
664
+		if (! $this->_minimum_where_conditions_strategy) {
665
+			// nothing was set during child constructor, so set default
666
+			$this->_minimum_where_conditions_strategy = new EE_Default_Where_Conditions();
667
+		}
668
+		$this->_minimum_where_conditions_strategy->_finalize_construct($this);
669
+		// if the cap slug hasn't been set, and we haven't set it to false on purpose
670
+		// to indicate to NOT set it, set it to the logical default
671
+		if ($this->_caps_slug === null) {
672
+			$this->_caps_slug = EEH_Inflector::pluralize_and_lower($this->get_this_model_name());
673
+		}
674
+		// initialize the standard cap restriction generators if none were specified by the child constructor
675
+		if (is_array($this->_cap_restriction_generators)) {
676
+			foreach ($this->cap_contexts_to_cap_action_map() as $cap_context => $action) {
677
+				if (! isset($this->_cap_restriction_generators[ $cap_context ])) {
678
+					$this->_cap_restriction_generators[ $cap_context ] = apply_filters(
679
+						'FHEE__EEM_Base___construct__standard_cap_restriction_generator',
680
+						new EE_Restriction_Generator_Protected(),
681
+						$cap_context,
682
+						$this
683
+					);
684
+				}
685
+			}
686
+		}
687
+		// if there are cap restriction generators, use them to make the default cap restrictions
688
+		if (is_array($this->_cap_restriction_generators)) {
689
+			foreach ($this->_cap_restriction_generators as $context => $generator_object) {
690
+				if (! $generator_object) {
691
+					continue;
692
+				}
693
+				if (! $generator_object instanceof EE_Restriction_Generator_Base) {
694
+					throw new EE_Error(
695
+						sprintf(
696
+							esc_html__(
697
+								'Index "%1$s" in the model %2$s\'s _cap_restriction_generators is not a child of EE_Restriction_Generator_Base. It should be that or NULL.',
698
+								'event_espresso'
699
+							),
700
+							$context,
701
+							$this->get_this_model_name()
702
+						)
703
+					);
704
+				}
705
+				$action = $this->cap_action_for_context($context);
706
+				if (! $generator_object->construction_finalized()) {
707
+					$generator_object->_construct_finalize($this, $action);
708
+				}
709
+			}
710
+		}
711
+		do_action('AHEE__' . get_class($this) . '__construct__end');
712
+	}
713
+
714
+
715
+	/**
716
+	 * @return LoaderInterface
717
+	 * @throws InvalidArgumentException
718
+	 * @throws InvalidDataTypeException
719
+	 * @throws InvalidInterfaceException
720
+	 */
721
+	protected static function getLoader(): LoaderInterface
722
+	{
723
+		if (! EEM_Base::$loader instanceof LoaderInterface) {
724
+			EEM_Base::$loader = LoaderFactory::getLoader();
725
+		}
726
+		return EEM_Base::$loader;
727
+	}
728
+
729
+
730
+	/**
731
+	 * @return Mirror
732
+	 * @since   5.0.0.p
733
+	 */
734
+	private static function getMirror(): Mirror
735
+	{
736
+		if (! EEM_Base::$mirror instanceof Mirror) {
737
+			EEM_Base::$mirror = EEM_Base::getLoader()->getShared(Mirror::class);
738
+		}
739
+		return EEM_Base::$mirror;
740
+	}
741
+
742
+
743
+	/**
744
+	 * @param string $model_class_Name
745
+	 * @param string $timezone
746
+	 * @return array
747
+	 * @throws ReflectionException
748
+	 * @since   5.0.0.p
749
+	 */
750
+	private static function getModelArguments(string $model_class_Name, string $timezone): array
751
+	{
752
+		$arguments = [$timezone];
753
+		$params    = EEM_Base::getMirror()->getParameters($model_class_Name);
754
+		if (count($params) > 1) {
755
+			if ($params[1]->getName() === 'model_field_factory') {
756
+				$arguments = [
757
+					$timezone,
758
+					EEM_Base::getLoader()->getShared(ModelFieldFactory::class),
759
+				];
760
+			} elseif ($model_class_Name === 'EEM_Form_Section') {
761
+				$arguments = [
762
+					EEM_Base::getLoader()->getShared('EventEspresso\core\services\form\meta\FormStatus'),
763
+					$timezone,
764
+				];
765
+			} elseif ($model_class_Name === 'EEM_Form_Element') {
766
+				$arguments = [
767
+					EEM_Base::getLoader()->getShared('EventEspresso\core\services\form\meta\FormStatus'),
768
+					EEM_Base::getLoader()->getShared('EventEspresso\core\services\form\meta\InputTypes'),
769
+					$timezone,
770
+				];
771
+			}
772
+		}
773
+		return $arguments;
774
+	}
775
+
776
+
777
+	/**
778
+	 * This function is a singleton method used to instantiate the Espresso_model object
779
+	 *
780
+	 * @param string|null $timezone   string representing the timezone we want to set for returned Date Time Strings
781
+	 *                                (and any incoming timezone data that gets saved).
782
+	 *                                Note this just sends the timezone info to the date time model field objects.
783
+	 *                                Default is NULL
784
+	 *                                (and will be assumed using the set timezone in the 'timezone_string' wp option)
785
+	 * @return static (as in the concrete child class)
786
+	 * @throws EE_Error
787
+	 * @throws ReflectionException
788
+	 */
789
+	public static function instance($timezone = '')
790
+	{
791
+		// check if instance of Espresso_model already exists
792
+		if (! static::$_instance instanceof static) {
793
+			$arguments = EEM_Base::getModelArguments(static::class, (string) $timezone);
794
+			$model     = new static(...$arguments);
795
+			EEM_Base::getLoader()->share(static::class, $model, $arguments);
796
+			static::$_instance = $model;
797
+		}
798
+		// we might have a timezone set, let set_timezone decide what to do with it
799
+		if ($timezone) {
800
+			static::$_instance->set_timezone($timezone);
801
+		}
802
+		// Espresso_model object
803
+		return static::$_instance;
804
+	}
805
+
806
+
807
+	/**
808
+	 * resets the model and returns it
809
+	 *
810
+	 * @param string|null $timezone
811
+	 * @return EEM_Base|null (if the model was already instantiated, returns it, with
812
+	 * all its properties reset; if it wasn't instantiated, returns null)
813
+	 * @throws EE_Error
814
+	 * @throws ReflectionException
815
+	 * @throws InvalidArgumentException
816
+	 * @throws InvalidDataTypeException
817
+	 * @throws InvalidInterfaceException
818
+	 */
819
+	public static function reset($timezone = '')
820
+	{
821
+		if (! static::$_instance instanceof EEM_Base) {
822
+			return null;
823
+		}
824
+		// Let's NOT swap out the current instance for a new one
825
+		// because if someone has a reference to it, we can't remove their reference.
826
+		// It's best to keep using the same reference but change the original object instead,
827
+		// so reset all its properties to their original values as defined in the class.
828
+		$static_properties = EEM_Base::getMirror()->getStaticProperties(static::class);
829
+		foreach (EEM_Base::getMirror()->getDefaultProperties(static::class) as $property => $value) {
830
+			// don't set instance to null like it was originally,
831
+			// but it's static anyways, and we're ignoring static properties (for now at least)
832
+			if (! isset($static_properties[ $property ])) {
833
+				static::$_instance->{$property} = $value;
834
+			}
835
+		}
836
+		// and then directly call its constructor again, like we would if we were creating a new one
837
+		$arguments = EEM_Base::getModelArguments(static::class, (string) $timezone);
838
+		static::$_instance->__construct(...$arguments);
839
+		return self::instance();
840
+	}
841
+
842
+
843
+	/**
844
+	 * Used to set the $_model_query_blog_id static property.
845
+	 *
846
+	 * @param int $blog_id  If provided then will set the blog_id for the models to this id.  If not provided then the
847
+	 *                      value for get_current_blog_id() will be used.
848
+	 */
849
+	public static function set_model_query_blog_id($blog_id = 0)
850
+	{
851
+		EEM_Base::$_model_query_blog_id = $blog_id > 0
852
+			? (int) $blog_id
853
+			: get_current_blog_id();
854
+	}
855
+
856
+
857
+	/**
858
+	 * Returns whatever is set as the internal $model_query_blog_id.
859
+	 *
860
+	 * @return int
861
+	 */
862
+	public static function get_model_query_blog_id()
863
+	{
864
+		return EEM_Base::$_model_query_blog_id;
865
+	}
866
+
867
+
868
+	/**
869
+	 * retrieve the status details from esp_status table as an array IF this model has the status table as a relation.
870
+	 *
871
+	 * @param boolean $translated return localized strings or JUST the array.
872
+	 * @return array
873
+	 * @throws EE_Error
874
+	 * @throws InvalidArgumentException
875
+	 * @throws InvalidDataTypeException
876
+	 * @throws InvalidInterfaceException
877
+	 * @throws ReflectionException
878
+	 */
879
+	public function status_array($translated = false)
880
+	{
881
+		if (! array_key_exists('Status', $this->_model_relations)) {
882
+			return [];
883
+		}
884
+		$model_name   = $this->get_this_model_name();
885
+		$status_type  = str_replace(' ', '_', strtolower(str_replace('_', ' ', $model_name)));
886
+		$stati        = EEM_Status::instance()->get_all([['STS_type' => $status_type]]);
887
+		$status_array = [];
888
+		foreach ($stati as $status) {
889
+			$status_array[ $status->ID() ] = $status->get('STS_code');
890
+		}
891
+		return $translated
892
+			? EEM_Status::instance()->localized_status($status_array, false, 'sentence')
893
+			: $status_array;
894
+	}
895
+
896
+
897
+	/**
898
+	 * Gets all the EE_Base_Class objects which match the $query_params, by querying the DB.
899
+	 *
900
+	 * @param array $query_params             @see
901
+	 *                                        https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
902
+	 *                                        or if you have the development copy of EE you can view this at the path:
903
+	 *                                        /docs/G--Model-System/model-query-params.md
904
+	 * @return EE_Base_Class[]  *note that there is NO option to pass the output type. If you want results different
905
+	 *                                        from EE_Base_Class[], use get_all_wpdb_results(). Array keys are object
906
+	 *                                        IDs (if there is a primary key on the model. if not, numerically indexed)
907
+	 *                                        Some full examples: get 10 transactions which have Scottish attendees:
908
+	 *                                        EEM_Transaction::instance()->get_all( array( array(
909
+	 *                                        'OR'=>array(
910
+	 *                                        'Registration.Attendee.ATT_fname'=>array('like','Mc%'),
911
+	 *                                        'Registration.Attendee.ATT_fname*other'=>array('like','Mac%')
912
+	 *                                        )
913
+	 *                                        ),
914
+	 *                                        'limit'=>10,
915
+	 *                                        'group_by'=>'TXN_ID'
916
+	 *                                        ));
917
+	 *                                        get all the answers to the question titled "shirt size" for event with id
918
+	 *                                        12, ordered by their answer EEM_Answer::instance()->get_all(array( array(
919
+	 *                                        'Question.QST_display_text'=>'shirt size',
920
+	 *                                        'Registration.Event.EVT_ID'=>12
921
+	 *                                        ),
922
+	 *                                        'order_by'=>array('ANS_value'=>'ASC')
923
+	 *                                        ));
924
+	 * @throws EE_Error
925
+	 * @throws ReflectionException
926
+	 */
927
+	public function get_all($query_params = [])
928
+	{
929
+		if (
930
+			isset($query_params['limit'])
931
+			&& ! isset($query_params['group_by'])
932
+		) {
933
+			$query_params['group_by'] = array_keys($this->get_combined_primary_key_fields());
934
+		}
935
+		return $this->_create_objects($this->_get_all_wpdb_results($query_params));
936
+	}
937
+
938
+
939
+	/**
940
+	 * Modifies the query parameters so we only get back model objects
941
+	 * that "belong" to the current user
942
+	 *
943
+	 * @param array $query_params @see
944
+	 *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
945
+	 * @return array @see
946
+	 *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
947
+	 * @throws ReflectionException
948
+	 * @throws ReflectionException
949
+	 */
950
+	public function alter_query_params_to_only_include_mine($query_params = [])
951
+	{
952
+		$wp_user_field_name = $this->wp_user_field_name();
953
+		if ($wp_user_field_name) {
954
+			$query_params[0][ $wp_user_field_name ] = get_current_user_id();
955
+		}
956
+		return $query_params;
957
+	}
958
+
959
+
960
+	/**
961
+	 * Returns the name of the field's name that points to the WP_User table
962
+	 *  on this model (or follows the _model_chain_to_wp_user and uses that model's
963
+	 * foreign key to the WP_User table)
964
+	 *
965
+	 * @return string|boolean string on success, boolean false when there is no
966
+	 * foreign key to the WP_User table
967
+	 * @throws ReflectionException
968
+	 * @throws ReflectionException
969
+	 */
970
+	public function wp_user_field_name()
971
+	{
972
+		try {
973
+			if (! empty($this->_model_chain_to_wp_user)) {
974
+				$models_to_follow_to_wp_users = explode('.', $this->_model_chain_to_wp_user);
975
+				$last_model_name              = end($models_to_follow_to_wp_users);
976
+				$model_with_fk_to_wp_users    = EE_Registry::instance()->load_model($last_model_name);
977
+				$model_chain_to_wp_user       = $this->_model_chain_to_wp_user . '.';
978
+			} else {
979
+				$model_with_fk_to_wp_users = $this;
980
+				$model_chain_to_wp_user    = '';
981
+			}
982
+			$wp_user_field = $model_with_fk_to_wp_users->get_foreign_key_to('WP_User');
983
+			return $model_chain_to_wp_user . $wp_user_field->get_name();
984
+		} catch (EE_Error $e) {
985
+			return false;
986
+		}
987
+	}
988
+
989
+
990
+	/**
991
+	 * Returns the _model_chain_to_wp_user string, which indicates which related model
992
+	 * (or transiently-related model) has a foreign key to the wp_users table;
993
+	 * useful for finding if model objects of this type are 'owned' by the current user.
994
+	 * This is an empty string when the foreign key is on this model and when it isn't,
995
+	 * but is only non-empty when this model's ownership is indicated by a RELATED model
996
+	 * (or transiently-related model)
997
+	 *
998
+	 * @return string
999
+	 */
1000
+	public function model_chain_to_wp_user()
1001
+	{
1002
+		return $this->_model_chain_to_wp_user;
1003
+	}
1004
+
1005
+
1006
+	/**
1007
+	 * Whether this model is 'owned' by a specific wordpress user (even indirectly,
1008
+	 * like how registrations don't have a foreign key to wp_users, but the
1009
+	 * events they are for are), or is unrelated to wp users.
1010
+	 * generally available
1011
+	 *
1012
+	 * @return boolean
1013
+	 */
1014
+	public function is_owned()
1015
+	{
1016
+		if ($this->model_chain_to_wp_user()) {
1017
+			return true;
1018
+		}
1019
+		try {
1020
+			$this->get_foreign_key_to('WP_User');
1021
+			return true;
1022
+		} catch (EE_Error $e) {
1023
+			return false;
1024
+		}
1025
+	}
1026
+
1027
+
1028
+	/**
1029
+	 * Used internally to get WPDB results, because other functions, besides get_all, may want to do some queries, but
1030
+	 * may want to preserve the WPDB results (eg, update, which first queries to make sure we have all the tables on
1031
+	 * the model)
1032
+	 *
1033
+	 * @param array  $query_params      @see
1034
+	 *                                  https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1035
+	 * @param string $output            ARRAY_A, OBJECT_K, etc. Just like
1036
+	 * @param mixed  $columns_to_select What columns to select. By default, we select all columns specified by the
1037
+	 *                                  fields on the model, and the models we joined to in the query. However, you can
1038
+	 *                                  override this and set the select to "*", or a specific column name, like
1039
+	 *                                  "ATT_ID", etc. If you would like to use these custom selections in WHERE,
1040
+	 *                                  GROUP_BY, or HAVING clauses, you must instead provide an array. Array keys are
1041
+	 *                                  the aliases used to refer to this selection, and values are to be
1042
+	 *                                  numerically-indexed arrays, where 0 is the selection and 1 is the data type.
1043
+	 *                                  Eg, array('count'=>array('COUNT(REG_ID)','%d'))
1044
+	 * @return array | stdClass[] like results of $wpdb->get_results($sql,OBJECT), (ie, output type is OBJECT)
1045
+	 * @throws EE_Error
1046
+	 * @throws InvalidArgumentException
1047
+	 * @throws ReflectionException
1048
+	 */
1049
+	protected function _get_all_wpdb_results($query_params = [], $output = ARRAY_A, $columns_to_select = null)
1050
+	{
1051
+		$this->_custom_selections = $this->getCustomSelection($query_params, $columns_to_select);
1052
+		$model_query_info         = $this->_create_model_query_info_carrier($query_params);
1053
+		$select_expressions       = $columns_to_select === null
1054
+			? $this->_construct_default_select_sql($model_query_info)
1055
+			: '';
1056
+		if ($this->_custom_selections instanceof CustomSelects) {
1057
+			$custom_expressions = $this->_custom_selections->columnsToSelectExpression();
1058
+			$select_expressions .= $select_expressions
1059
+				? ', ' . $custom_expressions
1060
+				: $custom_expressions;
1061
+		}
1062
+
1063
+		$SQL = "SELECT $select_expressions " . $this->_construct_2nd_half_of_select_query($model_query_info);
1064
+		return $this->_do_wpdb_query('get_results', [$SQL, $output]);
1065
+	}
1066
+
1067
+
1068
+	/**
1069
+	 * Get a CustomSelects object if the $query_params or $columns_to_select allows for it.
1070
+	 * Note: $query_params['extra_selects'] will always override any $columns_to_select values. It is the preferred
1071
+	 * method of including extra select information.
1072
+	 *
1073
+	 * @param array             $query_params
1074
+	 * @param null|array|string $columns_to_select
1075
+	 * @return null|CustomSelects
1076
+	 * @throws InvalidArgumentException
1077
+	 */
1078
+	protected function getCustomSelection(array $query_params, $columns_to_select = null)
1079
+	{
1080
+		if (! isset($query_params['extra_selects']) && $columns_to_select === null) {
1081
+			return null;
1082
+		}
1083
+		$selects = $query_params['extra_selects'] ?? $columns_to_select;
1084
+		$selects = is_string($selects)
1085
+			? explode(',', $selects)
1086
+			: $selects;
1087
+		return new CustomSelects($selects);
1088
+	}
1089
+
1090
+
1091
+	/**
1092
+	 * Gets an array of rows from the database just like $wpdb->get_results would,
1093
+	 * but you can use the model query params to more easily
1094
+	 * take care of joins, field preparation etc.
1095
+	 *
1096
+	 * @param array  $query_params      @see
1097
+	 *                                  https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1098
+	 * @param string $output            ARRAY_A, OBJECT_K, etc. Just like
1099
+	 * @param mixed  $columns_to_select , What columns to select. By default, we select all columns specified by the
1100
+	 *                                  fields on the model, and the models we joined to in the query. However, you can
1101
+	 *                                  override this and set the select to "*", or a specific column name, like
1102
+	 *                                  "ATT_ID", etc. If you would like to use these custom selections in WHERE,
1103
+	 *                                  GROUP_BY, or HAVING clauses, you must instead provide an array. Array keys are
1104
+	 *                                  the aliases used to refer to this selection, and values are to be
1105
+	 *                                  numerically-indexed arrays, where 0 is the selection and 1 is the data type.
1106
+	 *                                  Eg, array('count'=>array('COUNT(REG_ID)','%d'))
1107
+	 * @return array|stdClass[] like results of $wpdb->get_results($sql,OBJECT), (ie, output type is OBJECT)
1108
+	 * @throws EE_Error
1109
+	 * @throws ReflectionException
1110
+	 */
1111
+	public function get_all_wpdb_results($query_params = [], $output = ARRAY_A, $columns_to_select = null)
1112
+	{
1113
+		return $this->_get_all_wpdb_results($query_params, $output, $columns_to_select);
1114
+	}
1115
+
1116
+
1117
+	/**
1118
+	 * For creating a custom select statement
1119
+	 *
1120
+	 * @param mixed $columns_to_select either a string to be inserted directly as the select statement,
1121
+	 *                                 or an array where keys are aliases, and values are arrays where 0=>the selection
1122
+	 *                                 SQL, and 1=>is the datatype
1123
+	 * @return string
1124
+	 * @throws EE_Error
1125
+	 */
1126
+	private function _construct_select_from_input($columns_to_select)
1127
+	{
1128
+		if (is_array($columns_to_select)) {
1129
+			$select_sql_array = [];
1130
+			foreach ($columns_to_select as $alias => $selection_and_datatype) {
1131
+				if (! is_array($selection_and_datatype) || ! isset($selection_and_datatype[1])) {
1132
+					throw new EE_Error(
1133
+						sprintf(
1134
+							esc_html__(
1135
+								"Custom selection %s (alias %s) needs to be an array like array('COUNT(REG_ID)','%%d')",
1136
+								'event_espresso'
1137
+							),
1138
+							$selection_and_datatype,
1139
+							$alias
1140
+						)
1141
+					);
1142
+				}
1143
+				if (! in_array($selection_and_datatype[1], $this->_valid_wpdb_data_types, true)) {
1144
+					throw new EE_Error(
1145
+						sprintf(
1146
+							esc_html__(
1147
+								"Datatype %s (for selection '%s' and alias '%s') is not a valid wpdb datatype (eg %%s)",
1148
+								'event_espresso'
1149
+							),
1150
+							$selection_and_datatype[1],
1151
+							$selection_and_datatype[0],
1152
+							$alias,
1153
+							implode(', ', $this->_valid_wpdb_data_types)
1154
+						)
1155
+					);
1156
+				}
1157
+				$select_sql_array[] = "{$selection_and_datatype[0]} AS $alias";
1158
+			}
1159
+			$columns_to_select_string = implode(', ', $select_sql_array);
1160
+		} else {
1161
+			$columns_to_select_string = $columns_to_select;
1162
+		}
1163
+		return $columns_to_select_string;
1164
+	}
1165
+
1166
+
1167
+	/**
1168
+	 * Convenient wrapper for getting the primary key field's name. Eg, on Registration, this would be 'REG_ID'
1169
+	 *
1170
+	 * @return string
1171
+	 * @throws EE_Error
1172
+	 */
1173
+	public function primary_key_name()
1174
+	{
1175
+		return $this->get_primary_key_field()->get_name();
1176
+	}
1177
+
1178
+
1179
+	/**
1180
+	 * Gets a single item for this model from the DB, given only its ID (or null if none is found).
1181
+	 * If there is no primary key on this model, $id is treated as primary key string
1182
+	 *
1183
+	 * @param mixed $id int or string, depending on the type of the model's primary key
1184
+	 * @return EE_Base_Class|mixed|null
1185
+	 * @throws EE_Error
1186
+	 * @throws ReflectionException
1187
+	 */
1188
+	public function get_one_by_ID($id)
1189
+	{
1190
+		// if no id is passed, just return null
1191
+		if (empty($id)) {
1192
+			return null;
1193
+		}
1194
+		if ($this->get_from_entity_map($id)) {
1195
+			return $this->get_from_entity_map($id);
1196
+		}
1197
+		$model_object = $this->get_one(
1198
+			$this->alter_query_params_to_restrict_by_ID(
1199
+				$id,
1200
+				['default_where_conditions' => EEM_Base::default_where_conditions_minimum_all]
1201
+			)
1202
+		);
1203
+		$className    = $this->_get_class_name();
1204
+		if ($model_object instanceof $className) {
1205
+			// make sure valid objects get added to the entity map
1206
+			// so that the next call to this method doesn't trigger another trip to the db
1207
+			$this->add_to_entity_map($model_object);
1208
+		}
1209
+		return $model_object;
1210
+	}
1211
+
1212
+
1213
+	/**
1214
+	 * Alters query parameters to only get items with this ID are returned.
1215
+	 * Takes into account that the ID might be a string produced by EEM_Base::get_index_primary_key_string(),
1216
+	 * or could just be a simple primary key ID
1217
+	 *
1218
+	 * @param int   $id
1219
+	 * @param array $query_params
1220
+	 * @return array of normal query params, @see
1221
+	 *               https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1222
+	 * @throws EE_Error
1223
+	 */
1224
+	public function alter_query_params_to_restrict_by_ID($id, $query_params = [])
1225
+	{
1226
+		if (! isset($query_params[0])) {
1227
+			$query_params[0] = [];
1228
+		}
1229
+		$conditions_from_id = $this->parse_index_primary_key_string($id);
1230
+		if ($conditions_from_id === null) {
1231
+			$query_params[0][ $this->primary_key_name() ] = $id;
1232
+		} else {
1233
+			// no primary key, so the $id must be from the get_index_primary_key_string()
1234
+			$query_params[0] = array_replace_recursive($query_params[0], $this->parse_index_primary_key_string($id));
1235
+		}
1236
+		return $query_params;
1237
+	}
1238
+
1239
+
1240
+	/**
1241
+	 * Gets a single item for this model from the DB, given the $query_params. Only returns a single class, not an
1242
+	 * array. If no item is found, null is returned.
1243
+	 *
1244
+	 * @param array $query_params like EEM_Base's $query_params variable.
1245
+	 * @return EE_Base_Class|EE_Soft_Delete_Base_Class|NULL
1246
+	 * @throws EE_Error
1247
+	 * @throws ReflectionException
1248
+	 */
1249
+	public function get_one($query_params = [])
1250
+	{
1251
+		if (! is_array($query_params)) {
1252
+			EE_Error::doing_it_wrong(
1253
+				'EEM_Base::get_one',
1254
+				sprintf(
1255
+					esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1256
+					gettype($query_params)
1257
+				),
1258
+				'4.6.0'
1259
+			);
1260
+			$query_params = [];
1261
+		}
1262
+		$query_params['limit'] = 1;
1263
+		$items                 = $this->get_all($query_params);
1264
+		if (empty($items)) {
1265
+			return null;
1266
+		}
1267
+		return array_shift($items);
1268
+	}
1269
+
1270
+
1271
+	/**
1272
+	 * Returns the next x number of items in sequence from the given value as
1273
+	 * found in the database matching the given query conditions.
1274
+	 *
1275
+	 * @param mixed $current_field_value    Value used for the reference point.
1276
+	 * @param null  $field_to_order_by      What field is used for the
1277
+	 *                                      reference point.
1278
+	 * @param int   $limit                  How many to return.
1279
+	 * @param array $query_params           Extra conditions on the query.
1280
+	 * @param null  $columns_to_select      If left null, then an array of
1281
+	 *                                      EE_Base_Class objects is returned,
1282
+	 *                                      otherwise you can indicate just the
1283
+	 *                                      columns you want returned.
1284
+	 * @return EE_Base_Class[]|array
1285
+	 * @throws EE_Error
1286
+	 * @throws ReflectionException
1287
+	 */
1288
+	public function next_x(
1289
+		$current_field_value,
1290
+		$field_to_order_by = null,
1291
+		$limit = 1,
1292
+		$query_params = [],
1293
+		$columns_to_select = null
1294
+	) {
1295
+		return $this->_get_consecutive(
1296
+			$current_field_value,
1297
+			'>',
1298
+			$field_to_order_by,
1299
+			$limit,
1300
+			$query_params,
1301
+			$columns_to_select
1302
+		);
1303
+	}
1304
+
1305
+
1306
+	/**
1307
+	 * Returns the previous x number of items in sequence from the given value
1308
+	 * as found in the database matching the given query conditions.
1309
+	 *
1310
+	 * @param mixed $current_field_value    Value used for the reference point.
1311
+	 * @param null  $field_to_order_by      What field is used for the
1312
+	 *                                      reference point.
1313
+	 * @param int   $limit                  How many to return.
1314
+	 * @param array $query_params           Extra conditions on the query.
1315
+	 * @param null  $columns_to_select      If left null, then an array of
1316
+	 *                                      EE_Base_Class objects is returned,
1317
+	 *                                      otherwise you can indicate just the
1318
+	 *                                      columns you want returned.
1319
+	 * @return EE_Base_Class[]|array
1320
+	 * @throws EE_Error
1321
+	 * @throws ReflectionException
1322
+	 */
1323
+	public function previous_x(
1324
+		$current_field_value,
1325
+		$field_to_order_by = null,
1326
+		$limit = 1,
1327
+		$query_params = [],
1328
+		$columns_to_select = null
1329
+	) {
1330
+		return $this->_get_consecutive(
1331
+			$current_field_value,
1332
+			'<',
1333
+			$field_to_order_by,
1334
+			$limit,
1335
+			$query_params,
1336
+			$columns_to_select
1337
+		);
1338
+	}
1339
+
1340
+
1341
+	/**
1342
+	 * Returns the next item in sequence from the given value as found in the
1343
+	 * database matching the given query conditions.
1344
+	 *
1345
+	 * @param mixed $current_field_value    Value used for the reference point.
1346
+	 * @param null  $field_to_order_by      What field is used for the
1347
+	 *                                      reference point.
1348
+	 * @param array $query_params           Extra conditions on the query.
1349
+	 * @param null  $columns_to_select      If left null, then an EE_Base_Class
1350
+	 *                                      object is returned, otherwise you
1351
+	 *                                      can indicate just the columns you
1352
+	 *                                      want and a single array indexed by
1353
+	 *                                      the columns will be returned.
1354
+	 * @return EE_Base_Class|null|array()
1355
+	 * @throws EE_Error
1356
+	 * @throws ReflectionException
1357
+	 */
1358
+	public function next(
1359
+		$current_field_value,
1360
+		$field_to_order_by = null,
1361
+		$query_params = [],
1362
+		$columns_to_select = null
1363
+	) {
1364
+		$results = $this->_get_consecutive(
1365
+			$current_field_value,
1366
+			'>',
1367
+			$field_to_order_by,
1368
+			1,
1369
+			$query_params,
1370
+			$columns_to_select
1371
+		);
1372
+		return empty($results)
1373
+			? null
1374
+			: reset($results);
1375
+	}
1376
+
1377
+
1378
+	/**
1379
+	 * Returns the previous item in sequence from the given value as found in
1380
+	 * the database matching the given query conditions.
1381
+	 *
1382
+	 * @param mixed $current_field_value    Value used for the reference point.
1383
+	 * @param null  $field_to_order_by      What field is used for the
1384
+	 *                                      reference point.
1385
+	 * @param array $query_params           Extra conditions on the query.
1386
+	 * @param null  $columns_to_select      If left null, then an EE_Base_Class
1387
+	 *                                      object is returned, otherwise you
1388
+	 *                                      can indicate just the columns you
1389
+	 *                                      want and a single array indexed by
1390
+	 *                                      the columns will be returned.
1391
+	 * @return EE_Base_Class|null|array()
1392
+	 * @throws EE_Error
1393
+	 * @throws ReflectionException
1394
+	 */
1395
+	public function previous(
1396
+		$current_field_value,
1397
+		$field_to_order_by = null,
1398
+		$query_params = [],
1399
+		$columns_to_select = null
1400
+	) {
1401
+		$results = $this->_get_consecutive(
1402
+			$current_field_value,
1403
+			'<',
1404
+			$field_to_order_by,
1405
+			1,
1406
+			$query_params,
1407
+			$columns_to_select
1408
+		);
1409
+		return empty($results)
1410
+			? null
1411
+			: reset($results);
1412
+	}
1413
+
1414
+
1415
+	/**
1416
+	 * Returns the a consecutive number of items in sequence from the given
1417
+	 * value as found in the database matching the given query conditions.
1418
+	 *
1419
+	 * @param mixed  $current_field_value   Value used for the reference point.
1420
+	 * @param string $operand               What operand is used for the sequence.
1421
+	 * @param string $field_to_order_by     What field is used for the reference point.
1422
+	 * @param int    $limit                 How many to return.
1423
+	 * @param array  $query_params          Extra conditions on the query.
1424
+	 * @param null   $columns_to_select     If left null, then an array of EE_Base_Class objects is returned,
1425
+	 *                                      otherwise you can indicate just the columns you want returned.
1426
+	 * @return EE_Base_Class[]|array
1427
+	 * @throws EE_Error
1428
+	 * @throws ReflectionException
1429
+	 */
1430
+	protected function _get_consecutive(
1431
+		$current_field_value,
1432
+		$operand = '>',
1433
+		$field_to_order_by = null,
1434
+		$limit = 1,
1435
+		$query_params = [],
1436
+		$columns_to_select = null
1437
+	) {
1438
+		// if $field_to_order_by is empty then let's assume we're ordering by the primary key.
1439
+		if (empty($field_to_order_by)) {
1440
+			if ($this->has_primary_key_field()) {
1441
+				$field_to_order_by = $this->get_primary_key_field()->get_name();
1442
+			} else {
1443
+				if (WP_DEBUG) {
1444
+					throw new EE_Error(
1445
+						esc_html__(
1446
+							'EEM_Base::_get_consecutive() has been called with no $field_to_order_by argument and there is no primary key on the field.  Please provide the field you would like to use as the base for retrieving the next item(s).',
1447
+							'event_espresso'
1448
+						)
1449
+					);
1450
+				}
1451
+				EE_Error::add_error(
1452
+					esc_html__('There was an error with the query.', 'event_espresso'),
1453
+					__FILE__,
1454
+					__FUNCTION__,
1455
+					__LINE__
1456
+				);
1457
+				return [];
1458
+			}
1459
+		}
1460
+		if (! is_array($query_params)) {
1461
+			EE_Error::doing_it_wrong(
1462
+				'EEM_Base::_get_consecutive',
1463
+				sprintf(
1464
+					esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1465
+					gettype($query_params)
1466
+				),
1467
+				'4.6.0'
1468
+			);
1469
+			$query_params = [];
1470
+		}
1471
+		// let's add the where query param for consecutive look up.
1472
+		$query_params[0][ $field_to_order_by ] = [$operand, $current_field_value];
1473
+		$query_params['limit']                 = $limit;
1474
+		// set direction
1475
+		$incoming_orderby         = isset($query_params['order_by'])
1476
+			? (array) $query_params['order_by']
1477
+			: [];
1478
+		$query_params['order_by'] = $operand === '>'
1479
+			? [$field_to_order_by => 'ASC'] + $incoming_orderby
1480
+			: [$field_to_order_by => 'DESC'] + $incoming_orderby;
1481
+		// if $columns_to_select is empty then that means we're returning EE_Base_Class objects
1482
+		if (empty($columns_to_select)) {
1483
+			return $this->get_all($query_params);
1484
+		}
1485
+		// getting just the fields
1486
+		return $this->_get_all_wpdb_results($query_params, ARRAY_A, $columns_to_select);
1487
+	}
1488
+
1489
+
1490
+	/**
1491
+	 * This sets the _timezone property after model object has been instantiated.
1492
+	 *
1493
+	 * @param string|null $timezone valid PHP DateTimeZone timezone string
1494
+	 * @throws Exception
1495
+	 */
1496
+	public function set_timezone(?string $timezone = '')
1497
+	{
1498
+		if (! $timezone) {
1499
+			return;
1500
+		}
1501
+		$this->_timezone = $timezone;
1502
+		// note we need to loop through relations and set the timezone on those objects as well.
1503
+		foreach ($this->_model_relations as $relation) {
1504
+			$relation->set_timezone($timezone);
1505
+		}
1506
+		// and finally we do the same for any datetime fields
1507
+		foreach ($this->_fields as $field) {
1508
+			if ($field instanceof EE_Datetime_Field) {
1509
+				$field->set_timezone($timezone);
1510
+			}
1511
+		}
1512
+	}
1513
+
1514
+
1515
+	/**
1516
+	 * This just returns whatever is set for the current timezone.
1517
+	 *
1518
+	 * @access public
1519
+	 * @return string
1520
+	 * @throws Exception
1521
+	 */
1522
+	public function get_timezone()
1523
+	{
1524
+		// first validate if timezone is set.  If not, then let's set it be whatever is set on the model fields.
1525
+		if (empty($this->_timezone)) {
1526
+			foreach ($this->_fields as $field) {
1527
+				if ($field instanceof EE_Datetime_Field) {
1528
+					$this->set_timezone($field->get_timezone());
1529
+					break;
1530
+				}
1531
+			}
1532
+		}
1533
+		// if timezone STILL empty then return the default timezone for the site.
1534
+		if (empty($this->_timezone)) {
1535
+			$this->set_timezone(EEH_DTT_Helper::get_timezone());
1536
+		}
1537
+		return $this->_timezone;
1538
+	}
1539
+
1540
+
1541
+	/**
1542
+	 * This returns the date formats set for the given field name and also ensures that
1543
+	 * $this->_timezone property is set correctly.
1544
+	 *
1545
+	 * @param string $field_name The name of the field the formats are being retrieved for.
1546
+	 * @param bool   $pretty     Whether to return the pretty formats (true) or not (false).
1547
+	 * @return array formats in an array with the date format first, and the time format last.
1548
+	 * @throws EE_Error   If the given field_name is not of the EE_Datetime_Field type.
1549
+	 * @since 4.6.x
1550
+	 */
1551
+	public function get_formats_for($field_name, $pretty = false)
1552
+	{
1553
+		$field_settings = $this->field_settings_for($field_name);
1554
+		// if not a valid EE_Datetime_Field then throw error
1555
+		if (! $field_settings instanceof EE_Datetime_Field) {
1556
+			throw new EE_Error(
1557
+				sprintf(
1558
+					esc_html__(
1559
+						'The field sent into EEM_Base::get_formats_for (%s) is not registered as a EE_Datetime_Field. Please check the spelling and make sure you are submitting the right field name to retrieve date_formats for.',
1560
+						'event_espresso'
1561
+					),
1562
+					$field_name
1563
+				)
1564
+			);
1565
+		}
1566
+		// while we are here, let's make sure the timezone internally in EEM_Base matches what is stored on
1567
+		// the field.
1568
+		$this->_timezone = $field_settings->get_timezone();
1569
+		return [$field_settings->get_date_format($pretty), $field_settings->get_time_format($pretty)];
1570
+	}
1571
+
1572
+
1573
+	/**
1574
+	 * This returns the current time in a format setup for a query on this model.
1575
+	 * Usage of this method makes it easier to setup queries against EE_Datetime_Field columns because
1576
+	 * it will return:
1577
+	 *  - a formatted string in the timezone and format currently set on the EE_Datetime_Field for the given field for
1578
+	 *  NOW
1579
+	 *  - or a unix timestamp (equivalent to time())
1580
+	 * Note: When requesting a formatted string, if the date or time format doesn't include seconds, for example,
1581
+	 * the time returned, because it uses that format, will also NOT include seconds. For this reason, if you want
1582
+	 * the time returned to be the current time down to the exact second, set $timestamp to true.
1583
+	 *
1584
+	 * @param string $field_name       The field the current time is needed for.
1585
+	 * @param bool   $timestamp        True means to return a unix timestamp. Otherwise a
1586
+	 *                                 formatted string matching the set format for the field in the set timezone will
1587
+	 *                                 be returned.
1588
+	 * @param string $what             Whether to return the string in just the time format, the date format, or both.
1589
+	 * @return int|string  If the given field_name is not of the EE_Datetime_Field type, then an EE_Error
1590
+	 *                                 exception is triggered.
1591
+	 * @throws EE_Error    If the given field_name is not of the EE_Datetime_Field type.
1592
+	 * @throws Exception
1593
+	 * @since 4.6.x
1594
+	 */
1595
+	public function current_time_for_query($field_name, $timestamp = false, $what = 'both')
1596
+	{
1597
+		$formats  = $this->get_formats_for($field_name);
1598
+		$DateTime = new DateTime("now", new DateTimeZone($this->_timezone));
1599
+		if ($timestamp) {
1600
+			return $DateTime->format('U');
1601
+		}
1602
+		// not returning timestamp, so return formatted string in timezone.
1603
+		switch ($what) {
1604
+			case 'time':
1605
+				return $DateTime->format($formats[1]);
1606
+			case 'date':
1607
+				return $DateTime->format($formats[0]);
1608
+			default:
1609
+				return $DateTime->format(implode(' ', $formats));
1610
+		}
1611
+	}
1612
+
1613
+
1614
+	/**
1615
+	 * This receives a time string for a given field and ensures
1616
+	 * that it is set up to match what the internal settings for the model are.
1617
+	 * Returns a DateTime object.
1618
+	 * Note: a gotcha for when you send in unix timestamp.  Remember a unix timestamp is already timezone agnostic,
1619
+	 * (functionally the equivalent of UTC+0).
1620
+	 * So when you send it in, whatever timezone string you include is ignored.
1621
+	 *
1622
+	 * @param string      $field_name      The field being setup.
1623
+	 * @param string      $timestring      The date time string being used.
1624
+	 * @param string      $incoming_format The format for the time string.
1625
+	 * @param string|null $timezone_string By default, it is assumed the incoming time string is in timezone for
1626
+	 *                                     the blog.  If this is not the case, then it can be specified here.  If
1627
+	 *                                     incoming format is
1628
+	 *                                     'U', this is ignored.
1629
+	 * @return DbSafeDateTime
1630
+	 * @throws EE_Error
1631
+	 * @throws Exception
1632
+	 */
1633
+	public function convert_datetime_for_query(
1634
+		string $field_name,
1635
+		string $timestring,
1636
+		string $incoming_format,
1637
+		?string $timezone_string = ''
1638
+	): DbSafeDateTime {
1639
+		// just using this to ensure the timezone is set correctly internally
1640
+		$this->get_formats_for($field_name);
1641
+		// load EEH_DTT_Helper
1642
+		$timezone_string     = ! empty($timezone_string) ? $timezone_string : EEH_DTT_Helper::get_timezone();
1643
+		$incomingDateTime = date_create_from_format($incoming_format, $timestring, new DateTimeZone($timezone_string));
1644
+		EEH_DTT_Helper::setTimezone($incomingDateTime, new DateTimeZone($this->_timezone));
1645
+		return DbSafeDateTime::createFromDateTime($incomingDateTime);
1646
+	}
1647
+
1648
+
1649
+	/**
1650
+	 * Gets all the tables comprising this model. Array keys are the table aliases, and values are EE_Table objects
1651
+	 *
1652
+	 * @return EE_Table_Base[]
1653
+	 */
1654
+	public function get_tables()
1655
+	{
1656
+		return $this->_tables;
1657
+	}
1658
+
1659
+
1660
+	/**
1661
+	 * Updates all the database entries (in each table for this model) according to $fields_n_values and optionally
1662
+	 * also updates all the model objects, where the criteria expressed in $query_params are met..
1663
+	 * Also note: if this model has multiple tables, this update verifies all the secondary tables have an entry for
1664
+	 * each row (in the primary table) we're trying to update; if not, it inserts an entry in the secondary table. Eg:
1665
+	 * if our model has 2 tables: wp_posts (primary), and wp_esp_event (secondary). Let's say we are trying to update a
1666
+	 * model object with EVT_ID = 1
1667
+	 * (which means where wp_posts has ID = 1, because wp_posts.ID is the primary key's column), which exists, but
1668
+	 * there is no entry in wp_esp_event for this entry in wp_posts. So, this update script will insert a row into
1669
+	 * wp_esp_event, using any available parameters from $fields_n_values (eg, if "EVT_limit" => 40 is in
1670
+	 * $fields_n_values, the new entry in wp_esp_event will set EVT_limit = 40, and use default for other columns which
1671
+	 * are not specified)
1672
+	 *
1673
+	 * @param array   $fields_n_values         keys are model fields (exactly like keys in EEM_Base::_fields, NOT db
1674
+	 *                                         columns!), values are strings, ints, floats, and maybe arrays if they
1675
+	 *                                         are to be serialized. Basically, the values are what you'd expect to be
1676
+	 *                                         values on the model, NOT necessarily what's in the DB. For example, if
1677
+	 *                                         we wanted to update only the TXN_details on any Transactions where its
1678
+	 *                                         ID=34, we'd use this method as follows:
1679
+	 *                                         EEM_Transaction::instance()->update(
1680
+	 *                                         array('TXN_details'=>array('detail1'=>'monkey','detail2'=>'banana'),
1681
+	 *                                         array(array('TXN_ID'=>34)));
1682
+	 * @param array   $query_params            @see
1683
+	 *                                         https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1684
+	 *                                         Eg, consider updating Question's QST_admin_label field is of type
1685
+	 *                                         Simple_HTML. If you use this function to update that field to $new_value
1686
+	 *                                         = (note replace 8's with appropriate opening and closing tags in the
1687
+	 *                                         following example)"8script8alert('I hack all');8/script88b8boom
1688
+	 *                                         baby8/b8", then if you set $values_already_prepared_by_model_object to
1689
+	 *                                         TRUE, it is assumed that you've already called
1690
+	 *                                         EE_Simple_HTML_Field->prepare_for_set($new_value), which removes the
1691
+	 *                                         malicious javascript. However, if
1692
+	 *                                         $values_already_prepared_by_model_object is left as FALSE, then
1693
+	 *                                         EE_Simple_HTML_Field->prepare_for_set($new_value) will be called on it,
1694
+	 *                                         and every other field, before insertion. We provide this parameter
1695
+	 *                                         because model objects perform their prepare_for_set function on all
1696
+	 *                                         their values, and so don't need to be called again (and in many cases,
1697
+	 *                                         shouldn't be called again. Eg: if we escape HTML characters in the
1698
+	 *                                         prepare_for_set method...)
1699
+	 * @param boolean $keep_model_objs_in_sync if TRUE, makes sure we ALSO update model objects
1700
+	 *                                         in this model's entity map according to $fields_n_values that match
1701
+	 *                                         $query_params. This obviously has some overhead, so you can disable it
1702
+	 *                                         by setting this to FALSE, but be aware that model objects being used
1703
+	 *                                         could get out-of-sync with the database
1704
+	 * @return int how many rows got updated or FALSE if something went wrong with the query (wp returns FALSE or num
1705
+	 *                                         rows affected which *could* include 0 which DOES NOT mean the query was
1706
+	 *                                         bad)
1707
+	 * @throws EE_Error
1708
+	 * @throws ReflectionException
1709
+	 */
1710
+	public function update($fields_n_values, $query_params, $keep_model_objs_in_sync = true)
1711
+	{
1712
+		if (! is_array($query_params)) {
1713
+			EE_Error::doing_it_wrong(
1714
+				'EEM_Base::update',
1715
+				sprintf(
1716
+					esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1717
+					gettype($query_params)
1718
+				),
1719
+				'4.6.0'
1720
+			);
1721
+			$query_params = [];
1722
+		}
1723
+		/**
1724
+		 * Action called before a model update call has been made.
1725
+		 *
1726
+		 * @param EEM_Base $model
1727
+		 * @param array    $fields_n_values the updated fields and their new values
1728
+		 * @param array    $query_params    @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1729
+		 */
1730
+		do_action('AHEE__EEM_Base__update__begin', $this, $fields_n_values, $query_params);
1731
+		/**
1732
+		 * Filters the fields about to be updated given the query parameters. You can provide the
1733
+		 * $query_params to $this->get_all() to find exactly which records will be updated
1734
+		 *
1735
+		 * @param array    $fields_n_values fields and their new values
1736
+		 * @param EEM_Base $model           the model being queried
1737
+		 * @param array    $query_params    @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1738
+		 */
1739
+		$fields_n_values = (array) apply_filters(
1740
+			'FHEE__EEM_Base__update__fields_n_values',
1741
+			$fields_n_values,
1742
+			$this,
1743
+			$query_params
1744
+		);
1745
+		// need to verify that, for any entry we want to update, there are entries in each secondary table.
1746
+		// to do that, for each table, verify that it's PK isn't null.
1747
+		$tables = $this->get_tables();
1748
+		// and if the other tables don't have a row for each table-to-be-updated, we'll insert one with whatever values available in the current update query
1749
+		// NOTE: we should make this code more efficient by NOT querying twice
1750
+		// before the real update, but that needs to first go through ALPHA testing
1751
+		// as it's dangerous. says Mike August 8 2014
1752
+		// we want to make sure the default_where strategy is ignored
1753
+		$this->_ignore_where_strategy = true;
1754
+		$wpdb_select_results          = $this->_get_all_wpdb_results($query_params);
1755
+		foreach ($wpdb_select_results as $wpdb_result) {
1756
+			// type cast stdClass as array
1757
+			$wpdb_result = (array) $wpdb_result;
1758
+			// get the model object's PK, as we'll want this if we need to insert a row into secondary tables
1759
+			if ($this->has_primary_key_field()) {
1760
+				$main_table_pk_value = $wpdb_result[ $this->get_primary_key_field()->get_qualified_column() ];
1761
+			} else {
1762
+				// if there's no primary key, we basically can't support having a 2nd table on the model (we could but it would be lots of work)
1763
+				$main_table_pk_value = null;
1764
+			}
1765
+			// if there are more than 1 tables, we'll want to verify that each table for this model has an entry in the other tables
1766
+			// and if the other tables don't have a row for each table-to-be-updated, we'll insert one with whatever values available in the current update query
1767
+			if (count($tables) > 1) {
1768
+				// foreach matching row in the DB, ensure that each table's PK isn't null. If so, there must not be an entry
1769
+				// in that table, and so we'll want to insert one
1770
+				foreach ($tables as $table_obj) {
1771
+					$this_table_pk_column = $table_obj->get_fully_qualified_pk_column();
1772
+					// if there is no private key for this table on the results, it means there's no entry
1773
+					// in this table, right? so insert a row in the current table, using any fields available
1774
+					if (
1775
+						! (array_key_exists($this_table_pk_column, $wpdb_result)
1776
+						   && $wpdb_result[ $this_table_pk_column ])
1777
+					) {
1778
+						$success = $this->_insert_into_specific_table(
1779
+							$table_obj,
1780
+							$fields_n_values,
1781
+							$main_table_pk_value
1782
+						);
1783
+						// if we died here, report the error
1784
+						if (! $success) {
1785
+							return false;
1786
+						}
1787
+					}
1788
+				}
1789
+			}
1790
+			//              //and now check that if we have cached any models by that ID on the model, that
1791
+			//              //they also get updated properly
1792
+			//              $model_object = $this->get_from_entity_map( $main_table_pk_value );
1793
+			//              if( $model_object ){
1794
+			//                  foreach( $fields_n_values as $field => $value ){
1795
+			//                      $model_object->set($field, $value);
1796
+			// let's make sure default_where strategy is followed now
1797
+			$this->_ignore_where_strategy = false;
1798
+		}
1799
+		// if we want to keep model objects in sync, AND
1800
+		// if this wasn't called from a model object (to update itself)
1801
+		// then we want to make sure we keep all the existing
1802
+		// model objects in sync with the db
1803
+		if ($keep_model_objs_in_sync && ! $this->_values_already_prepared_by_model_object) {
1804
+			if ($this->has_primary_key_field()) {
1805
+				$model_objs_affected_ids = $this->get_col($query_params);
1806
+			} else {
1807
+				// we need to select a bunch of columns and then combine them into the the "index primary key string"s
1808
+				$models_affected_key_columns = $this->_get_all_wpdb_results($query_params, ARRAY_A);
1809
+				$model_objs_affected_ids     = [];
1810
+				foreach ($models_affected_key_columns as $row) {
1811
+					$combined_index_key                             = $this->get_index_primary_key_string($row);
1812
+					$model_objs_affected_ids[ $combined_index_key ] = $combined_index_key;
1813
+				}
1814
+			}
1815
+			if (! $model_objs_affected_ids) {
1816
+				// wait wait wait- if nothing was affected let's stop here
1817
+				return 0;
1818
+			}
1819
+			foreach ($model_objs_affected_ids as $id) {
1820
+				$model_obj_in_entity_map = $this->get_from_entity_map($id);
1821
+				if ($model_obj_in_entity_map) {
1822
+					foreach ($fields_n_values as $field => $new_value) {
1823
+						$model_obj_in_entity_map->set($field, $new_value);
1824
+					}
1825
+				}
1826
+			}
1827
+			// if there is a primary key on this model, we can now do a slight optimization
1828
+			if ($this->has_primary_key_field()) {
1829
+				// we already know what we want to update. So let's make the query simpler so it's a little more efficient
1830
+				$query_params = [
1831
+					[$this->primary_key_name() => ['IN', $model_objs_affected_ids]],
1832
+					'limit'                    => count($model_objs_affected_ids),
1833
+					'default_where_conditions' => EEM_Base::default_where_conditions_none,
1834
+				];
1835
+			}
1836
+		}
1837
+		$model_query_info = $this->_create_model_query_info_carrier($query_params);
1838
+
1839
+		// note: the following query doesn't use _construct_2nd_half_of_select_query()
1840
+		// because it doesn't accept LIMIT, ORDER BY, etc.
1841
+		$rows_affected = $this->_do_wpdb_query(
1842
+			'query',
1843
+			[
1844
+				"UPDATE " . $model_query_info->get_full_join_sql()
1845
+				. " SET " . $this->_construct_update_sql($fields_n_values)
1846
+				. $model_query_info->get_where_sql(),
1847
+			]
1848
+		);
1849
+
1850
+		/**
1851
+		 * Action called after a model update call has been made.
1852
+		 *
1853
+		 * @param EEM_Base $model
1854
+		 * @param array    $fields_n_values the updated fields and their new values
1855
+		 * @param array    $query_params    @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1856
+		 * @param int      $rows_affected
1857
+		 */
1858
+		do_action('AHEE__EEM_Base__update__end', $this, $fields_n_values, $query_params, $rows_affected);
1859
+		return $rows_affected;// how many supposedly got updated
1860
+	}
1861
+
1862
+
1863
+	/**
1864
+	 * Analogous to $wpdb->get_col, returns a 1-dimensional array where teh values
1865
+	 * are teh values of the field specified (or by default the primary key field)
1866
+	 * that matched the query params. Note that you should pass the name of the
1867
+	 * model FIELD, not the database table's column name.
1868
+	 *
1869
+	 * @param array  $query_params @see
1870
+	 *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1871
+	 * @param string $field_to_select
1872
+	 * @return array just like $wpdb->get_col()
1873
+	 * @throws EE_Error
1874
+	 * @throws ReflectionException
1875
+	 */
1876
+	public function get_col($query_params = [], $field_to_select = null)
1877
+	{
1878
+		if ($field_to_select) {
1879
+			$field = $this->field_settings_for($field_to_select);
1880
+		} elseif ($this->has_primary_key_field()) {
1881
+			$field = $this->get_primary_key_field();
1882
+		} else {
1883
+			$field_settings = $this->field_settings();
1884
+			// no primary key, just grab the first column
1885
+			$field = reset($field_settings);
1886
+			// don't need this array now
1887
+			unset($field_settings);
1888
+		}
1889
+		$model_query_info   = $this->_create_model_query_info_carrier($query_params);
1890
+		$select_expressions = $field->get_qualified_column();
1891
+		$SQL                =
1892
+			"SELECT $select_expressions " . $this->_construct_2nd_half_of_select_query($model_query_info);
1893
+		return $this->_do_wpdb_query('get_col', [$SQL]);
1894
+	}
1895
+
1896
+
1897
+	/**
1898
+	 * Returns a single column value for a single row from the database
1899
+	 *
1900
+	 * @param array  $query_params    @see
1901
+	 *                                https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1902
+	 * @param string $field_to_select @see EEM_Base::get_col()
1903
+	 * @return string
1904
+	 * @throws EE_Error
1905
+	 * @throws ReflectionException
1906
+	 */
1907
+	public function get_var($query_params = [], $field_to_select = null)
1908
+	{
1909
+		$query_params['limit'] = 1;
1910
+		$col                   = $this->get_col($query_params, $field_to_select);
1911
+		if (! empty($col)) {
1912
+			return reset($col);
1913
+		}
1914
+		return null;
1915
+	}
1916
+
1917
+
1918
+	/**
1919
+	 * Makes the SQL for after "UPDATE table_X inner join table_Y..." and before "...WHERE". Eg "Question.name='party
1920
+	 * time?', Question.desc='what do you think?',..." Values are filtered through wpdb->prepare to avoid against SQL
1921
+	 * injection, but currently no further filtering is done
1922
+	 *
1923
+	 * @param array $fields_n_values array keys are field names on this model, and values are what those fields should
1924
+	 *                               be updated to in the DB
1925
+	 * @return string of SQL
1926
+	 * @throws EE_Error
1927
+	 * @global      $wpdb
1928
+	 */
1929
+	public function _construct_update_sql($fields_n_values)
1930
+	{
1931
+		/** @type WPDB $wpdb */
1932
+		global $wpdb;
1933
+		$cols_n_values = [];
1934
+		foreach ($fields_n_values as $field_name => $value) {
1935
+			$field_obj = $this->field_settings_for($field_name);
1936
+			// if the value is NULL, we want to assign the value to that.
1937
+			// wpdb->prepare doesn't really handle that properly
1938
+			$prepared_value  = $this->_prepare_value_or_use_default($field_obj, $fields_n_values);
1939
+			$value_sql       = $prepared_value === null
1940
+				? 'NULL'
1941
+				: $wpdb->prepare($field_obj->get_wpdb_data_type(), $prepared_value);
1942
+			$cols_n_values[] = $field_obj->get_qualified_column() . "=" . $value_sql;
1943
+		}
1944
+		return implode(",", $cols_n_values);
1945
+	}
1946
+
1947
+
1948
+	/**
1949
+	 * Deletes a single row from the DB given the model object's primary key value. (eg, EE_Attendee->ID()'s value).
1950
+	 * Performs a HARD delete, meaning the database row should always be removed,
1951
+	 * not just have a flag field on it switched
1952
+	 * Wrapper for EEM_Base::delete_permanently()
1953
+	 *
1954
+	 * @param mixed   $id
1955
+	 * @param boolean $allow_blocking
1956
+	 * @return int the number of rows deleted
1957
+	 * @throws EE_Error
1958
+	 * @throws ReflectionException
1959
+	 */
1960
+	public function delete_permanently_by_ID($id, $allow_blocking = true)
1961
+	{
1962
+		return $this->delete_permanently(
1963
+			[
1964
+				[$this->get_primary_key_field()->get_name() => $id],
1965
+				'limit' => 1,
1966
+			],
1967
+			$allow_blocking
1968
+		);
1969
+	}
1970
+
1971
+
1972
+	/**
1973
+	 * Deletes a single row from the DB given the model object's primary key value. (eg, EE_Attendee->ID()'s value).
1974
+	 * Wrapper for EEM_Base::delete()
1975
+	 *
1976
+	 * @param mixed   $id
1977
+	 * @param boolean $allow_blocking
1978
+	 * @return int the number of rows deleted
1979
+	 * @throws EE_Error
1980
+	 * @throws ReflectionException
1981
+	 */
1982
+	public function delete_by_ID($id, $allow_blocking = true)
1983
+	{
1984
+		return $this->delete(
1985
+			[
1986
+				[$this->get_primary_key_field()->get_name() => $id],
1987
+				'limit' => 1,
1988
+			],
1989
+			$allow_blocking
1990
+		);
1991
+	}
1992
+
1993
+
1994
+	/**
1995
+	 * Identical to delete_permanently, but does a "soft" delete if possible,
1996
+	 * meaning if the model has a field that indicates its been "trashed" or
1997
+	 * "soft deleted", we will just set that instead of actually deleting the rows.
1998
+	 *
1999
+	 * @param array   $query_params
2000
+	 * @param boolean $allow_blocking
2001
+	 * @return int how many rows got deleted
2002
+	 * @throws EE_Error
2003
+	 * @throws ReflectionException
2004
+	 * @see EEM_Base::delete_permanently
2005
+	 */
2006
+	public function delete($query_params, $allow_blocking = true)
2007
+	{
2008
+		return $this->delete_permanently($query_params, $allow_blocking);
2009
+	}
2010
+
2011
+
2012
+	/**
2013
+	 * Deletes the model objects that meet the query params. Note: this method is overridden
2014
+	 * in EEM_Soft_Delete_Base so that soft-deleted model objects are instead only flagged
2015
+	 * as archived, not actually deleted
2016
+	 *
2017
+	 * @param array   $query_params   @see
2018
+	 *                                https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2019
+	 * @param boolean $allow_blocking if TRUE, matched objects will only be deleted if there is no related model info
2020
+	 *                                that blocks it (ie, there' sno other data that depends on this data); if false,
2021
+	 *                                deletes regardless of other objects which may depend on it. Its generally
2022
+	 *                                advisable to always leave this as TRUE, otherwise you could easily corrupt your
2023
+	 *                                DB
2024
+	 * @return int how many rows got deleted
2025
+	 * @throws EE_Error
2026
+	 * @throws ReflectionException
2027
+	 */
2028
+	public function delete_permanently($query_params, $allow_blocking = true)
2029
+	{
2030
+		/**
2031
+		 * Action called just before performing a real deletion query. You can use the
2032
+		 * model and its $query_params to find exactly which items will be deleted
2033
+		 *
2034
+		 * @param EEM_Base $model
2035
+		 * @param array    $query_params   @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2036
+		 * @param boolean  $allow_blocking whether or not to allow related model objects
2037
+		 *                                 to block (prevent) this deletion
2038
+		 */
2039
+		do_action('AHEE__EEM_Base__delete__begin', $this, $query_params, $allow_blocking);
2040
+		// some MySQL databases may be running safe mode, which may restrict
2041
+		// deletion if there is no KEY column used in the WHERE statement of a deletion.
2042
+		// to get around this, we first do a SELECT, get all the IDs, and then run another query
2043
+		// to delete them
2044
+		$items_for_deletion           = $this->_get_all_wpdb_results($query_params);
2045
+		$columns_and_ids_for_deleting = $this->_get_ids_for_delete($items_for_deletion, $allow_blocking);
2046
+		$deletion_where_query_part    = $this->_build_query_part_for_deleting_from_columns_and_values(
2047
+			$columns_and_ids_for_deleting
2048
+		);
2049
+		/**
2050
+		 * Allows client code to act on the items being deleted before the query is actually executed.
2051
+		 *
2052
+		 * @param EEM_Base $this                            The model instance being acted on.
2053
+		 * @param array    $query_params                    The incoming array of query parameters influencing what gets deleted.
2054
+		 * @param bool     $allow_blocking                  @see param description in method phpdoc block.
2055
+		 * @param array    $columns_and_ids_for_deleting    An array indicating what entities will get removed as
2056
+		 *                                                  derived from the incoming query parameters.
2057
+		 * @see details on the structure of this array in the phpdocs
2058
+		 *                                                  for the `_get_ids_for_delete_method`
2059
+		 */
2060
+		do_action(
2061
+			'AHEE__EEM_Base__delete__before_query',
2062
+			$this,
2063
+			$query_params,
2064
+			$allow_blocking,
2065
+			$columns_and_ids_for_deleting
2066
+		);
2067
+		if ($deletion_where_query_part) {
2068
+			$model_query_info = $this->_create_model_query_info_carrier($query_params);
2069
+			$table_aliases    = array_keys($this->_tables);
2070
+			$SQL              = "DELETE "
2071
+								. implode(", ", $table_aliases)
2072
+								. " FROM "
2073
+								. $model_query_info->get_full_join_sql()
2074
+								. " WHERE "
2075
+								. $deletion_where_query_part;
2076
+			$rows_deleted     = $this->_do_wpdb_query('query', [$SQL]);
2077
+		} else {
2078
+			$rows_deleted = 0;
2079
+		}
2080
+
2081
+		// Next, make sure those items are removed from the entity map; if they could be put into it at all; and if
2082
+		// there was no error with the delete query.
2083
+		if (
2084
+			$this->has_primary_key_field()
2085
+			&& $rows_deleted !== false
2086
+			&& isset($columns_and_ids_for_deleting[ $this->get_primary_key_field()->get_qualified_column() ])
2087
+		) {
2088
+			$ids_for_removal = $columns_and_ids_for_deleting[ $this->get_primary_key_field()->get_qualified_column() ];
2089
+			foreach ($ids_for_removal as $id) {
2090
+				if (isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])) {
2091
+					unset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ]);
2092
+				}
2093
+			}
2094
+
2095
+			// delete any extra meta attached to the deleted entities but ONLY if this model is not an instance of
2096
+			// `EEM_Extra_Meta`.  In other words we want to prevent recursion on EEM_Extra_Meta::delete_permanently calls
2097
+			// unnecessarily.  It's very unlikely that users will have assigned Extra Meta to Extra Meta
2098
+			// (although it is possible).
2099
+			// Note this can be skipped by using the provided filter and returning false.
2100
+			if (
2101
+				apply_filters(
2102
+					'FHEE__EEM_Base__delete_permanently__dont_delete_extra_meta_for_extra_meta',
2103
+					! $this instanceof EEM_Extra_Meta,
2104
+					$this
2105
+				)
2106
+			) {
2107
+				EEM_Extra_Meta::instance()->delete_permanently(
2108
+					[
2109
+						0 => [
2110
+							'EXM_type' => $this->get_this_model_name(),
2111
+							'OBJ_ID'   => [
2112
+								'IN',
2113
+								$ids_for_removal,
2114
+							],
2115
+						],
2116
+					]
2117
+				);
2118
+			}
2119
+		}
2120
+
2121
+		/**
2122
+		 * Action called just after performing a real deletion query. Although at this point the
2123
+		 * items should have been deleted
2124
+		 *
2125
+		 * @param EEM_Base $model
2126
+		 * @param array    $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2127
+		 * @param int      $rows_deleted
2128
+		 */
2129
+		do_action('AHEE__EEM_Base__delete__end', $this, $query_params, $rows_deleted, $columns_and_ids_for_deleting);
2130
+		return $rows_deleted;// how many supposedly got deleted
2131
+	}
2132
+
2133
+
2134
+	/**
2135
+	 * Checks all the relations that throw error messages when there are blocking related objects
2136
+	 * for related model objects. If there are any related model objects on those relations,
2137
+	 * adds an EE_Error, and return true
2138
+	 *
2139
+	 * @param EE_Base_Class|int $this_model_obj_or_id
2140
+	 * @param EE_Base_Class     $ignore_this_model_obj a model object like 'EE_Event', or 'EE_Term_Taxonomy', which
2141
+	 *                                                 should be ignored when determining whether there are related
2142
+	 *                                                 model objects which block this model object's deletion. Useful
2143
+	 *                                                 if you know A is related to B and are considering deleting A,
2144
+	 *                                                 but want to see if A has any other objects blocking its deletion
2145
+	 *                                                 before removing the relation between A and B
2146
+	 * @return boolean
2147
+	 * @throws EE_Error
2148
+	 * @throws ReflectionException
2149
+	 */
2150
+	public function delete_is_blocked_by_related_models($this_model_obj_or_id, $ignore_this_model_obj = null)
2151
+	{
2152
+		// first, if $ignore_this_model_obj was supplied, get its model
2153
+		$ignored_model = $ignore_this_model_obj instanceof EE_Base_Class
2154
+			? $ignore_this_model_obj->get_model()
2155
+			: null;
2156
+		// now check all the relations of $this_model_obj_or_id and see if there
2157
+		// are any related model objects blocking it?
2158
+		$is_blocked = false;
2159
+		foreach ($this->_model_relations as $relation_name => $relation_obj) {
2160
+			if ($relation_obj->block_delete_if_related_models_exist()) {
2161
+				// if $ignore_this_model_obj was supplied, then for the query
2162
+				// on that model needs to be told to ignore $ignore_this_model_obj
2163
+				if ($ignored_model && $relation_name === $ignored_model->get_this_model_name()) {
2164
+					$related_model_objects = $relation_obj->get_all_related(
2165
+						$this_model_obj_or_id,
2166
+						[
2167
+							[
2168
+								$ignored_model->get_primary_key_field()->get_name() => [
2169
+									'!=',
2170
+									$ignore_this_model_obj->ID(),
2171
+								],
2172
+							],
2173
+						]
2174
+					);
2175
+				} else {
2176
+					$related_model_objects = $relation_obj->get_all_related($this_model_obj_or_id);
2177
+				}
2178
+				if ($related_model_objects) {
2179
+					EE_Error::add_error($relation_obj->get_deletion_error_message(), __FILE__, __FUNCTION__, __LINE__);
2180
+					$is_blocked = true;
2181
+				}
2182
+			}
2183
+		}
2184
+		return $is_blocked;
2185
+	}
2186
+
2187
+
2188
+	/**
2189
+	 * Builds the columns and values for items to delete from the incoming $row_results_for_deleting array.
2190
+	 *
2191
+	 * @param array $row_results_for_deleting
2192
+	 * @param bool  $allow_blocking
2193
+	 * @return array   The shape of this array depends on whether the model `has_primary_key_field` or not.  If the
2194
+	 *                              model DOES have a primary_key_field, then the array will be a simple single
2195
+	 *                              dimension array where the key is the fully qualified primary key column and the
2196
+	 *                              value is an array of ids that will be deleted. Example: array('Event.EVT_ID' =>
2197
+	 *                              array( 1,2,3)) If the model DOES NOT have a primary_key_field, then the array will
2198
+	 *                              be a two dimensional array where each element is a group of columns and values that
2199
+	 *                              get deleted. Example: array(
2200
+	 *                              0 => array(
2201
+	 *                              'Term_Relationship.object_id' => 1
2202
+	 *                              'Term_Relationship.term_taxonomy_id' => 5
2203
+	 *                              ),
2204
+	 *                              1 => array(
2205
+	 *                              'Term_Relationship.object_id' => 1
2206
+	 *                              'Term_Relationship.term_taxonomy_id' => 6
2207
+	 *                              )
2208
+	 *                              )
2209
+	 * @throws EE_Error
2210
+	 * @throws ReflectionException
2211
+	 */
2212
+	protected function _get_ids_for_delete(array $row_results_for_deleting, $allow_blocking = true)
2213
+	{
2214
+		$ids_to_delete_indexed_by_column = [];
2215
+		if ($this->has_primary_key_field()) {
2216
+			$primary_table = $this->_get_main_table();
2217
+			// following lines are commented out because the variables were not being used
2218
+			// not deleting because unsure if calls were intentionally causing side effects
2219
+			// $primary_table_pk_field          =
2220
+			//     $this->get_field_by_column($primary_table->get_fully_qualified_pk_column());
2221
+			// $other_tables                    = $this->_get_other_tables();
2222
+			$ids_to_delete_indexed_by_column = $query = [];
2223
+			foreach ($row_results_for_deleting as $item_to_delete) {
2224
+				// before we mark this item for deletion,
2225
+				// make sure there's no related entities blocking its deletion (if we're checking)
2226
+				if (
2227
+					$allow_blocking
2228
+					&& $this->delete_is_blocked_by_related_models(
2229
+						$item_to_delete[ $primary_table->get_fully_qualified_pk_column() ]
2230
+					)
2231
+				) {
2232
+					continue;
2233
+				}
2234
+				// primary table deletes
2235
+				if (isset($item_to_delete[ $primary_table->get_fully_qualified_pk_column() ])) {
2236
+					$ids_to_delete_indexed_by_column[ $primary_table->get_fully_qualified_pk_column() ][] =
2237
+						$item_to_delete[ $primary_table->get_fully_qualified_pk_column() ];
2238
+				}
2239
+			}
2240
+		} elseif (count($this->get_combined_primary_key_fields()) > 1) {
2241
+			$fields = $this->get_combined_primary_key_fields();
2242
+			foreach ($row_results_for_deleting as $item_to_delete) {
2243
+				$ids_to_delete_indexed_by_column_for_row = [];
2244
+				foreach ($fields as $cpk_field) {
2245
+					if ($cpk_field instanceof EE_Model_Field_Base) {
2246
+						$ids_to_delete_indexed_by_column_for_row[ $cpk_field->get_qualified_column() ] =
2247
+							$item_to_delete[ $cpk_field->get_qualified_column() ];
2248
+					}
2249
+				}
2250
+				$ids_to_delete_indexed_by_column[] = $ids_to_delete_indexed_by_column_for_row;
2251
+			}
2252
+		} else {
2253
+			// so there's no primary key and no combined key...
2254
+			// sorry, can't help you
2255
+			throw new EE_Error(
2256
+				sprintf(
2257
+					esc_html__(
2258
+						"Cannot delete objects of type %s because there is no primary key NOR combined key",
2259
+						"event_espresso"
2260
+					),
2261
+					get_class($this)
2262
+				)
2263
+			);
2264
+		}
2265
+		return $ids_to_delete_indexed_by_column;
2266
+	}
2267
+
2268
+
2269
+	/**
2270
+	 * This receives an array of columns and values set to be deleted (as prepared by _get_ids_for_delete) and prepares
2271
+	 * the corresponding query_part for the query performing the delete.
2272
+	 *
2273
+	 * @param array $ids_to_delete_indexed_by_column @see _get_ids_for_delete for how this array might be shaped.
2274
+	 * @return string
2275
+	 * @throws EE_Error
2276
+	 */
2277
+	protected function _build_query_part_for_deleting_from_columns_and_values(array $ids_to_delete_indexed_by_column)
2278
+	{
2279
+		$query_part = '';
2280
+		if (empty($ids_to_delete_indexed_by_column)) {
2281
+			return $query_part;
2282
+		} elseif ($this->has_primary_key_field()) {
2283
+			$query = [];
2284
+			foreach ($ids_to_delete_indexed_by_column as $column => $ids) {
2285
+				$query[] = $column . ' IN' . $this->_construct_in_value($ids, $this->_primary_key_field);
2286
+			}
2287
+			$query_part = ! empty($query)
2288
+				? implode(' AND ', $query)
2289
+				: $query_part;
2290
+		} elseif (count($this->get_combined_primary_key_fields()) > 1) {
2291
+			$ways_to_identify_a_row = [];
2292
+			foreach ($ids_to_delete_indexed_by_column as $ids_to_delete_indexed_by_column_for_each_row) {
2293
+				$values_for_each_combined_primary_key_for_a_row = [];
2294
+				foreach ($ids_to_delete_indexed_by_column_for_each_row as $column => $id) {
2295
+					$values_for_each_combined_primary_key_for_a_row[] = $column . '=' . $id;
2296
+				}
2297
+				$ways_to_identify_a_row[] = '('
2298
+											. implode(' AND ', $values_for_each_combined_primary_key_for_a_row)
2299
+											. ')';
2300
+			}
2301
+			$query_part = implode(' OR ', $ways_to_identify_a_row);
2302
+		}
2303
+		return $query_part;
2304
+	}
2305
+
2306
+
2307
+	/**
2308
+	 * Gets the model field by the fully qualified name
2309
+	 *
2310
+	 * @param string $qualified_column_name eg 'Event_CPT.post_name' or $field_obj->get_qualified_column()
2311
+	 * @return EE_Model_Field_Base
2312
+	 * @throws EE_Error
2313
+	 * @throws EE_Error
2314
+	 */
2315
+	public function get_field_by_column($qualified_column_name)
2316
+	{
2317
+		foreach ($this->field_settings(true) as $field_name => $field_obj) {
2318
+			if ($field_obj->get_qualified_column() === $qualified_column_name) {
2319
+				return $field_obj;
2320
+			}
2321
+		}
2322
+		throw new EE_Error(
2323
+			sprintf(
2324
+				esc_html__('Could not find a field on the model "%1$s" for qualified column "%2$s"', 'event_espresso'),
2325
+				$this->get_this_model_name(),
2326
+				$qualified_column_name
2327
+			)
2328
+		);
2329
+	}
2330
+
2331
+
2332
+	/**
2333
+	 * Count all the rows that match criteria the model query params.
2334
+	 * If $field_to_count isn't provided, the model's primary key is used. Otherwise, we count by field_to_count's
2335
+	 * column
2336
+	 *
2337
+	 * @param array  $query_params   @see
2338
+	 *                               https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2339
+	 * @param string $field_to_count field on model to count by (not column name)
2340
+	 * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2341
+	 *                               that by the setting $distinct to TRUE;
2342
+	 * @return int
2343
+	 * @throws EE_Error
2344
+	 * @throws ReflectionException
2345
+	 */
2346
+	public function count($query_params = [], $field_to_count = null, $distinct = false)
2347
+	{
2348
+		$model_query_info = $this->_create_model_query_info_carrier($query_params);
2349
+		if ($field_to_count) {
2350
+			$field_obj       = $this->field_settings_for($field_to_count);
2351
+			$column_to_count = $field_obj->get_qualified_column();
2352
+		} elseif ($this->has_primary_key_field()) {
2353
+			$pk_field_obj    = $this->get_primary_key_field();
2354
+			$column_to_count = $pk_field_obj->get_qualified_column();
2355
+		} else {
2356
+			// there's no primary key
2357
+			// if we're counting distinct items, and there's no primary key,
2358
+			// we need to list out the columns for distinction;
2359
+			// otherwise we can just use star
2360
+			if ($distinct) {
2361
+				$columns_to_use = [];
2362
+				foreach ($this->get_combined_primary_key_fields() as $field_obj) {
2363
+					$columns_to_use[] = $field_obj->get_qualified_column();
2364
+				}
2365
+				$column_to_count = implode(',', $columns_to_use);
2366
+			} else {
2367
+				$column_to_count = '*';
2368
+			}
2369
+		}
2370
+		$column_to_count = $distinct
2371
+			? "DISTINCT " . $column_to_count
2372
+			: $column_to_count;
2373
+		$SQL             =
2374
+			"SELECT COUNT(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2375
+		return (int) $this->_do_wpdb_query('get_var', [$SQL]);
2376
+	}
2377
+
2378
+
2379
+	/**
2380
+	 * Sums up the value of the $field_to_sum (defaults to the primary key, which isn't terribly useful)
2381
+	 *
2382
+	 * @param array  $query_params @see
2383
+	 *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2384
+	 * @param string $field_to_sum name of field (array key in $_fields array)
2385
+	 * @return float
2386
+	 * @throws EE_Error
2387
+	 * @throws ReflectionException
2388
+	 */
2389
+	public function sum($query_params, $field_to_sum = null)
2390
+	{
2391
+		$model_query_info = $this->_create_model_query_info_carrier($query_params);
2392
+		if ($field_to_sum) {
2393
+			$field_obj = $this->field_settings_for($field_to_sum);
2394
+		} else {
2395
+			$field_obj = $this->get_primary_key_field();
2396
+		}
2397
+		$column_to_count = $field_obj->get_qualified_column();
2398
+		$SQL             =
2399
+			"SELECT SUM(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2400
+		$return_value    = $this->_do_wpdb_query('get_var', [$SQL]);
2401
+		$data_type       = $field_obj->get_wpdb_data_type();
2402
+		if ($data_type === '%d' || $data_type === '%s') {
2403
+			return (float) $return_value;
2404
+		}
2405
+		// must be %f
2406
+		return (float) $return_value;
2407
+	}
2408
+
2409
+
2410
+	/**
2411
+	 * Just calls the specified method on $wpdb with the given arguments
2412
+	 * Consolidates a little extra error handling code
2413
+	 *
2414
+	 * @param string $wpdb_method
2415
+	 * @param array  $arguments_to_provide
2416
+	 * @return mixed
2417
+	 * @throws EE_Error
2418
+	 * @global wpdb  $wpdb
2419
+	 */
2420
+	protected function _do_wpdb_query($wpdb_method, $arguments_to_provide)
2421
+	{
2422
+		// if we're in maintenance mode level 2, DON'T run any queries
2423
+		// because level 2 indicates the database needs updating and
2424
+		// is probably out of sync with the code
2425
+		if (DbStatus::isOffline()) {
2426
+			throw new RuntimeException(
2427
+				esc_html__(
2428
+					"Event Espresso Level 2 Maintenance mode is active. That means EE can not run ANY database queries until the necessary migration scripts have run which will take EE out of maintenance mode level 2. Please inform support of this error.",
2429
+					"event_espresso"
2430
+				)
2431
+			);
2432
+		}
2433
+		/** @type WPDB $wpdb */
2434
+		global $wpdb;
2435
+		if (! method_exists($wpdb, $wpdb_method)) {
2436
+			throw new DomainException(
2437
+				sprintf(
2438
+					esc_html__(
2439
+						'There is no method named "%s" on Wordpress\' $wpdb object',
2440
+						'event_espresso'
2441
+					),
2442
+					$wpdb_method
2443
+				)
2444
+			);
2445
+		}
2446
+		$old_show_errors_value = $wpdb->show_errors;
2447
+		if (WP_DEBUG) {
2448
+			$wpdb->show_errors(false);
2449
+		}
2450
+		$result = $this->_process_wpdb_query($wpdb_method, $arguments_to_provide);
2451
+		$this->show_db_query_if_previously_requested($wpdb->last_query);
2452
+		if (WP_DEBUG) {
2453
+			$wpdb->show_errors($old_show_errors_value);
2454
+			if (! empty($wpdb->last_error)) {
2455
+				throw new EE_Error(sprintf(esc_html__('WPDB Error: "%s"', 'event_espresso'), $wpdb->last_error));
2456
+			}
2457
+			if ($result === false) {
2458
+				throw new EE_Error(
2459
+					sprintf(
2460
+						esc_html__(
2461
+							'WPDB Error occurred, but no error message was logged by wpdb! The wpdb method called was "%1$s" and the arguments were "%2$s"',
2462
+							'event_espresso'
2463
+						),
2464
+						$wpdb_method,
2465
+						var_export($arguments_to_provide, true)
2466
+					)
2467
+				);
2468
+			}
2469
+		} elseif ($result === false) {
2470
+			EE_Error::add_error(
2471
+				sprintf(
2472
+					esc_html__(
2473
+						'A database error has occurred. Turn on WP_DEBUG for more information.||A database error occurred doing wpdb method "%1$s", with arguments "%2$s". The error was "%3$s"',
2474
+						'event_espresso'
2475
+					),
2476
+					$wpdb_method,
2477
+					var_export($arguments_to_provide, true),
2478
+					$wpdb->last_error
2479
+				),
2480
+				__FILE__,
2481
+				__FUNCTION__,
2482
+				__LINE__
2483
+			);
2484
+		}
2485
+		return $result;
2486
+	}
2487
+
2488
+
2489
+	/**
2490
+	 * Attempts to run the indicated WPDB method with the provided arguments,
2491
+	 * and if there's an error tries to verify the DB is correct. Uses
2492
+	 * the static property EEM_Base::$_db_verification_level to determine whether
2493
+	 * we should try to fix the EE core db, the addons, or just give up
2494
+	 *
2495
+	 * @param string $wpdb_method
2496
+	 * @param array  $arguments_to_provide
2497
+	 * @return mixed
2498
+	 */
2499
+	private function _process_wpdb_query($wpdb_method, $arguments_to_provide)
2500
+	{
2501
+		/** @type WPDB $wpdb */
2502
+		global $wpdb;
2503
+		$wpdb->last_error = null;
2504
+		$result           = call_user_func_array([$wpdb, $wpdb_method], $arguments_to_provide);
2505
+		// was there an error running the query? but we don't care on new activations
2506
+		// (we're going to setup the DB anyway on new activations)
2507
+		if (
2508
+			($result === false || ! empty($wpdb->last_error))
2509
+			&& EE_System::instance()->detect_req_type() !== EE_System::req_type_new_activation
2510
+		) {
2511
+			switch (EEM_Base::$_db_verification_level) {
2512
+				case EEM_Base::db_verified_none:
2513
+					// let's double-check core's DB
2514
+					$error_message = $this->_verify_core_db($wpdb_method, $arguments_to_provide);
2515
+					break;
2516
+				case EEM_Base::db_verified_core:
2517
+					// STILL NO LOVE?? verify all the addons too. Maybe they need to be fixed
2518
+					$error_message = $this->_verify_addons_db($wpdb_method, $arguments_to_provide);
2519
+					break;
2520
+				case EEM_Base::db_verified_addons:
2521
+					// ummmm... you in trouble
2522
+					return $result;
2523
+			}
2524
+			if (! empty($error_message)) {
2525
+				EE_Log::instance()->log(__FILE__, __FUNCTION__, $error_message, 'error');
2526
+				trigger_error($error_message);
2527
+			}
2528
+			return $this->_process_wpdb_query($wpdb_method, $arguments_to_provide);
2529
+		}
2530
+		return $result;
2531
+	}
2532
+
2533
+
2534
+	/**
2535
+	 * Verifies the EE core database is up-to-date and records that we've done it on
2536
+	 * EEM_Base::$_db_verification_level
2537
+	 *
2538
+	 * @param string $wpdb_method
2539
+	 * @param array  $arguments_to_provide
2540
+	 * @return string
2541
+	 * @throws EE_Error
2542
+	 * @throws ReflectionException
2543
+	 */
2544
+	private function _verify_core_db($wpdb_method, $arguments_to_provide)
2545
+	{
2546
+		/** @type WPDB $wpdb */
2547
+		global $wpdb;
2548
+		// ok remember that we've already attempted fixing the core db, in case the problem persists
2549
+		EEM_Base::$_db_verification_level = EEM_Base::db_verified_core;
2550
+		$error_message                    = sprintf(
2551
+			esc_html__(
2552
+				'WPDB Error "%1$s" while running wpdb method "%2$s" with arguments %3$s. Automatically attempting to fix EE Core DB',
2553
+				'event_espresso'
2554
+			),
2555
+			$wpdb->last_error,
2556
+			$wpdb_method,
2557
+			wp_json_encode($arguments_to_provide)
2558
+		);
2559
+		EE_System::instance()->initialize_db_if_no_migrations_required(false, true);
2560
+		return $error_message;
2561
+	}
2562
+
2563
+
2564
+	/**
2565
+	 * Verifies the EE addons' database is up-to-date and records that we've done it on
2566
+	 * EEM_Base::$_db_verification_level
2567
+	 *
2568
+	 * @param $wpdb_method
2569
+	 * @param $arguments_to_provide
2570
+	 * @return string
2571
+	 * @throws EE_Error
2572
+	 * @throws ReflectionException
2573
+	 */
2574
+	private function _verify_addons_db($wpdb_method, $arguments_to_provide)
2575
+	{
2576
+		/** @type WPDB $wpdb */
2577
+		global $wpdb;
2578
+		// ok remember that we've already attempted fixing the addons dbs, in case the problem persists
2579
+		EEM_Base::$_db_verification_level = EEM_Base::db_verified_addons;
2580
+		$error_message                    = sprintf(
2581
+			esc_html__(
2582
+				'WPDB AGAIN: Error "%1$s" while running the same method and arguments as before. Automatically attempting to fix EE Addons DB',
2583
+				'event_espresso'
2584
+			),
2585
+			$wpdb->last_error,
2586
+			$wpdb_method,
2587
+			wp_json_encode($arguments_to_provide)
2588
+		);
2589
+		EE_System::instance()->initialize_addons();
2590
+		return $error_message;
2591
+	}
2592
+
2593
+
2594
+	/**
2595
+	 * In order to avoid repeating this code for the get_all, sum, and count functions, put the code parts
2596
+	 * that are identical in here. Returns a string of SQL of everything in a SELECT query except the beginning
2597
+	 * SELECT clause, eg " FROM wp_posts AS Event INNER JOIN ... WHERE ... ORDER BY ... LIMIT ... GROUP BY ... HAVING
2598
+	 * ..."
2599
+	 *
2600
+	 * @param EE_Model_Query_Info_Carrier $model_query_info
2601
+	 * @return string
2602
+	 */
2603
+	private function _construct_2nd_half_of_select_query(EE_Model_Query_Info_Carrier $model_query_info)
2604
+	{
2605
+		return " FROM " . $model_query_info->get_full_join_sql() .
2606
+			   $model_query_info->get_where_sql() .
2607
+			   $model_query_info->get_group_by_sql() .
2608
+			   $model_query_info->get_having_sql() .
2609
+			   $model_query_info->get_order_by_sql() .
2610
+			   $model_query_info->get_limit_sql();
2611
+	}
2612
+
2613
+
2614
+	/**
2615
+	 * Set to easily debug the next X queries ran from this model.
2616
+	 *
2617
+	 * @param int $count
2618
+	 */
2619
+	public function show_next_x_db_queries($count = 1)
2620
+	{
2621
+		$this->_show_next_x_db_queries = $count;
2622
+	}
2623
+
2624
+
2625
+	/**
2626
+	 * @param $sql_query
2627
+	 */
2628
+	public function show_db_query_if_previously_requested($sql_query)
2629
+	{
2630
+		if ($this->_show_next_x_db_queries > 0) {
2631
+			$left = is_admin() ? '12rem' : '2rem';
2632
+			echo "
2633 2633
             <div class='ee-status-outline ee-status-bg--attention' style='margin: 2rem 2rem 2rem $left;'>
2634 2634
                 " . esc_html($sql_query) . "
2635 2635
             </div>";
2636
-            $this->_show_next_x_db_queries--;
2637
-        }
2638
-    }
2639
-
2640
-
2641
-    /**
2642
-     * Adds a relationship of the correct type between $modelObject and $otherModelObject.
2643
-     * There are the 3 cases:
2644
-     * 'belongsTo' relationship: sets $id_or_obj's foreign_key to be $other_model_id_or_obj's primary_key. If
2645
-     * $otherModelObject has no ID, it is first saved.
2646
-     * 'hasMany' relationship: sets $other_model_id_or_obj's foreign_key to be $id_or_obj's primary_key. If $id_or_obj
2647
-     * has no ID, it is first saved.
2648
-     * 'hasAndBelongsToMany' relationships: checks that there isn't already an entry in the join table, and adds one.
2649
-     * If one of the model Objects has not yet been saved to the database, it is saved before adding the entry in the
2650
-     * join table
2651
-     *
2652
-     * @param EE_Base_Class|int $id_or_obj                        EE_base_Class or ID of $thisModelObject
2653
-     * @param EE_Base_Class|int $other_model_id_or_obj            EE_base_Class or ID of other Model Object
2654
-     * @param string            $relationName                     , key in EEM_Base::_relations
2655
-     *                                                            an attendee to a group, you also want to specify
2656
-     *                                                            which role they will have in that group. So you would
2657
-     *                                                            use this parameter to specify
2658
-     *                                                            array('role-column-name'=>'role-id')
2659
-     * @param array|null        $extra_join_model_fields_n_values This allows you to enter further query params for the
2660
-     *                                                            relation to for relation to methods that allow you to
2661
-     *                                                            further specify extra columns to join by (such as
2662
-     *                                                            HABTM).  Keep in mind that the only acceptable
2663
-     *                                                            query_params is strict "col" => "value" pairs because
2664
-     *                                                            these will be inserted in any new rows created as
2665
-     *                                                            well.
2666
-     * @return EE_Base_Class which was added as a relation. Object referred to by $other_model_id_or_obj
2667
-     * @throws EE_Error
2668
-     */
2669
-    public function add_relationship_to(
2670
-        $id_or_obj,
2671
-        $other_model_id_or_obj,
2672
-        $relationName,
2673
-        $extra_join_model_fields_n_values = []
2674
-    ) {
2675
-        $relation_obj = $this->related_settings_for($relationName);
2676
-        return $relation_obj->add_relation_to($id_or_obj, $other_model_id_or_obj, $extra_join_model_fields_n_values);
2677
-    }
2678
-
2679
-
2680
-    /**
2681
-     * Removes a relationship of the correct type between $modelObject and $otherModelObject.
2682
-     * There are the 3 cases:
2683
-     * 'belongsTo' relationship: sets $modelObject's foreign_key to null, if that field is nullable.Otherwise throws an
2684
-     * error
2685
-     * 'hasMany' relationship: sets $otherModelObject's foreign_key to null,if that field is nullable.Otherwise throws
2686
-     * an error
2687
-     * 'hasAndBelongsToMany' relationships:removes any existing entry in the join table between the two models.
2688
-     *
2689
-     * @param EE_Base_Class|int $id_or_obj             EE_base_Class or ID of $thisModelObject
2690
-     * @param EE_Base_Class|int $other_model_id_or_obj EE_base_Class or ID of other Model Object
2691
-     * @param string            $relationName          key in EEM_Base::_relations
2692
-     * @param array|null        $where_query           This allows you to enter further query params for the relation
2693
-     *                                                 to for relation to methods that allow you to further specify
2694
-     *                                                 extra columns to join by (such as HABTM). Keep in mind that the
2695
-     *                                                 only acceptable query_params is strict "col" => "value" pairs
2696
-     *                                                 because these will be inserted in any new rows created as well.
2697
-     * @return EE_Base_Class
2698
-     * @throws EE_Error
2699
-     */
2700
-    public function remove_relationship_to($id_or_obj, $other_model_id_or_obj, $relationName, $where_query = [])
2701
-    {
2702
-        $relation_obj = $this->related_settings_for($relationName);
2703
-        return $relation_obj->remove_relation_to($id_or_obj, $other_model_id_or_obj, $where_query);
2704
-    }
2705
-
2706
-
2707
-    /**
2708
-     * @param mixed       $id_or_obj
2709
-     * @param string      $relationName
2710
-     * @param array|null  $where_query_params
2711
-     * @return EE_Base_Class[]
2712
-     * @throws EE_Error
2713
-     * @throws ReflectionException
2714
-     */
2715
-    public function remove_relations($id_or_obj, $relationName, $where_query_params = [])
2716
-    {
2717
-        $relation_obj = $this->related_settings_for($relationName);
2718
-        return $relation_obj->remove_relations($id_or_obj, $where_query_params);
2719
-    }
2720
-
2721
-
2722
-    /**
2723
-     * Gets all the related items of the specified $model_name, using $query_params.
2724
-     * Note: by default, we remove the "default query params"
2725
-     * because we want to get even deleted items etc.
2726
-     *
2727
-     * @param mixed       $id_or_obj    EE_Base_Class child or its ID
2728
-     * @param string      $model_name   like 'Event', 'Registration', etc. always singular
2729
-     * @param array|null  $query_params @see
2730
-     *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2731
-     * @return EE_Base_Class[]
2732
-     * @throws EE_Error
2733
-     * @throws ReflectionException
2734
-     */
2735
-    public function get_all_related($id_or_obj, $model_name, ?array $query_params = [])
2736
-    {
2737
-        $model_obj         = $this->ensure_is_obj($id_or_obj);
2738
-        $relation_settings = $this->related_settings_for($model_name);
2739
-        return $relation_settings->get_all_related($model_obj, $query_params);
2740
-    }
2741
-
2742
-
2743
-    /**
2744
-     * Deletes all the model objects across the relation indicated by $model_name
2745
-     * which are related to $id_or_obj which meet the criteria set in $query_params.
2746
-     * However, if the model objects can't be deleted because of blocking related model objects, then
2747
-     * they aren't deleted. (Unless the thing that would have been deleted can be soft-deleted, that still happens).
2748
-     *
2749
-     * @param EE_Base_Class|int|string $id_or_obj
2750
-     * @param string                   $model_name
2751
-     * @param array|null               $query_params
2752
-     * @return int how many deleted
2753
-     * @throws EE_Error
2754
-     * @throws ReflectionException
2755
-     */
2756
-    public function delete_related($id_or_obj, $model_name, $query_params = [])
2757
-    {
2758
-        $model_obj         = $this->ensure_is_obj($id_or_obj);
2759
-        $relation_settings = $this->related_settings_for($model_name);
2760
-        return $relation_settings->delete_all_related($model_obj, $query_params);
2761
-    }
2762
-
2763
-
2764
-    /**
2765
-     * Hard deletes all the model objects across the relation indicated by $model_name
2766
-     * which are related to $id_or_obj which meet the criteria set in $query_params. If
2767
-     * the model objects can't be hard deleted because of blocking related model objects,
2768
-     * just does a soft-delete on them instead.
2769
-     *
2770
-     * @param EE_Base_Class|int|string $id_or_obj
2771
-     * @param string                   $model_name
2772
-     * @param array|null               $query_params
2773
-     * @return int how many deleted
2774
-     * @throws EE_Error
2775
-     * @throws ReflectionException
2776
-     */
2777
-    public function delete_related_permanently($id_or_obj, $model_name, $query_params = [])
2778
-    {
2779
-        $model_obj         = $this->ensure_is_obj($id_or_obj);
2780
-        $relation_settings = $this->related_settings_for($model_name);
2781
-        return $relation_settings->delete_related_permanently($model_obj, $query_params);
2782
-    }
2783
-
2784
-
2785
-    /**
2786
-     * Instead of getting the related model objects, simply counts them. Ignores default_where_conditions by default,
2787
-     * unless otherwise specified in the $query_params
2788
-     *
2789
-     * @param EE_Base_Class|int|string $id_or_obj
2790
-     * @param string                   $model_name     like 'Event', or 'Registration'
2791
-     * @param array|null               $query_params   @see
2792
-     *                                                 https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2793
-     * @param string                   $field_to_count name of field to count by. By default, uses primary key
2794
-     * @param bool                     $distinct       if we want to only count the distinct values for the column then
2795
-     *                                                 you can trigger that by the setting $distinct to TRUE;
2796
-     * @return int
2797
-     * @throws EE_Error
2798
-     * @throws ReflectionException
2799
-     */
2800
-    public function count_related(
2801
-        $id_or_obj,
2802
-        $model_name,
2803
-        $query_params = [],
2804
-        $field_to_count = null,
2805
-        $distinct = false
2806
-    ) {
2807
-        $related_model = $this->get_related_model_obj($model_name);
2808
-        // we're just going to use the query params on the related model's normal get_all query,
2809
-        // except add a condition to say to match the current mod
2810
-        if (! isset($query_params['default_where_conditions'])) {
2811
-            $query_params['default_where_conditions'] = EEM_Base::default_where_conditions_none;
2812
-        }
2813
-        $this_model_name                                                 = $this->get_this_model_name();
2814
-        $this_pk_field_name                                              = $this->get_primary_key_field()->get_name();
2815
-        $query_params[0][ $this_model_name . "." . $this_pk_field_name ] = $id_or_obj;
2816
-        return $related_model->count($query_params, $field_to_count, $distinct);
2817
-    }
2818
-
2819
-
2820
-    /**
2821
-     * Instead of getting the related model objects, simply sums up the values of the specified field.
2822
-     * Note: ignores default_where_conditions by default, unless otherwise specified in the $query_params
2823
-     *
2824
-     * @param EE_Base_Class|int|string $id_or_obj
2825
-     * @param string                   $model_name   like 'Event', or 'Registration'
2826
-     * @param array|null               $query_params @see
2827
-     *                                               https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2828
-     * @param string                   $field_to_sum name of field to count by. By default, uses primary key
2829
-     * @return float
2830
-     * @throws EE_Error
2831
-     * @throws ReflectionException
2832
-     */
2833
-    public function sum_related($id_or_obj, $model_name, $query_params, $field_to_sum = null)
2834
-    {
2835
-        $related_model = $this->get_related_model_obj($model_name);
2836
-        if (! is_array($query_params)) {
2837
-            EE_Error::doing_it_wrong(
2838
-                'EEM_Base::sum_related',
2839
-                sprintf(
2840
-                    esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
2841
-                    gettype($query_params)
2842
-                ),
2843
-                '4.6.0'
2844
-            );
2845
-            $query_params = [];
2846
-        }
2847
-        // we're just going to use the query params on the related model's normal get_all query,
2848
-        // except add a condition to say to match the current mod
2849
-        if (! isset($query_params['default_where_conditions'])) {
2850
-            $query_params['default_where_conditions'] = EEM_Base::default_where_conditions_none;
2851
-        }
2852
-        $this_model_name                                                 = $this->get_this_model_name();
2853
-        $this_pk_field_name                                              = $this->get_primary_key_field()->get_name();
2854
-        $query_params[0][ $this_model_name . "." . $this_pk_field_name ] = $id_or_obj;
2855
-        return $related_model->sum($query_params, $field_to_sum);
2856
-    }
2857
-
2858
-
2859
-    /**
2860
-     * Uses $this->_relatedModels info to find the first related model object of relation $relationName to the given
2861
-     * $modelObject
2862
-     *
2863
-     * @param int | EE_Base_Class $id_or_obj        EE_Base_Class child or its ID
2864
-     * @param string              $other_model_name , key in $this->_relatedModels, eg 'Registration', or 'Events'
2865
-     * @param array|null          $query_params     @see
2866
-     *                                              https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2867
-     * @return EE_Base_Class
2868
-     * @throws EE_Error
2869
-     * @throws ReflectionException
2870
-     */
2871
-    public function get_first_related(EE_Base_Class $id_or_obj, $other_model_name, $query_params)
2872
-    {
2873
-        $query_params['limit'] = 1;
2874
-        $results               = $this->get_all_related($id_or_obj, $other_model_name, $query_params);
2875
-        if ($results) {
2876
-            return array_shift($results);
2877
-        }
2878
-        return null;
2879
-    }
2880
-
2881
-
2882
-    /**
2883
-     * Gets the model's name as it's expected in queries. For example, if this is EEM_Event model, that would be Event
2884
-     *
2885
-     * @return string
2886
-     */
2887
-    public function get_this_model_name()
2888
-    {
2889
-        return str_replace("EEM_", "", get_class($this));
2890
-    }
2891
-
2892
-
2893
-    /**
2894
-     * Gets the model field on this model which is of type EE_Any_Foreign_Model_Name_Field
2895
-     *
2896
-     * @return EE_Any_Foreign_Model_Name_Field
2897
-     * @throws EE_Error
2898
-     */
2899
-    public function get_field_containing_related_model_name()
2900
-    {
2901
-        foreach ($this->field_settings(true) as $field) {
2902
-            if ($field instanceof EE_Any_Foreign_Model_Name_Field) {
2903
-                $field_with_model_name = $field;
2904
-            }
2905
-        }
2906
-        if (! isset($field_with_model_name) || ! $field_with_model_name) {
2907
-            throw new EE_Error(
2908
-                sprintf(
2909
-                    esc_html__("There is no EE_Any_Foreign_Model_Name field on model %s", "event_espresso"),
2910
-                    $this->get_this_model_name()
2911
-                )
2912
-            );
2913
-        }
2914
-        return $field_with_model_name;
2915
-    }
2916
-
2917
-
2918
-    /**
2919
-     * Inserts a new entry into the database, for each table.
2920
-     * Note: does not add the item to the entity map because that is done by EE_Base_Class::save() right after this.
2921
-     * If client code uses EEM_Base::insert() directly, then although the item isn't in the entity map,
2922
-     * we also know there is no model object with the newly inserted item's ID at the moment (because
2923
-     * if there were, then they would already be in the DB and this would fail); and in the future if someone
2924
-     * creates a model object with this ID (or grabs it from the DB) then it will be added to the
2925
-     * entity map at that time anyways. SO, no need for EEM_Base::insert ot add to the entity map
2926
-     *
2927
-     * @param array $field_n_values keys are field names, values are their values (in the client code's domain if
2928
-     *                              $values_already_prepared_by_model_object is false, in the model object's domain if
2929
-     *                              $values_already_prepared_by_model_object is true. See comment about this at the top
2930
-     *                              of EEM_Base)
2931
-     * @return int|string new primary key on main table that got inserted
2932
-     * @throws EE_Error
2933
-     * @throws ReflectionException
2934
-     */
2935
-    public function insert($field_n_values)
2936
-    {
2937
-        /**
2938
-         * Filters the fields and their values before inserting an item using the models
2939
-         *
2940
-         * @param array    $fields_n_values keys are the fields and values are their new values
2941
-         * @param EEM_Base $model           the model used
2942
-         */
2943
-        $field_n_values = (array) apply_filters('FHEE__EEM_Base__insert__fields_n_values', $field_n_values, $this);
2944
-        if ($this->_satisfies_unique_indexes($field_n_values)) {
2945
-            $main_table = $this->_get_main_table();
2946
-            $new_id     = $this->_insert_into_specific_table($main_table, $field_n_values, false);
2947
-            if ($new_id !== false) {
2948
-                foreach ($this->_get_other_tables() as $other_table) {
2949
-                    $this->_insert_into_specific_table($other_table, $field_n_values, $new_id);
2950
-                }
2951
-            }
2952
-            /**
2953
-             * Done just after attempting to insert a new model object
2954
-             *
2955
-             * @param EEM_Base $model           used
2956
-             * @param array    $fields_n_values fields and their values
2957
-             * @param int|string the              ID of the newly-inserted model object
2958
-             */
2959
-            do_action('AHEE__EEM_Base__insert__end', $this, $field_n_values, $new_id);
2960
-            return $new_id;
2961
-        }
2962
-        return false;
2963
-    }
2964
-
2965
-
2966
-    /**
2967
-     * Checks that the result would satisfy the unique indexes on this model
2968
-     *
2969
-     * @param array  $field_n_values
2970
-     * @param string $action
2971
-     * @return boolean
2972
-     * @throws EE_Error
2973
-     * @throws ReflectionException
2974
-     */
2975
-    protected function _satisfies_unique_indexes(array $field_n_values, $action = 'insert')
2976
-    {
2977
-        foreach ($this->unique_indexes() as $index_name => $index) {
2978
-            $uniqueness_where_params = array_intersect_key($field_n_values, $index->fields());
2979
-            if ($this->exists([$uniqueness_where_params])) {
2980
-                EE_Error::add_error(
2981
-                    sprintf(
2982
-                        esc_html__(
2983
-                            "Could not %s %s. %s uniqueness index failed. Fields %s must form a unique set, but an entry already exists with values %s.",
2984
-                            "event_espresso"
2985
-                        ),
2986
-                        $action,
2987
-                        $this->_get_class_name(),
2988
-                        $index_name,
2989
-                        implode(",", $index->field_names()),
2990
-                        http_build_query($uniqueness_where_params)
2991
-                    ),
2992
-                    __FILE__,
2993
-                    __FUNCTION__,
2994
-                    __LINE__
2995
-                );
2996
-                return false;
2997
-            }
2998
-        }
2999
-        return true;
3000
-    }
3001
-
3002
-
3003
-    /**
3004
-     * Checks the database for an item that conflicts (ie, if this item were
3005
-     * saved to the DB would break some uniqueness requirement, like a primary key
3006
-     * or an index primary key set) with the item specified. $id_obj_or_fields_array
3007
-     * can be either an EE_Base_Class or an array of fields n values
3008
-     *
3009
-     * @param EE_Base_Class|array $obj_or_fields_array
3010
-     * @param boolean             $include_primary_key whether to use the model object's primary key
3011
-     *                                                 when looking for conflicts
3012
-     *                                                 (ie, if false, we ignore the model object's primary key
3013
-     *                                                 when finding "conflicts". If true, it's also considered).
3014
-     *                                                 Only works for INT primary key,
3015
-     *                                                 STRING primary keys cannot be ignored
3016
-     * @return EE_Base_Class|array
3017
-     * @throws EE_Error
3018
-     * @throws ReflectionException
3019
-     */
3020
-    public function get_one_conflicting($obj_or_fields_array, $include_primary_key = true)
3021
-    {
3022
-        if ($obj_or_fields_array instanceof EE_Base_Class) {
3023
-            $fields_n_values = $obj_or_fields_array->model_field_array();
3024
-        } elseif (is_array($obj_or_fields_array)) {
3025
-            $fields_n_values = $obj_or_fields_array;
3026
-        } else {
3027
-            throw new EE_Error(
3028
-                sprintf(
3029
-                    esc_html__(
3030
-                        "%s get_all_conflicting should be called with a model object or an array of field names and values, you provided %d",
3031
-                        "event_espresso"
3032
-                    ),
3033
-                    get_class($this),
3034
-                    $obj_or_fields_array
3035
-                )
3036
-            );
3037
-        }
3038
-        $query_params = [];
3039
-        if (
3040
-            $this->has_primary_key_field()
3041
-            && ($include_primary_key
3042
-                || $this->get_primary_key_field()
3043
-                   instanceof
3044
-                   EE_Primary_Key_String_Field)
3045
-            && isset($fields_n_values[ $this->primary_key_name() ])
3046
-        ) {
3047
-            $query_params[0]['OR'][ $this->primary_key_name() ] = $fields_n_values[ $this->primary_key_name() ];
3048
-        }
3049
-        foreach ($this->unique_indexes() as $unique_index_name => $unique_index) {
3050
-            $uniqueness_where_params                              =
3051
-                array_intersect_key($fields_n_values, $unique_index->fields());
3052
-            $query_params[0]['OR'][ 'AND*' . $unique_index_name ] = $uniqueness_where_params;
3053
-        }
3054
-        // if there is nothing to base this search on, then we shouldn't find anything
3055
-        if (empty($query_params)) {
3056
-            return [];
3057
-        }
3058
-        return $this->get_one($query_params);
3059
-    }
3060
-
3061
-
3062
-    /**
3063
-     * Like count, but is optimized and returns a boolean instead of an int
3064
-     *
3065
-     * @param array $query_params
3066
-     * @return boolean
3067
-     * @throws EE_Error
3068
-     * @throws ReflectionException
3069
-     */
3070
-    public function exists($query_params)
3071
-    {
3072
-        $query_params['limit'] = 1;
3073
-        return $this->count($query_params) > 0;
3074
-    }
3075
-
3076
-
3077
-    /**
3078
-     * Wrapper for exists, except ignores default query parameters so we're only considering ID
3079
-     *
3080
-     * @param int|string $id
3081
-     * @return boolean
3082
-     * @throws EE_Error
3083
-     * @throws ReflectionException
3084
-     */
3085
-    public function exists_by_ID($id)
3086
-    {
3087
-        return $this->exists(
3088
-            [
3089
-                'default_where_conditions' => EEM_Base::default_where_conditions_none,
3090
-                [
3091
-                    $this->primary_key_name() => $id,
3092
-                ],
3093
-            ]
3094
-        );
3095
-    }
3096
-
3097
-
3098
-    /**
3099
-     * Inserts a new row in $table, using the $cols_n_values which apply to that table.
3100
-     * If a $new_id is supplied and if $table is an EE_Other_Table, we assume
3101
-     * we need to add a foreign key column to point to $new_id (which should be the primary key's value
3102
-     * on the main table)
3103
-     * This is protected rather than private because private is not accessible to any child methods and there MAY be
3104
-     * cases where we want to call it directly rather than via insert().
3105
-     *
3106
-     * @access   protected
3107
-     * @param EE_Table_Base $table
3108
-     * @param array         $fields_n_values each key should be in field's keys, and value should be an int, string or
3109
-     *                                       float
3110
-     * @param int           $new_id          for now we assume only int keys
3111
-     * @return int ID of new row inserted, or FALSE on failure
3112
-     * @throws EE_Error
3113
-     * @global WPDB         $wpdb            only used to get the $wpdb->insert_id after performing an insert
3114
-     */
3115
-    protected function _insert_into_specific_table(EE_Table_Base $table, $fields_n_values, $new_id = 0)
3116
-    {
3117
-        global $wpdb;
3118
-        $insertion_col_n_values = [];
3119
-        $format_for_insertion   = [];
3120
-        $fields_on_table        = $this->_get_fields_for_table($table->get_table_alias());
3121
-        foreach ($fields_on_table as $field_obj) {
3122
-            // check if its an auto-incrementing column, in which case we should just leave it to do its autoincrement thing
3123
-            if ($field_obj->is_auto_increment()) {
3124
-                continue;
3125
-            }
3126
-            $prepared_value = $this->_prepare_value_or_use_default($field_obj, $fields_n_values);
3127
-            // if the value we want to assign it to is NULL, just don't mention it for the insertion
3128
-            if ($prepared_value !== null) {
3129
-                $insertion_col_n_values[ $field_obj->get_table_column() ] = $prepared_value;
3130
-                $format_for_insertion[]                                   = $field_obj->get_wpdb_data_type();
3131
-            }
3132
-        }
3133
-        if ($table instanceof EE_Secondary_Table && $new_id) {
3134
-            // its not the main table, so we should have already saved the main table's PK which we just inserted
3135
-            // so add the fk to the main table as a column
3136
-            $insertion_col_n_values[ $table->get_fk_on_table() ] = $new_id;
3137
-            $format_for_insertion[]                              =
3138
-                '%d';// yes right now we're only allowing these foreign keys to be INTs
3139
-        }
3140
-
3141
-        // insert the new entry
3142
-        $result = $this->_do_wpdb_query(
3143
-            'insert',
3144
-            [$table->get_table_name(), $insertion_col_n_values, $format_for_insertion]
3145
-        );
3146
-        if ($result === false) {
3147
-            return false;
3148
-        }
3149
-        // ok, now what do we return for the ID of the newly-inserted thing?
3150
-        if ($this->has_primary_key_field()) {
3151
-            if ($this->get_primary_key_field()->is_auto_increment()) {
3152
-                return $wpdb->insert_id;
3153
-            }
3154
-            // it's not an auto-increment primary key, so
3155
-            // it must have been supplied
3156
-            return $fields_n_values[ $this->get_primary_key_field()->get_name() ];
3157
-        }
3158
-        // we can't return a  primary key because there is none. instead return
3159
-        // a unique string indicating this model
3160
-        return $this->get_index_primary_key_string($fields_n_values);
3161
-    }
3162
-
3163
-
3164
-    /**
3165
-     * Prepare the $field_obj 's value in $fields_n_values for use in the database.
3166
-     * If the field doesn't allow NULL, try to use its default. (If it doesn't allow NULL,
3167
-     * and there is no default, we pass it along. WPDB will take care of it)
3168
-     *
3169
-     * @param EE_Model_Field_Base $field_obj
3170
-     * @param array               $fields_n_values
3171
-     * @return mixed string|int|float depending on what the table column will be expecting
3172
-     * @throws EE_Error
3173
-     */
3174
-    protected function _prepare_value_or_use_default($field_obj, $fields_n_values)
3175
-    {
3176
-        $field_name = $field_obj->get_name();
3177
-        // if this field doesn't allow nullable, don't allow it
3178
-        if (! $field_obj->is_nullable() && ! isset($fields_n_values[ $field_name ])) {
3179
-            $fields_n_values[ $field_name ] = $field_obj->get_default_value();
3180
-        }
3181
-        $unprepared_value = $fields_n_values[ $field_name ] ?? null;
3182
-        return $this->_prepare_value_for_use_in_db($unprepared_value, $field_obj);
3183
-    }
3184
-
3185
-
3186
-    /**
3187
-     * Consolidates code for preparing  a value supplied to the model for use int eh db. Calls the field's
3188
-     * prepare_for_use_in_db method on the value, and depending on $value_already_prepare_by_model_obj, may also call
3189
-     * the field's prepare_for_set() method.
3190
-     *
3191
-     * @param mixed               $value value in the client code domain if $value_already_prepared_by_model_object is
3192
-     *                                   false, otherwise a value in the model object's domain (see lengthy comment at
3193
-     *                                   top of file)
3194
-     * @param EE_Model_Field_Base $field field which will be doing the preparing of the value. If null, we assume
3195
-     *                                   $value is a custom selection
3196
-     * @return mixed a value ready for use in the database for insertions, updating, or in a where clause
3197
-     */
3198
-    private function _prepare_value_for_use_in_db($value, $field)
3199
-    {
3200
-        if ($field instanceof EE_Model_Field_Base) {
3201
-            // phpcs:disable PSR2.ControlStructures.SwitchDeclaration.TerminatingComment
3202
-            switch ($this->_values_already_prepared_by_model_object) {
3203
-                /** @noinspection PhpMissingBreakStatementInspection */
3204
-                case self::not_prepared_by_model_object:
3205
-                    $value = $field->prepare_for_set($value);
3206
-                // purposefully left out "return"
3207
-                // no break
3208
-                case self::prepared_by_model_object:
3209
-                    /** @noinspection SuspiciousAssignmentsInspection */
3210
-                    $value = $field->prepare_for_use_in_db($value);
3211
-                // no break
3212
-                case self::prepared_for_use_in_db:
3213
-                    // leave the value alone
3214
-            }
3215
-            // phpcs:enable
3216
-        }
3217
-        return $value;
3218
-    }
3219
-
3220
-
3221
-    /**
3222
-     * Returns the main table on this model
3223
-     *
3224
-     * @return EE_Primary_Table
3225
-     * @throws EE_Error
3226
-     */
3227
-    protected function _get_main_table()
3228
-    {
3229
-        foreach ($this->_tables as $table) {
3230
-            if ($table instanceof EE_Primary_Table) {
3231
-                return $table;
3232
-            }
3233
-        }
3234
-        throw new EE_Error(
3235
-            sprintf(
3236
-                esc_html__(
3237
-                    'There are no main tables on %s. They should be added to _tables array in the constructor',
3238
-                    'event_espresso'
3239
-                ),
3240
-                get_class($this)
3241
-            )
3242
-        );
3243
-    }
3244
-
3245
-
3246
-    /**
3247
-     * table
3248
-     * returns EE_Primary_Table table name
3249
-     *
3250
-     * @return string
3251
-     * @throws EE_Error
3252
-     */
3253
-    public function table()
3254
-    {
3255
-        return $this->_get_main_table()->get_table_name();
3256
-    }
3257
-
3258
-
3259
-    /**
3260
-     * table
3261
-     * returns first EE_Secondary_Table table name
3262
-     *
3263
-     * @return string
3264
-     */
3265
-    public function second_table()
3266
-    {
3267
-        // grab second table from tables array
3268
-        $second_table = end($this->_tables);
3269
-        return $second_table instanceof EE_Secondary_Table
3270
-            ? $second_table->get_table_name()
3271
-            : null;
3272
-    }
3273
-
3274
-
3275
-    /**
3276
-     * get_table_obj_by_alias
3277
-     * returns table name given it's alias
3278
-     *
3279
-     * @param string $table_alias
3280
-     * @return EE_Primary_Table | EE_Secondary_Table
3281
-     */
3282
-    public function get_table_obj_by_alias($table_alias = '')
3283
-    {
3284
-        return $this->_tables[ $table_alias ] ?? null;
3285
-    }
3286
-
3287
-
3288
-    /**
3289
-     * Gets all the tables of type EE_Other_Table from EEM_CPT_Basel_Model::_tables
3290
-     *
3291
-     * @return EE_Secondary_Table[]
3292
-     */
3293
-    protected function _get_other_tables()
3294
-    {
3295
-        $other_tables = [];
3296
-        foreach ($this->_tables as $table_alias => $table) {
3297
-            if ($table instanceof EE_Secondary_Table) {
3298
-                $other_tables[ $table_alias ] = $table;
3299
-            }
3300
-        }
3301
-        return $other_tables;
3302
-    }
3303
-
3304
-
3305
-    /**
3306
-     * Finds all the fields that correspond to the given table
3307
-     *
3308
-     * @param string $table_alias , array key in EEM_Base::_tables
3309
-     * @return EE_Model_Field_Base[]
3310
-     */
3311
-    public function _get_fields_for_table($table_alias)
3312
-    {
3313
-        return $this->_fields[ $table_alias ];
3314
-    }
3315
-
3316
-
3317
-    /**
3318
-     * Recurses through all the where parameters, and finds all the related models we'll need
3319
-     * to complete this query. Eg, given where parameters like array('EVT_ID'=>3) from within Event model, we won't
3320
-     * need any related models. But if the array were array('Registrations.REG_ID'=>3), we'd need the related
3321
-     * Registration model. If it were array('Registrations.Transactions.Payments.PAY_ID'=>3), then we'd need the
3322
-     * related Registration, Transaction, and Payment models.
3323
-     *
3324
-     * @param array $query_params @see
3325
-     *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
3326
-     * @return EE_Model_Query_Info_Carrier
3327
-     * @throws EE_Error
3328
-     */
3329
-    public function _extract_related_models_from_query($query_params)
3330
-    {
3331
-        $query_info_carrier = new EE_Model_Query_Info_Carrier();
3332
-        if (array_key_exists(0, $query_params)) {
3333
-            $this->_extract_related_models_from_sub_params_array_keys($query_params[0], $query_info_carrier, 0);
3334
-        }
3335
-        if (array_key_exists('group_by', $query_params)) {
3336
-            if (is_array($query_params['group_by'])) {
3337
-                $this->_extract_related_models_from_sub_params_array_values(
3338
-                    $query_params['group_by'],
3339
-                    $query_info_carrier,
3340
-                    'group_by'
3341
-                );
3342
-            } elseif (! empty($query_params['group_by'])) {
3343
-                $this->_extract_related_model_info_from_query_param(
3344
-                    $query_params['group_by'],
3345
-                    $query_info_carrier,
3346
-                    'group_by'
3347
-                );
3348
-            }
3349
-        }
3350
-        if (array_key_exists('having', $query_params)) {
3351
-            $this->_extract_related_models_from_sub_params_array_keys(
3352
-                $query_params[0],
3353
-                $query_info_carrier,
3354
-                'having'
3355
-            );
3356
-        }
3357
-        if (array_key_exists('order_by', $query_params)) {
3358
-            if (is_array($query_params['order_by'])) {
3359
-                $this->_extract_related_models_from_sub_params_array_keys(
3360
-                    $query_params['order_by'],
3361
-                    $query_info_carrier,
3362
-                    'order_by'
3363
-                );
3364
-            } elseif (! empty($query_params['order_by'])) {
3365
-                $this->_extract_related_model_info_from_query_param(
3366
-                    $query_params['order_by'],
3367
-                    $query_info_carrier,
3368
-                    'order_by'
3369
-                );
3370
-            }
3371
-        }
3372
-        if (array_key_exists('force_join', $query_params)) {
3373
-            $this->_extract_related_models_from_sub_params_array_values(
3374
-                $query_params['force_join'],
3375
-                $query_info_carrier,
3376
-                'force_join'
3377
-            );
3378
-        }
3379
-        $this->extractRelatedModelsFromCustomSelects($query_info_carrier);
3380
-        return $query_info_carrier;
3381
-    }
3382
-
3383
-
3384
-    /**
3385
-     * For extracting related models from WHERE (0), HAVING (having), ORDER BY (order_by) or forced joins (force_join)
3386
-     *
3387
-     * @param array                       $sub_query_params
3388
-     * @param EE_Model_Query_Info_Carrier $model_query_info_carrier
3389
-     * @param string                      $query_param_type one of $this->_allowed_query_params
3390
-     * @return EE_Model_Query_Info_Carrier
3391
-     * @throws EE_Error
3392
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#-0-where-conditions
3393
-     */
3394
-    private function _extract_related_models_from_sub_params_array_keys(
3395
-        $sub_query_params,
3396
-        EE_Model_Query_Info_Carrier $model_query_info_carrier,
3397
-        $query_param_type
3398
-    ) {
3399
-        if (! empty($sub_query_params)) {
3400
-            $sub_query_params = (array) $sub_query_params;
3401
-            foreach ($sub_query_params as $param => $possibly_array_of_params) {
3402
-                // $param could be simply 'EVT_ID', or it could be 'Registrations.REG_ID', or even 'Registrations.Transactions.Payments.PAY_amount'
3403
-                $this->_extract_related_model_info_from_query_param(
3404
-                    $param,
3405
-                    $model_query_info_carrier,
3406
-                    $query_param_type
3407
-                );
3408
-                // if $possibly_array_of_params is an array, try recursing into it, searching for keys which
3409
-                // indicate needed joins. Eg, array('NOT'=>array('Registration.TXN_ID'=>23)). In this case, we tried
3410
-                // extracting models out of the 'NOT', which obviously wasn't successful, and then we recurse into the value
3411
-                // of array('Registration.TXN_ID'=>23)
3412
-                $query_param_sans_stars =
3413
-                    $this->_remove_stars_and_anything_after_from_condition_query_param_key($param);
3414
-                if (in_array($query_param_sans_stars, $this->_logic_query_param_keys, true)) {
3415
-                    if (! is_array($possibly_array_of_params)) {
3416
-                        throw new EE_Error(
3417
-                            sprintf(
3418
-                                esc_html__(
3419
-                                    "You used a special where query param %s, but the value isn't an array of where query params, it's just %s'. It should be an array, eg array('EVT_ID'=>23,'OR'=>array('Venue.VNU_ID'=>32,'Venue.VNU_name'=>'monkey_land'))",
3420
-                                    "event_espresso"
3421
-                                ),
3422
-                                $param,
3423
-                                $possibly_array_of_params
3424
-                            )
3425
-                        );
3426
-                    }
3427
-                    $this->_extract_related_models_from_sub_params_array_keys(
3428
-                        $possibly_array_of_params,
3429
-                        $model_query_info_carrier,
3430
-                        $query_param_type
3431
-                    );
3432
-                } elseif (
3433
-                    $query_param_type === 0 // ie WHERE
3434
-                    && is_array($possibly_array_of_params) // need is_array() check so we don't try to explode a string
3435
-                    && isset($possibly_array_of_params[2])
3436
-                    && $possibly_array_of_params[2]
3437
-                ) {
3438
-                    // then $possible_array_of_params looks something like array('<','DTT_sold',true)
3439
-                    // indicating that $possible_array_of_params[1] is actually a field name,
3440
-                    // from which we should extract query parameters!
3441
-                    if (! isset($possibly_array_of_params[0], $possibly_array_of_params[1])) {
3442
-                        throw new EE_Error(
3443
-                            sprintf(
3444
-                                esc_html__(
3445
-                                    "Improperly formed query parameter %s. It should be numerically indexed like array('<','DTT_sold',true); but you provided %s",
3446
-                                    "event_espresso"
3447
-                                ),
3448
-                                $query_param_type,
3449
-                                implode(",", $possibly_array_of_params)
3450
-                            )
3451
-                        );
3452
-                    }
3453
-                    $this->_extract_related_model_info_from_query_param(
3454
-                        $possibly_array_of_params[1],
3455
-                        $model_query_info_carrier,
3456
-                        $query_param_type
3457
-                    );
3458
-                }
3459
-            }
3460
-        }
3461
-        return $model_query_info_carrier;
3462
-    }
3463
-
3464
-
3465
-    /**
3466
-     * For extracting related models from forced_joins, where the array values contain the info about what
3467
-     * models to join with. Eg an array like array('Attendee','Price.Price_Type');
3468
-     *
3469
-     * @param array                       $sub_query_params @see
3470
-     *                                                      https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3471
-     * @param EE_Model_Query_Info_Carrier $model_query_info_carrier
3472
-     * @param string                      $query_param_type one of $this->_allowed_query_params
3473
-     * @return EE_Model_Query_Info_Carrier
3474
-     * @throws EE_Error
3475
-     */
3476
-    private function _extract_related_models_from_sub_params_array_values(
3477
-        $sub_query_params,
3478
-        EE_Model_Query_Info_Carrier $model_query_info_carrier,
3479
-        $query_param_type
3480
-    ) {
3481
-        if (! empty($sub_query_params)) {
3482
-            if (! is_array($sub_query_params)) {
3483
-                throw new EE_Error(
3484
-                    sprintf(
3485
-                        esc_html__("Query parameter %s should be an array, but it isn't.", "event_espresso"),
3486
-                        $sub_query_params
3487
-                    )
3488
-                );
3489
-            }
3490
-            foreach ($sub_query_params as $param) {
3491
-                // $param could be simply 'EVT_ID', or it could be 'Registrations.REG_ID', or even 'Registrations.Transactions.Payments.PAY_amount'
3492
-                $this->_extract_related_model_info_from_query_param(
3493
-                    $param,
3494
-                    $model_query_info_carrier,
3495
-                    $query_param_type
3496
-                );
3497
-            }
3498
-        }
3499
-        return $model_query_info_carrier;
3500
-    }
3501
-
3502
-
3503
-    /**
3504
-     * Extract all the query parts from  model query params
3505
-     * and put into a EEM_Related_Model_Info_Carrier for easy extraction into a query. We create this object
3506
-     * instead of directly constructing the SQL because often we need to extract info from the $query_params
3507
-     * but use them in a different order. Eg, we need to know what models we are querying
3508
-     * before we know what joins to perform. However, we need to know what data types correspond to which fields on
3509
-     * other models before we can finalize the where clause SQL.
3510
-     *
3511
-     * @param array $query_params
3512
-     * @return EE_Model_Query_Info_Carrier
3513
-     * @throws EE_Error
3514
-     * @throws ModelConfigurationException
3515
-     * @throws ReflectionException
3516
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
3517
-     */
3518
-    public function _create_model_query_info_carrier($query_params)
3519
-    {
3520
-        if (! is_array($query_params)) {
3521
-            EE_Error::doing_it_wrong(
3522
-                'EEM_Base::_create_model_query_info_carrier',
3523
-                sprintf(
3524
-                    esc_html__(
3525
-                        '$query_params should be an array, you passed a variable of type %s',
3526
-                        'event_espresso'
3527
-                    ),
3528
-                    gettype($query_params)
3529
-                ),
3530
-                '4.6.0'
3531
-            );
3532
-            $query_params = [];
3533
-        }
3534
-        $query_params[0] = $query_params[0] ?? [];
3535
-        // first check if we should alter the query to account for caps or not
3536
-        // because the caps might require us to do extra joins
3537
-        if (isset($query_params['caps']) && $query_params['caps'] !== 'none') {
3538
-            $query_params[0] = array_replace_recursive(
3539
-                $query_params[0],
3540
-                $this->caps_where_conditions($query_params['caps'])
3541
-            );
3542
-        }
3543
-
3544
-        // check if we should alter the query to remove data related to protected
3545
-        // custom post types
3546
-        if (isset($query_params['exclude_protected']) && $query_params['exclude_protected'] === true) {
3547
-            $where_param_key_for_password = $this->modelChainAndPassword();
3548
-            // only include if related to a cpt where no password has been set
3549
-            $query_params[0]['OR*nopassword'] = [
3550
-                $where_param_key_for_password       => '',
3551
-                $where_param_key_for_password . '*' => ['IS_NULL'],
3552
-            ];
3553
-        }
3554
-        $query_object = $this->_extract_related_models_from_query($query_params);
3555
-        // verify where_query_params has NO numeric indexes.... that's simply not how you use it!
3556
-        foreach ($query_params[0] as $key => $value) {
3557
-            if (is_int($key)) {
3558
-                throw new EE_Error(
3559
-                    sprintf(
3560
-                        esc_html__(
3561
-                            "WHERE query params must NOT be numerically-indexed. You provided the array key '%s' for value '%s' while querying model %s. All the query params provided were '%s' Please read documentation on EEM_Base::get_all.",
3562
-                            "event_espresso"
3563
-                        ),
3564
-                        $key,
3565
-                        var_export($value, true),
3566
-                        var_export($query_params, true),
3567
-                        get_class($this)
3568
-                    )
3569
-                );
3570
-            }
3571
-        }
3572
-        if (
3573
-            array_key_exists('default_where_conditions', $query_params)
3574
-            && ! empty($query_params['default_where_conditions'])
3575
-        ) {
3576
-            $use_default_where_conditions = $query_params['default_where_conditions'];
3577
-        } else {
3578
-            $use_default_where_conditions = EEM_Base::default_where_conditions_all;
3579
-        }
3580
-        $query_params[0] = array_merge(
3581
-            $this->_get_default_where_conditions_for_models_in_query(
3582
-                $query_object,
3583
-                $use_default_where_conditions,
3584
-                $query_params[0]
3585
-            ),
3586
-            $query_params[0]
3587
-        );
3588
-        $query_object->set_where_sql($this->_construct_where_clause($query_params[0]));
3589
-        // if this is a "on_join_limit" then we are limiting on on a specific table in a multi_table join.
3590
-        // So we need to setup a subquery and use that for the main join.
3591
-        // Note for now this only works on the primary table for the model.
3592
-        // So for instance, you could set the limit array like this:
3593
-        // array( 'on_join_limit' => array('Primary_Table_Alias', array(1,10) ) )
3594
-        if (array_key_exists('on_join_limit', $query_params) && ! empty($query_params['on_join_limit'])) {
3595
-            $query_object->set_main_model_join_sql(
3596
-                $this->_construct_limit_join_select(
3597
-                    $query_params['on_join_limit'][0],
3598
-                    $query_params['on_join_limit'][1]
3599
-                )
3600
-            );
3601
-        }
3602
-        // set limit
3603
-        if (array_key_exists('limit', $query_params)) {
3604
-            if (is_array($query_params['limit'])) {
3605
-                if (! isset($query_params['limit'][0], $query_params['limit'][1])) {
3606
-                    $e = sprintf(
3607
-                        esc_html__(
3608
-                            "Invalid DB query. You passed '%s' for the LIMIT, but only the following are valid: an integer, string representing an integer, a string like 'int,int', or an array like array(int,int)",
3609
-                            "event_espresso"
3610
-                        ),
3611
-                        http_build_query($query_params['limit'])
3612
-                    );
3613
-                    throw new EE_Error($e . "|" . $e);
3614
-                }
3615
-                // they passed us an array for the limit. Assume it's like array(50,25), meaning offset by 50, and get 25
3616
-                $query_object->set_limit_sql(" LIMIT " . $query_params['limit'][0] . "," . $query_params['limit'][1]);
3617
-            } elseif (! empty($query_params['limit'])) {
3618
-                $query_object->set_limit_sql(" LIMIT " . $query_params['limit']);
3619
-            }
3620
-        }
3621
-        // set order by
3622
-        if (array_key_exists('order_by', $query_params)) {
3623
-            if (is_array($query_params['order_by'])) {
3624
-                // if they're using 'order_by' as an array, they can't use 'order' (because 'order_by' must
3625
-                // specify whether to ascend or descend on each field. Eg 'order_by'=>array('EVT_ID'=>'ASC'). So
3626
-                // including 'order' wouldn't make any sense if 'order_by' has already specified which way to order!
3627
-                if (array_key_exists('order', $query_params)) {
3628
-                    throw new EE_Error(
3629
-                        sprintf(
3630
-                            esc_html__(
3631
-                                "In querying %s, we are using query parameter 'order_by' as an array (keys:%s,values:%s), and so we can't use query parameter 'order' (value %s). You should just use the 'order_by' parameter ",
3632
-                                "event_espresso"
3633
-                            ),
3634
-                            get_class($this),
3635
-                            implode(", ", array_keys($query_params['order_by'])),
3636
-                            implode(", ", $query_params['order_by']),
3637
-                            $query_params['order']
3638
-                        )
3639
-                    );
3640
-                }
3641
-                $this->_extract_related_models_from_sub_params_array_keys(
3642
-                    $query_params['order_by'],
3643
-                    $query_object,
3644
-                    'order_by'
3645
-                );
3646
-                // assume it's an array of fields to order by
3647
-                $order_array = [];
3648
-                foreach ($query_params['order_by'] as $field_name_to_order_by => $order) {
3649
-                    $order         = $this->_extract_order($order);
3650
-                    $order_array[] = $this->_deduce_column_name_from_query_param($field_name_to_order_by) . SP . $order;
3651
-                }
3652
-                $query_object->set_order_by_sql(" ORDER BY " . implode(",", $order_array));
3653
-            } elseif (! empty($query_params['order_by'])) {
3654
-                $this->_extract_related_model_info_from_query_param(
3655
-                    $query_params['order_by'],
3656
-                    $query_object,
3657
-                    'order',
3658
-                    $query_params['order_by']
3659
-                );
3660
-                $order = isset($query_params['order'])
3661
-                    ? $this->_extract_order($query_params['order'])
3662
-                    : 'DESC';
3663
-                $query_object->set_order_by_sql(
3664
-                    " ORDER BY " . $this->_deduce_column_name_from_query_param($query_params['order_by']) . SP . $order
3665
-                );
3666
-            }
3667
-        }
3668
-        // if 'order_by' wasn't set, maybe they are just using 'order' on its own?
3669
-        if (
3670
-            ! array_key_exists('order_by', $query_params)
3671
-            && array_key_exists('order', $query_params)
3672
-            && ! empty($query_params['order'])
3673
-        ) {
3674
-            $pk_field = $this->get_primary_key_field();
3675
-            $order    = $this->_extract_order($query_params['order']);
3676
-            $query_object->set_order_by_sql(" ORDER BY " . $pk_field->get_qualified_column() . SP . $order);
3677
-        }
3678
-        // set group by
3679
-        if (array_key_exists('group_by', $query_params)) {
3680
-            if (is_array($query_params['group_by'])) {
3681
-                // it's an array, so assume we'll be grouping by a bunch of stuff
3682
-                $group_by_array = [];
3683
-                foreach ($query_params['group_by'] as $field_name_to_group_by) {
3684
-                    $group_by_array[] = $this->_deduce_column_name_from_query_param($field_name_to_group_by);
3685
-                }
3686
-                $query_object->set_group_by_sql(" GROUP BY " . implode(", ", $group_by_array));
3687
-            } elseif (! empty($query_params['group_by'])) {
3688
-                $query_object->set_group_by_sql(
3689
-                    " GROUP BY " . $this->_deduce_column_name_from_query_param($query_params['group_by'])
3690
-                );
3691
-            }
3692
-        }
3693
-        // set having
3694
-        if (array_key_exists('having', $query_params) && $query_params['having']) {
3695
-            $query_object->set_having_sql($this->_construct_having_clause($query_params['having']));
3696
-        }
3697
-        // now, just verify they didn't pass anything wack
3698
-        foreach ($query_params as $query_key => $query_value) {
3699
-            if (! in_array($query_key, $this->_allowed_query_params, true)) {
3700
-                throw new EE_Error(
3701
-                    sprintf(
3702
-                        esc_html__(
3703
-                            "You passed %s as a query parameter to %s, which is illegal! The allowed query parameters are %s",
3704
-                            'event_espresso'
3705
-                        ),
3706
-                        $query_key,
3707
-                        get_class($this),
3708
-                        //                      print_r( $this->_allowed_query_params, TRUE )
3709
-                        implode(',', $this->_allowed_query_params)
3710
-                    )
3711
-                );
3712
-            }
3713
-        }
3714
-        $main_model_join_sql = $query_object->get_main_model_join_sql();
3715
-        if (empty($main_model_join_sql)) {
3716
-            $query_object->set_main_model_join_sql($this->_construct_internal_join());
3717
-        }
3718
-        return $query_object;
3719
-    }
3720
-
3721
-
3722
-    /**
3723
-     * Gets the where conditions that should be imposed on the query based on the
3724
-     * context (eg reading frontend, backend, edit or delete).
3725
-     *
3726
-     * @param string $context one of EEM_Base::valid_cap_contexts()
3727
-     * @return array @see
3728
-     *                        https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3729
-     * @throws EE_Error
3730
-     */
3731
-    public function caps_where_conditions($context = self::caps_read)
3732
-    {
3733
-        EEM_Base::verify_is_valid_cap_context($context);
3734
-        $cap_where_conditions = [];
3735
-        $cap_restrictions     = $this->caps_missing($context);
3736
-        foreach ($cap_restrictions as $restriction_if_no_cap) {
3737
-            $cap_where_conditions = array_replace_recursive(
3738
-                $cap_where_conditions,
3739
-                $restriction_if_no_cap->get_default_where_conditions()
3740
-            );
3741
-        }
3742
-        return apply_filters(
3743
-            'FHEE__EEM_Base__caps_where_conditions__return',
3744
-            $cap_where_conditions,
3745
-            $this,
3746
-            $context,
3747
-            $cap_restrictions
3748
-        );
3749
-    }
3750
-
3751
-
3752
-    /**
3753
-     * Verifies that $should_be_order_string is in $this->_allowed_order_values,
3754
-     * otherwise throws an exception
3755
-     *
3756
-     * @param string $should_be_order_string
3757
-     * @return string either ASC, asc, DESC or desc
3758
-     * @throws EE_Error
3759
-     */
3760
-    private function _extract_order($should_be_order_string)
3761
-    {
3762
-        if (in_array($should_be_order_string, $this->_allowed_order_values)) {
3763
-            return $should_be_order_string;
3764
-        }
3765
-        throw new EE_Error(
3766
-            sprintf(
3767
-                esc_html__(
3768
-                    "While performing a query on '%s', tried to use '%s' as an order parameter. ",
3769
-                    "event_espresso"
3770
-                ),
3771
-                get_class($this),
3772
-                $should_be_order_string
3773
-            )
3774
-        );
3775
-    }
3776
-
3777
-
3778
-    /**
3779
-     * Looks at all the models which are included in this query, and asks each
3780
-     * for their universal_where_params, and returns them in the same format as $query_params[0] (where),
3781
-     * so they can be merged
3782
-     *
3783
-     * @param EE_Model_Query_Info_Carrier $query_info_carrier
3784
-     * @param string                      $use_default_where_conditions can be 'none','other_models_only', or 'all'.
3785
-     *                                                                  'none' means NO default where conditions will
3786
-     *                                                                  be used AT ALL during this query.
3787
-     *                                                                  'other_models_only' means default where
3788
-     *                                                                  conditions from other models will be used, but
3789
-     *                                                                  not for this primary model. 'all', the default,
3790
-     *                                                                  means default where conditions will apply as
3791
-     *                                                                  normal
3792
-     * @param array                       $where_query_params
3793
-     * @return array
3794
-     * @throws EE_Error
3795
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params
3796
-     *                                                                  .md#0-where-conditions
3797
-     */
3798
-    private function _get_default_where_conditions_for_models_in_query(
3799
-        EE_Model_Query_Info_Carrier $query_info_carrier,
3800
-        $use_default_where_conditions = EEM_Base::default_where_conditions_all,
3801
-        $where_query_params = []
3802
-    ) {
3803
-        $allowed_used_default_where_conditions_values = EEM_Base::valid_default_where_conditions();
3804
-        if (! in_array($use_default_where_conditions, $allowed_used_default_where_conditions_values)) {
3805
-            throw new EE_Error(
3806
-                sprintf(
3807
-                    esc_html__(
3808
-                        "You passed an invalid value to the query parameter 'default_where_conditions' of '%s'. Allowed values are %s",
3809
-                        "event_espresso"
3810
-                    ),
3811
-                    $use_default_where_conditions,
3812
-                    implode(", ", $allowed_used_default_where_conditions_values)
3813
-                )
3814
-            );
3815
-        }
3816
-        $universal_query_params = [];
3817
-        if ($this->_should_use_default_where_conditions($use_default_where_conditions, true)) {
3818
-            $universal_query_params = $this->_get_default_where_conditions();
3819
-        } elseif ($this->_should_use_minimum_where_conditions($use_default_where_conditions, true)) {
3820
-            $universal_query_params = $this->_get_minimum_where_conditions();
3821
-        }
3822
-        foreach ($query_info_carrier->get_model_names_included() as $model_relation_path => $model_name) {
3823
-            $related_model = $this->get_related_model_obj($model_name);
3824
-            if ($this->_should_use_default_where_conditions($use_default_where_conditions, false)) {
3825
-                $related_model_universal_where_params =
3826
-                    $related_model->_get_default_where_conditions($model_relation_path);
3827
-            } elseif ($this->_should_use_minimum_where_conditions($use_default_where_conditions, false)) {
3828
-                $related_model_universal_where_params =
3829
-                    $related_model->_get_minimum_where_conditions($model_relation_path);
3830
-            } else {
3831
-                // we don't want to add full or even minimum default where conditions from this model, so just continue
3832
-                continue;
3833
-            }
3834
-            $overrides              = $this->_override_defaults_or_make_null_friendly(
3835
-                $related_model_universal_where_params,
3836
-                $where_query_params,
3837
-                $related_model,
3838
-                $model_relation_path
3839
-            );
3840
-            $universal_query_params = EEH_Array::merge_arrays_and_overwrite_keys(
3841
-                $universal_query_params,
3842
-                $overrides
3843
-            );
3844
-        }
3845
-        return $universal_query_params;
3846
-    }
3847
-
3848
-
3849
-    /**
3850
-     * Determines whether or not we should use default where conditions for the model in question
3851
-     * (this model, or other related models).
3852
-     * Basically, we should use default where conditions on this model if they have requested to use them on all models,
3853
-     * this model only, or to use minimum where conditions on all other models and normal where conditions on this one.
3854
-     * We should use default where conditions on related models when they requested to use default where conditions
3855
-     * on all models, or specifically just on other related models
3856
-     *
3857
-     * @param      $default_where_conditions_value
3858
-     * @param bool $for_this_model false means this is for OTHER related models
3859
-     * @return bool
3860
-     */
3861
-    private function _should_use_default_where_conditions($default_where_conditions_value, $for_this_model = true)
3862
-    {
3863
-        return (
3864
-                   $for_this_model
3865
-                   && in_array(
3866
-                       $default_where_conditions_value,
3867
-                       [
3868
-                           EEM_Base::default_where_conditions_all,
3869
-                           EEM_Base::default_where_conditions_this_only,
3870
-                           EEM_Base::default_where_conditions_minimum_others,
3871
-                       ],
3872
-                       true
3873
-                   )
3874
-               )
3875
-               || (
3876
-                   ! $for_this_model
3877
-                   && in_array(
3878
-                       $default_where_conditions_value,
3879
-                       [
3880
-                           EEM_Base::default_where_conditions_all,
3881
-                           EEM_Base::default_where_conditions_others_only,
3882
-                       ],
3883
-                       true
3884
-                   )
3885
-               );
3886
-    }
3887
-
3888
-
3889
-    /**
3890
-     * Determines whether or not we should use default minimum conditions for the model in question
3891
-     * (this model, or other related models).
3892
-     * Basically, we should use minimum where conditions on this model only if they requested all models to use minimum
3893
-     * where conditions.
3894
-     * We should use minimum where conditions on related models if they requested to use minimum where conditions
3895
-     * on this model or others
3896
-     *
3897
-     * @param      $default_where_conditions_value
3898
-     * @param bool $for_this_model false means this is for OTHER related models
3899
-     * @return bool
3900
-     */
3901
-    private function _should_use_minimum_where_conditions($default_where_conditions_value, $for_this_model = true)
3902
-    {
3903
-        return (
3904
-                   $for_this_model
3905
-                   && $default_where_conditions_value === EEM_Base::default_where_conditions_minimum_all
3906
-               )
3907
-               || (
3908
-                   ! $for_this_model
3909
-                   && in_array(
3910
-                       $default_where_conditions_value,
3911
-                       [
3912
-                           EEM_Base::default_where_conditions_minimum_others,
3913
-                           EEM_Base::default_where_conditions_minimum_all,
3914
-                       ],
3915
-                       true
3916
-                   )
3917
-               );
3918
-    }
3919
-
3920
-
3921
-    /**
3922
-     * Checks if any of the defaults have been overridden. If there are any that AREN'T overridden,
3923
-     * then we also add a special where condition which allows for that model's primary key
3924
-     * to be null (which is important for JOINs. Eg, if you want to see all Events ordered by Venue's name,
3925
-     * then Event's with NO Venue won't appear unless you allow VNU_ID to be NULL)
3926
-     *
3927
-     * @param array    $default_where_conditions
3928
-     * @param array    $provided_where_conditions
3929
-     * @param EEM_Base $model
3930
-     * @param string   $model_relation_path like 'Transaction.Payment.'
3931
-     * @return array @see
3932
-     *                                      https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3933
-     * @throws EE_Error
3934
-     */
3935
-    private function _override_defaults_or_make_null_friendly(
3936
-        $default_where_conditions,
3937
-        $provided_where_conditions,
3938
-        $model,
3939
-        $model_relation_path
3940
-    ) {
3941
-        $null_friendly_where_conditions = [];
3942
-        $none_overridden                = true;
3943
-        $or_condition_key_for_defaults  = 'OR*' . get_class($model);
3944
-        foreach ($default_where_conditions as $key => $val) {
3945
-            if (isset($provided_where_conditions[ $key ])) {
3946
-                $none_overridden = false;
3947
-            } else {
3948
-                $null_friendly_where_conditions[ $or_condition_key_for_defaults ]['AND'][ $key ] = $val;
3949
-            }
3950
-        }
3951
-        if ($none_overridden && $default_where_conditions) {
3952
-            if ($model->has_primary_key_field()) {
3953
-                $null_friendly_where_conditions[ $or_condition_key_for_defaults ][ $model_relation_path
3954
-                                                                                   . "."
3955
-                                                                                   . $model->primary_key_name() ] =
3956
-                    ['IS NULL'];
3957
-            }/*else{
2636
+			$this->_show_next_x_db_queries--;
2637
+		}
2638
+	}
2639
+
2640
+
2641
+	/**
2642
+	 * Adds a relationship of the correct type between $modelObject and $otherModelObject.
2643
+	 * There are the 3 cases:
2644
+	 * 'belongsTo' relationship: sets $id_or_obj's foreign_key to be $other_model_id_or_obj's primary_key. If
2645
+	 * $otherModelObject has no ID, it is first saved.
2646
+	 * 'hasMany' relationship: sets $other_model_id_or_obj's foreign_key to be $id_or_obj's primary_key. If $id_or_obj
2647
+	 * has no ID, it is first saved.
2648
+	 * 'hasAndBelongsToMany' relationships: checks that there isn't already an entry in the join table, and adds one.
2649
+	 * If one of the model Objects has not yet been saved to the database, it is saved before adding the entry in the
2650
+	 * join table
2651
+	 *
2652
+	 * @param EE_Base_Class|int $id_or_obj                        EE_base_Class or ID of $thisModelObject
2653
+	 * @param EE_Base_Class|int $other_model_id_or_obj            EE_base_Class or ID of other Model Object
2654
+	 * @param string            $relationName                     , key in EEM_Base::_relations
2655
+	 *                                                            an attendee to a group, you also want to specify
2656
+	 *                                                            which role they will have in that group. So you would
2657
+	 *                                                            use this parameter to specify
2658
+	 *                                                            array('role-column-name'=>'role-id')
2659
+	 * @param array|null        $extra_join_model_fields_n_values This allows you to enter further query params for the
2660
+	 *                                                            relation to for relation to methods that allow you to
2661
+	 *                                                            further specify extra columns to join by (such as
2662
+	 *                                                            HABTM).  Keep in mind that the only acceptable
2663
+	 *                                                            query_params is strict "col" => "value" pairs because
2664
+	 *                                                            these will be inserted in any new rows created as
2665
+	 *                                                            well.
2666
+	 * @return EE_Base_Class which was added as a relation. Object referred to by $other_model_id_or_obj
2667
+	 * @throws EE_Error
2668
+	 */
2669
+	public function add_relationship_to(
2670
+		$id_or_obj,
2671
+		$other_model_id_or_obj,
2672
+		$relationName,
2673
+		$extra_join_model_fields_n_values = []
2674
+	) {
2675
+		$relation_obj = $this->related_settings_for($relationName);
2676
+		return $relation_obj->add_relation_to($id_or_obj, $other_model_id_or_obj, $extra_join_model_fields_n_values);
2677
+	}
2678
+
2679
+
2680
+	/**
2681
+	 * Removes a relationship of the correct type between $modelObject and $otherModelObject.
2682
+	 * There are the 3 cases:
2683
+	 * 'belongsTo' relationship: sets $modelObject's foreign_key to null, if that field is nullable.Otherwise throws an
2684
+	 * error
2685
+	 * 'hasMany' relationship: sets $otherModelObject's foreign_key to null,if that field is nullable.Otherwise throws
2686
+	 * an error
2687
+	 * 'hasAndBelongsToMany' relationships:removes any existing entry in the join table between the two models.
2688
+	 *
2689
+	 * @param EE_Base_Class|int $id_or_obj             EE_base_Class or ID of $thisModelObject
2690
+	 * @param EE_Base_Class|int $other_model_id_or_obj EE_base_Class or ID of other Model Object
2691
+	 * @param string            $relationName          key in EEM_Base::_relations
2692
+	 * @param array|null        $where_query           This allows you to enter further query params for the relation
2693
+	 *                                                 to for relation to methods that allow you to further specify
2694
+	 *                                                 extra columns to join by (such as HABTM). Keep in mind that the
2695
+	 *                                                 only acceptable query_params is strict "col" => "value" pairs
2696
+	 *                                                 because these will be inserted in any new rows created as well.
2697
+	 * @return EE_Base_Class
2698
+	 * @throws EE_Error
2699
+	 */
2700
+	public function remove_relationship_to($id_or_obj, $other_model_id_or_obj, $relationName, $where_query = [])
2701
+	{
2702
+		$relation_obj = $this->related_settings_for($relationName);
2703
+		return $relation_obj->remove_relation_to($id_or_obj, $other_model_id_or_obj, $where_query);
2704
+	}
2705
+
2706
+
2707
+	/**
2708
+	 * @param mixed       $id_or_obj
2709
+	 * @param string      $relationName
2710
+	 * @param array|null  $where_query_params
2711
+	 * @return EE_Base_Class[]
2712
+	 * @throws EE_Error
2713
+	 * @throws ReflectionException
2714
+	 */
2715
+	public function remove_relations($id_or_obj, $relationName, $where_query_params = [])
2716
+	{
2717
+		$relation_obj = $this->related_settings_for($relationName);
2718
+		return $relation_obj->remove_relations($id_or_obj, $where_query_params);
2719
+	}
2720
+
2721
+
2722
+	/**
2723
+	 * Gets all the related items of the specified $model_name, using $query_params.
2724
+	 * Note: by default, we remove the "default query params"
2725
+	 * because we want to get even deleted items etc.
2726
+	 *
2727
+	 * @param mixed       $id_or_obj    EE_Base_Class child or its ID
2728
+	 * @param string      $model_name   like 'Event', 'Registration', etc. always singular
2729
+	 * @param array|null  $query_params @see
2730
+	 *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2731
+	 * @return EE_Base_Class[]
2732
+	 * @throws EE_Error
2733
+	 * @throws ReflectionException
2734
+	 */
2735
+	public function get_all_related($id_or_obj, $model_name, ?array $query_params = [])
2736
+	{
2737
+		$model_obj         = $this->ensure_is_obj($id_or_obj);
2738
+		$relation_settings = $this->related_settings_for($model_name);
2739
+		return $relation_settings->get_all_related($model_obj, $query_params);
2740
+	}
2741
+
2742
+
2743
+	/**
2744
+	 * Deletes all the model objects across the relation indicated by $model_name
2745
+	 * which are related to $id_or_obj which meet the criteria set in $query_params.
2746
+	 * However, if the model objects can't be deleted because of blocking related model objects, then
2747
+	 * they aren't deleted. (Unless the thing that would have been deleted can be soft-deleted, that still happens).
2748
+	 *
2749
+	 * @param EE_Base_Class|int|string $id_or_obj
2750
+	 * @param string                   $model_name
2751
+	 * @param array|null               $query_params
2752
+	 * @return int how many deleted
2753
+	 * @throws EE_Error
2754
+	 * @throws ReflectionException
2755
+	 */
2756
+	public function delete_related($id_or_obj, $model_name, $query_params = [])
2757
+	{
2758
+		$model_obj         = $this->ensure_is_obj($id_or_obj);
2759
+		$relation_settings = $this->related_settings_for($model_name);
2760
+		return $relation_settings->delete_all_related($model_obj, $query_params);
2761
+	}
2762
+
2763
+
2764
+	/**
2765
+	 * Hard deletes all the model objects across the relation indicated by $model_name
2766
+	 * which are related to $id_or_obj which meet the criteria set in $query_params. If
2767
+	 * the model objects can't be hard deleted because of blocking related model objects,
2768
+	 * just does a soft-delete on them instead.
2769
+	 *
2770
+	 * @param EE_Base_Class|int|string $id_or_obj
2771
+	 * @param string                   $model_name
2772
+	 * @param array|null               $query_params
2773
+	 * @return int how many deleted
2774
+	 * @throws EE_Error
2775
+	 * @throws ReflectionException
2776
+	 */
2777
+	public function delete_related_permanently($id_or_obj, $model_name, $query_params = [])
2778
+	{
2779
+		$model_obj         = $this->ensure_is_obj($id_or_obj);
2780
+		$relation_settings = $this->related_settings_for($model_name);
2781
+		return $relation_settings->delete_related_permanently($model_obj, $query_params);
2782
+	}
2783
+
2784
+
2785
+	/**
2786
+	 * Instead of getting the related model objects, simply counts them. Ignores default_where_conditions by default,
2787
+	 * unless otherwise specified in the $query_params
2788
+	 *
2789
+	 * @param EE_Base_Class|int|string $id_or_obj
2790
+	 * @param string                   $model_name     like 'Event', or 'Registration'
2791
+	 * @param array|null               $query_params   @see
2792
+	 *                                                 https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2793
+	 * @param string                   $field_to_count name of field to count by. By default, uses primary key
2794
+	 * @param bool                     $distinct       if we want to only count the distinct values for the column then
2795
+	 *                                                 you can trigger that by the setting $distinct to TRUE;
2796
+	 * @return int
2797
+	 * @throws EE_Error
2798
+	 * @throws ReflectionException
2799
+	 */
2800
+	public function count_related(
2801
+		$id_or_obj,
2802
+		$model_name,
2803
+		$query_params = [],
2804
+		$field_to_count = null,
2805
+		$distinct = false
2806
+	) {
2807
+		$related_model = $this->get_related_model_obj($model_name);
2808
+		// we're just going to use the query params on the related model's normal get_all query,
2809
+		// except add a condition to say to match the current mod
2810
+		if (! isset($query_params['default_where_conditions'])) {
2811
+			$query_params['default_where_conditions'] = EEM_Base::default_where_conditions_none;
2812
+		}
2813
+		$this_model_name                                                 = $this->get_this_model_name();
2814
+		$this_pk_field_name                                              = $this->get_primary_key_field()->get_name();
2815
+		$query_params[0][ $this_model_name . "." . $this_pk_field_name ] = $id_or_obj;
2816
+		return $related_model->count($query_params, $field_to_count, $distinct);
2817
+	}
2818
+
2819
+
2820
+	/**
2821
+	 * Instead of getting the related model objects, simply sums up the values of the specified field.
2822
+	 * Note: ignores default_where_conditions by default, unless otherwise specified in the $query_params
2823
+	 *
2824
+	 * @param EE_Base_Class|int|string $id_or_obj
2825
+	 * @param string                   $model_name   like 'Event', or 'Registration'
2826
+	 * @param array|null               $query_params @see
2827
+	 *                                               https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2828
+	 * @param string                   $field_to_sum name of field to count by. By default, uses primary key
2829
+	 * @return float
2830
+	 * @throws EE_Error
2831
+	 * @throws ReflectionException
2832
+	 */
2833
+	public function sum_related($id_or_obj, $model_name, $query_params, $field_to_sum = null)
2834
+	{
2835
+		$related_model = $this->get_related_model_obj($model_name);
2836
+		if (! is_array($query_params)) {
2837
+			EE_Error::doing_it_wrong(
2838
+				'EEM_Base::sum_related',
2839
+				sprintf(
2840
+					esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
2841
+					gettype($query_params)
2842
+				),
2843
+				'4.6.0'
2844
+			);
2845
+			$query_params = [];
2846
+		}
2847
+		// we're just going to use the query params on the related model's normal get_all query,
2848
+		// except add a condition to say to match the current mod
2849
+		if (! isset($query_params['default_where_conditions'])) {
2850
+			$query_params['default_where_conditions'] = EEM_Base::default_where_conditions_none;
2851
+		}
2852
+		$this_model_name                                                 = $this->get_this_model_name();
2853
+		$this_pk_field_name                                              = $this->get_primary_key_field()->get_name();
2854
+		$query_params[0][ $this_model_name . "." . $this_pk_field_name ] = $id_or_obj;
2855
+		return $related_model->sum($query_params, $field_to_sum);
2856
+	}
2857
+
2858
+
2859
+	/**
2860
+	 * Uses $this->_relatedModels info to find the first related model object of relation $relationName to the given
2861
+	 * $modelObject
2862
+	 *
2863
+	 * @param int | EE_Base_Class $id_or_obj        EE_Base_Class child or its ID
2864
+	 * @param string              $other_model_name , key in $this->_relatedModels, eg 'Registration', or 'Events'
2865
+	 * @param array|null          $query_params     @see
2866
+	 *                                              https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2867
+	 * @return EE_Base_Class
2868
+	 * @throws EE_Error
2869
+	 * @throws ReflectionException
2870
+	 */
2871
+	public function get_first_related(EE_Base_Class $id_or_obj, $other_model_name, $query_params)
2872
+	{
2873
+		$query_params['limit'] = 1;
2874
+		$results               = $this->get_all_related($id_or_obj, $other_model_name, $query_params);
2875
+		if ($results) {
2876
+			return array_shift($results);
2877
+		}
2878
+		return null;
2879
+	}
2880
+
2881
+
2882
+	/**
2883
+	 * Gets the model's name as it's expected in queries. For example, if this is EEM_Event model, that would be Event
2884
+	 *
2885
+	 * @return string
2886
+	 */
2887
+	public function get_this_model_name()
2888
+	{
2889
+		return str_replace("EEM_", "", get_class($this));
2890
+	}
2891
+
2892
+
2893
+	/**
2894
+	 * Gets the model field on this model which is of type EE_Any_Foreign_Model_Name_Field
2895
+	 *
2896
+	 * @return EE_Any_Foreign_Model_Name_Field
2897
+	 * @throws EE_Error
2898
+	 */
2899
+	public function get_field_containing_related_model_name()
2900
+	{
2901
+		foreach ($this->field_settings(true) as $field) {
2902
+			if ($field instanceof EE_Any_Foreign_Model_Name_Field) {
2903
+				$field_with_model_name = $field;
2904
+			}
2905
+		}
2906
+		if (! isset($field_with_model_name) || ! $field_with_model_name) {
2907
+			throw new EE_Error(
2908
+				sprintf(
2909
+					esc_html__("There is no EE_Any_Foreign_Model_Name field on model %s", "event_espresso"),
2910
+					$this->get_this_model_name()
2911
+				)
2912
+			);
2913
+		}
2914
+		return $field_with_model_name;
2915
+	}
2916
+
2917
+
2918
+	/**
2919
+	 * Inserts a new entry into the database, for each table.
2920
+	 * Note: does not add the item to the entity map because that is done by EE_Base_Class::save() right after this.
2921
+	 * If client code uses EEM_Base::insert() directly, then although the item isn't in the entity map,
2922
+	 * we also know there is no model object with the newly inserted item's ID at the moment (because
2923
+	 * if there were, then they would already be in the DB and this would fail); and in the future if someone
2924
+	 * creates a model object with this ID (or grabs it from the DB) then it will be added to the
2925
+	 * entity map at that time anyways. SO, no need for EEM_Base::insert ot add to the entity map
2926
+	 *
2927
+	 * @param array $field_n_values keys are field names, values are their values (in the client code's domain if
2928
+	 *                              $values_already_prepared_by_model_object is false, in the model object's domain if
2929
+	 *                              $values_already_prepared_by_model_object is true. See comment about this at the top
2930
+	 *                              of EEM_Base)
2931
+	 * @return int|string new primary key on main table that got inserted
2932
+	 * @throws EE_Error
2933
+	 * @throws ReflectionException
2934
+	 */
2935
+	public function insert($field_n_values)
2936
+	{
2937
+		/**
2938
+		 * Filters the fields and their values before inserting an item using the models
2939
+		 *
2940
+		 * @param array    $fields_n_values keys are the fields and values are their new values
2941
+		 * @param EEM_Base $model           the model used
2942
+		 */
2943
+		$field_n_values = (array) apply_filters('FHEE__EEM_Base__insert__fields_n_values', $field_n_values, $this);
2944
+		if ($this->_satisfies_unique_indexes($field_n_values)) {
2945
+			$main_table = $this->_get_main_table();
2946
+			$new_id     = $this->_insert_into_specific_table($main_table, $field_n_values, false);
2947
+			if ($new_id !== false) {
2948
+				foreach ($this->_get_other_tables() as $other_table) {
2949
+					$this->_insert_into_specific_table($other_table, $field_n_values, $new_id);
2950
+				}
2951
+			}
2952
+			/**
2953
+			 * Done just after attempting to insert a new model object
2954
+			 *
2955
+			 * @param EEM_Base $model           used
2956
+			 * @param array    $fields_n_values fields and their values
2957
+			 * @param int|string the              ID of the newly-inserted model object
2958
+			 */
2959
+			do_action('AHEE__EEM_Base__insert__end', $this, $field_n_values, $new_id);
2960
+			return $new_id;
2961
+		}
2962
+		return false;
2963
+	}
2964
+
2965
+
2966
+	/**
2967
+	 * Checks that the result would satisfy the unique indexes on this model
2968
+	 *
2969
+	 * @param array  $field_n_values
2970
+	 * @param string $action
2971
+	 * @return boolean
2972
+	 * @throws EE_Error
2973
+	 * @throws ReflectionException
2974
+	 */
2975
+	protected function _satisfies_unique_indexes(array $field_n_values, $action = 'insert')
2976
+	{
2977
+		foreach ($this->unique_indexes() as $index_name => $index) {
2978
+			$uniqueness_where_params = array_intersect_key($field_n_values, $index->fields());
2979
+			if ($this->exists([$uniqueness_where_params])) {
2980
+				EE_Error::add_error(
2981
+					sprintf(
2982
+						esc_html__(
2983
+							"Could not %s %s. %s uniqueness index failed. Fields %s must form a unique set, but an entry already exists with values %s.",
2984
+							"event_espresso"
2985
+						),
2986
+						$action,
2987
+						$this->_get_class_name(),
2988
+						$index_name,
2989
+						implode(",", $index->field_names()),
2990
+						http_build_query($uniqueness_where_params)
2991
+					),
2992
+					__FILE__,
2993
+					__FUNCTION__,
2994
+					__LINE__
2995
+				);
2996
+				return false;
2997
+			}
2998
+		}
2999
+		return true;
3000
+	}
3001
+
3002
+
3003
+	/**
3004
+	 * Checks the database for an item that conflicts (ie, if this item were
3005
+	 * saved to the DB would break some uniqueness requirement, like a primary key
3006
+	 * or an index primary key set) with the item specified. $id_obj_or_fields_array
3007
+	 * can be either an EE_Base_Class or an array of fields n values
3008
+	 *
3009
+	 * @param EE_Base_Class|array $obj_or_fields_array
3010
+	 * @param boolean             $include_primary_key whether to use the model object's primary key
3011
+	 *                                                 when looking for conflicts
3012
+	 *                                                 (ie, if false, we ignore the model object's primary key
3013
+	 *                                                 when finding "conflicts". If true, it's also considered).
3014
+	 *                                                 Only works for INT primary key,
3015
+	 *                                                 STRING primary keys cannot be ignored
3016
+	 * @return EE_Base_Class|array
3017
+	 * @throws EE_Error
3018
+	 * @throws ReflectionException
3019
+	 */
3020
+	public function get_one_conflicting($obj_or_fields_array, $include_primary_key = true)
3021
+	{
3022
+		if ($obj_or_fields_array instanceof EE_Base_Class) {
3023
+			$fields_n_values = $obj_or_fields_array->model_field_array();
3024
+		} elseif (is_array($obj_or_fields_array)) {
3025
+			$fields_n_values = $obj_or_fields_array;
3026
+		} else {
3027
+			throw new EE_Error(
3028
+				sprintf(
3029
+					esc_html__(
3030
+						"%s get_all_conflicting should be called with a model object or an array of field names and values, you provided %d",
3031
+						"event_espresso"
3032
+					),
3033
+					get_class($this),
3034
+					$obj_or_fields_array
3035
+				)
3036
+			);
3037
+		}
3038
+		$query_params = [];
3039
+		if (
3040
+			$this->has_primary_key_field()
3041
+			&& ($include_primary_key
3042
+				|| $this->get_primary_key_field()
3043
+				   instanceof
3044
+				   EE_Primary_Key_String_Field)
3045
+			&& isset($fields_n_values[ $this->primary_key_name() ])
3046
+		) {
3047
+			$query_params[0]['OR'][ $this->primary_key_name() ] = $fields_n_values[ $this->primary_key_name() ];
3048
+		}
3049
+		foreach ($this->unique_indexes() as $unique_index_name => $unique_index) {
3050
+			$uniqueness_where_params                              =
3051
+				array_intersect_key($fields_n_values, $unique_index->fields());
3052
+			$query_params[0]['OR'][ 'AND*' . $unique_index_name ] = $uniqueness_where_params;
3053
+		}
3054
+		// if there is nothing to base this search on, then we shouldn't find anything
3055
+		if (empty($query_params)) {
3056
+			return [];
3057
+		}
3058
+		return $this->get_one($query_params);
3059
+	}
3060
+
3061
+
3062
+	/**
3063
+	 * Like count, but is optimized and returns a boolean instead of an int
3064
+	 *
3065
+	 * @param array $query_params
3066
+	 * @return boolean
3067
+	 * @throws EE_Error
3068
+	 * @throws ReflectionException
3069
+	 */
3070
+	public function exists($query_params)
3071
+	{
3072
+		$query_params['limit'] = 1;
3073
+		return $this->count($query_params) > 0;
3074
+	}
3075
+
3076
+
3077
+	/**
3078
+	 * Wrapper for exists, except ignores default query parameters so we're only considering ID
3079
+	 *
3080
+	 * @param int|string $id
3081
+	 * @return boolean
3082
+	 * @throws EE_Error
3083
+	 * @throws ReflectionException
3084
+	 */
3085
+	public function exists_by_ID($id)
3086
+	{
3087
+		return $this->exists(
3088
+			[
3089
+				'default_where_conditions' => EEM_Base::default_where_conditions_none,
3090
+				[
3091
+					$this->primary_key_name() => $id,
3092
+				],
3093
+			]
3094
+		);
3095
+	}
3096
+
3097
+
3098
+	/**
3099
+	 * Inserts a new row in $table, using the $cols_n_values which apply to that table.
3100
+	 * If a $new_id is supplied and if $table is an EE_Other_Table, we assume
3101
+	 * we need to add a foreign key column to point to $new_id (which should be the primary key's value
3102
+	 * on the main table)
3103
+	 * This is protected rather than private because private is not accessible to any child methods and there MAY be
3104
+	 * cases where we want to call it directly rather than via insert().
3105
+	 *
3106
+	 * @access   protected
3107
+	 * @param EE_Table_Base $table
3108
+	 * @param array         $fields_n_values each key should be in field's keys, and value should be an int, string or
3109
+	 *                                       float
3110
+	 * @param int           $new_id          for now we assume only int keys
3111
+	 * @return int ID of new row inserted, or FALSE on failure
3112
+	 * @throws EE_Error
3113
+	 * @global WPDB         $wpdb            only used to get the $wpdb->insert_id after performing an insert
3114
+	 */
3115
+	protected function _insert_into_specific_table(EE_Table_Base $table, $fields_n_values, $new_id = 0)
3116
+	{
3117
+		global $wpdb;
3118
+		$insertion_col_n_values = [];
3119
+		$format_for_insertion   = [];
3120
+		$fields_on_table        = $this->_get_fields_for_table($table->get_table_alias());
3121
+		foreach ($fields_on_table as $field_obj) {
3122
+			// check if its an auto-incrementing column, in which case we should just leave it to do its autoincrement thing
3123
+			if ($field_obj->is_auto_increment()) {
3124
+				continue;
3125
+			}
3126
+			$prepared_value = $this->_prepare_value_or_use_default($field_obj, $fields_n_values);
3127
+			// if the value we want to assign it to is NULL, just don't mention it for the insertion
3128
+			if ($prepared_value !== null) {
3129
+				$insertion_col_n_values[ $field_obj->get_table_column() ] = $prepared_value;
3130
+				$format_for_insertion[]                                   = $field_obj->get_wpdb_data_type();
3131
+			}
3132
+		}
3133
+		if ($table instanceof EE_Secondary_Table && $new_id) {
3134
+			// its not the main table, so we should have already saved the main table's PK which we just inserted
3135
+			// so add the fk to the main table as a column
3136
+			$insertion_col_n_values[ $table->get_fk_on_table() ] = $new_id;
3137
+			$format_for_insertion[]                              =
3138
+				'%d';// yes right now we're only allowing these foreign keys to be INTs
3139
+		}
3140
+
3141
+		// insert the new entry
3142
+		$result = $this->_do_wpdb_query(
3143
+			'insert',
3144
+			[$table->get_table_name(), $insertion_col_n_values, $format_for_insertion]
3145
+		);
3146
+		if ($result === false) {
3147
+			return false;
3148
+		}
3149
+		// ok, now what do we return for the ID of the newly-inserted thing?
3150
+		if ($this->has_primary_key_field()) {
3151
+			if ($this->get_primary_key_field()->is_auto_increment()) {
3152
+				return $wpdb->insert_id;
3153
+			}
3154
+			// it's not an auto-increment primary key, so
3155
+			// it must have been supplied
3156
+			return $fields_n_values[ $this->get_primary_key_field()->get_name() ];
3157
+		}
3158
+		// we can't return a  primary key because there is none. instead return
3159
+		// a unique string indicating this model
3160
+		return $this->get_index_primary_key_string($fields_n_values);
3161
+	}
3162
+
3163
+
3164
+	/**
3165
+	 * Prepare the $field_obj 's value in $fields_n_values for use in the database.
3166
+	 * If the field doesn't allow NULL, try to use its default. (If it doesn't allow NULL,
3167
+	 * and there is no default, we pass it along. WPDB will take care of it)
3168
+	 *
3169
+	 * @param EE_Model_Field_Base $field_obj
3170
+	 * @param array               $fields_n_values
3171
+	 * @return mixed string|int|float depending on what the table column will be expecting
3172
+	 * @throws EE_Error
3173
+	 */
3174
+	protected function _prepare_value_or_use_default($field_obj, $fields_n_values)
3175
+	{
3176
+		$field_name = $field_obj->get_name();
3177
+		// if this field doesn't allow nullable, don't allow it
3178
+		if (! $field_obj->is_nullable() && ! isset($fields_n_values[ $field_name ])) {
3179
+			$fields_n_values[ $field_name ] = $field_obj->get_default_value();
3180
+		}
3181
+		$unprepared_value = $fields_n_values[ $field_name ] ?? null;
3182
+		return $this->_prepare_value_for_use_in_db($unprepared_value, $field_obj);
3183
+	}
3184
+
3185
+
3186
+	/**
3187
+	 * Consolidates code for preparing  a value supplied to the model for use int eh db. Calls the field's
3188
+	 * prepare_for_use_in_db method on the value, and depending on $value_already_prepare_by_model_obj, may also call
3189
+	 * the field's prepare_for_set() method.
3190
+	 *
3191
+	 * @param mixed               $value value in the client code domain if $value_already_prepared_by_model_object is
3192
+	 *                                   false, otherwise a value in the model object's domain (see lengthy comment at
3193
+	 *                                   top of file)
3194
+	 * @param EE_Model_Field_Base $field field which will be doing the preparing of the value. If null, we assume
3195
+	 *                                   $value is a custom selection
3196
+	 * @return mixed a value ready for use in the database for insertions, updating, or in a where clause
3197
+	 */
3198
+	private function _prepare_value_for_use_in_db($value, $field)
3199
+	{
3200
+		if ($field instanceof EE_Model_Field_Base) {
3201
+			// phpcs:disable PSR2.ControlStructures.SwitchDeclaration.TerminatingComment
3202
+			switch ($this->_values_already_prepared_by_model_object) {
3203
+				/** @noinspection PhpMissingBreakStatementInspection */
3204
+				case self::not_prepared_by_model_object:
3205
+					$value = $field->prepare_for_set($value);
3206
+				// purposefully left out "return"
3207
+				// no break
3208
+				case self::prepared_by_model_object:
3209
+					/** @noinspection SuspiciousAssignmentsInspection */
3210
+					$value = $field->prepare_for_use_in_db($value);
3211
+				// no break
3212
+				case self::prepared_for_use_in_db:
3213
+					// leave the value alone
3214
+			}
3215
+			// phpcs:enable
3216
+		}
3217
+		return $value;
3218
+	}
3219
+
3220
+
3221
+	/**
3222
+	 * Returns the main table on this model
3223
+	 *
3224
+	 * @return EE_Primary_Table
3225
+	 * @throws EE_Error
3226
+	 */
3227
+	protected function _get_main_table()
3228
+	{
3229
+		foreach ($this->_tables as $table) {
3230
+			if ($table instanceof EE_Primary_Table) {
3231
+				return $table;
3232
+			}
3233
+		}
3234
+		throw new EE_Error(
3235
+			sprintf(
3236
+				esc_html__(
3237
+					'There are no main tables on %s. They should be added to _tables array in the constructor',
3238
+					'event_espresso'
3239
+				),
3240
+				get_class($this)
3241
+			)
3242
+		);
3243
+	}
3244
+
3245
+
3246
+	/**
3247
+	 * table
3248
+	 * returns EE_Primary_Table table name
3249
+	 *
3250
+	 * @return string
3251
+	 * @throws EE_Error
3252
+	 */
3253
+	public function table()
3254
+	{
3255
+		return $this->_get_main_table()->get_table_name();
3256
+	}
3257
+
3258
+
3259
+	/**
3260
+	 * table
3261
+	 * returns first EE_Secondary_Table table name
3262
+	 *
3263
+	 * @return string
3264
+	 */
3265
+	public function second_table()
3266
+	{
3267
+		// grab second table from tables array
3268
+		$second_table = end($this->_tables);
3269
+		return $second_table instanceof EE_Secondary_Table
3270
+			? $second_table->get_table_name()
3271
+			: null;
3272
+	}
3273
+
3274
+
3275
+	/**
3276
+	 * get_table_obj_by_alias
3277
+	 * returns table name given it's alias
3278
+	 *
3279
+	 * @param string $table_alias
3280
+	 * @return EE_Primary_Table | EE_Secondary_Table
3281
+	 */
3282
+	public function get_table_obj_by_alias($table_alias = '')
3283
+	{
3284
+		return $this->_tables[ $table_alias ] ?? null;
3285
+	}
3286
+
3287
+
3288
+	/**
3289
+	 * Gets all the tables of type EE_Other_Table from EEM_CPT_Basel_Model::_tables
3290
+	 *
3291
+	 * @return EE_Secondary_Table[]
3292
+	 */
3293
+	protected function _get_other_tables()
3294
+	{
3295
+		$other_tables = [];
3296
+		foreach ($this->_tables as $table_alias => $table) {
3297
+			if ($table instanceof EE_Secondary_Table) {
3298
+				$other_tables[ $table_alias ] = $table;
3299
+			}
3300
+		}
3301
+		return $other_tables;
3302
+	}
3303
+
3304
+
3305
+	/**
3306
+	 * Finds all the fields that correspond to the given table
3307
+	 *
3308
+	 * @param string $table_alias , array key in EEM_Base::_tables
3309
+	 * @return EE_Model_Field_Base[]
3310
+	 */
3311
+	public function _get_fields_for_table($table_alias)
3312
+	{
3313
+		return $this->_fields[ $table_alias ];
3314
+	}
3315
+
3316
+
3317
+	/**
3318
+	 * Recurses through all the where parameters, and finds all the related models we'll need
3319
+	 * to complete this query. Eg, given where parameters like array('EVT_ID'=>3) from within Event model, we won't
3320
+	 * need any related models. But if the array were array('Registrations.REG_ID'=>3), we'd need the related
3321
+	 * Registration model. If it were array('Registrations.Transactions.Payments.PAY_ID'=>3), then we'd need the
3322
+	 * related Registration, Transaction, and Payment models.
3323
+	 *
3324
+	 * @param array $query_params @see
3325
+	 *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
3326
+	 * @return EE_Model_Query_Info_Carrier
3327
+	 * @throws EE_Error
3328
+	 */
3329
+	public function _extract_related_models_from_query($query_params)
3330
+	{
3331
+		$query_info_carrier = new EE_Model_Query_Info_Carrier();
3332
+		if (array_key_exists(0, $query_params)) {
3333
+			$this->_extract_related_models_from_sub_params_array_keys($query_params[0], $query_info_carrier, 0);
3334
+		}
3335
+		if (array_key_exists('group_by', $query_params)) {
3336
+			if (is_array($query_params['group_by'])) {
3337
+				$this->_extract_related_models_from_sub_params_array_values(
3338
+					$query_params['group_by'],
3339
+					$query_info_carrier,
3340
+					'group_by'
3341
+				);
3342
+			} elseif (! empty($query_params['group_by'])) {
3343
+				$this->_extract_related_model_info_from_query_param(
3344
+					$query_params['group_by'],
3345
+					$query_info_carrier,
3346
+					'group_by'
3347
+				);
3348
+			}
3349
+		}
3350
+		if (array_key_exists('having', $query_params)) {
3351
+			$this->_extract_related_models_from_sub_params_array_keys(
3352
+				$query_params[0],
3353
+				$query_info_carrier,
3354
+				'having'
3355
+			);
3356
+		}
3357
+		if (array_key_exists('order_by', $query_params)) {
3358
+			if (is_array($query_params['order_by'])) {
3359
+				$this->_extract_related_models_from_sub_params_array_keys(
3360
+					$query_params['order_by'],
3361
+					$query_info_carrier,
3362
+					'order_by'
3363
+				);
3364
+			} elseif (! empty($query_params['order_by'])) {
3365
+				$this->_extract_related_model_info_from_query_param(
3366
+					$query_params['order_by'],
3367
+					$query_info_carrier,
3368
+					'order_by'
3369
+				);
3370
+			}
3371
+		}
3372
+		if (array_key_exists('force_join', $query_params)) {
3373
+			$this->_extract_related_models_from_sub_params_array_values(
3374
+				$query_params['force_join'],
3375
+				$query_info_carrier,
3376
+				'force_join'
3377
+			);
3378
+		}
3379
+		$this->extractRelatedModelsFromCustomSelects($query_info_carrier);
3380
+		return $query_info_carrier;
3381
+	}
3382
+
3383
+
3384
+	/**
3385
+	 * For extracting related models from WHERE (0), HAVING (having), ORDER BY (order_by) or forced joins (force_join)
3386
+	 *
3387
+	 * @param array                       $sub_query_params
3388
+	 * @param EE_Model_Query_Info_Carrier $model_query_info_carrier
3389
+	 * @param string                      $query_param_type one of $this->_allowed_query_params
3390
+	 * @return EE_Model_Query_Info_Carrier
3391
+	 * @throws EE_Error
3392
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#-0-where-conditions
3393
+	 */
3394
+	private function _extract_related_models_from_sub_params_array_keys(
3395
+		$sub_query_params,
3396
+		EE_Model_Query_Info_Carrier $model_query_info_carrier,
3397
+		$query_param_type
3398
+	) {
3399
+		if (! empty($sub_query_params)) {
3400
+			$sub_query_params = (array) $sub_query_params;
3401
+			foreach ($sub_query_params as $param => $possibly_array_of_params) {
3402
+				// $param could be simply 'EVT_ID', or it could be 'Registrations.REG_ID', or even 'Registrations.Transactions.Payments.PAY_amount'
3403
+				$this->_extract_related_model_info_from_query_param(
3404
+					$param,
3405
+					$model_query_info_carrier,
3406
+					$query_param_type
3407
+				);
3408
+				// if $possibly_array_of_params is an array, try recursing into it, searching for keys which
3409
+				// indicate needed joins. Eg, array('NOT'=>array('Registration.TXN_ID'=>23)). In this case, we tried
3410
+				// extracting models out of the 'NOT', which obviously wasn't successful, and then we recurse into the value
3411
+				// of array('Registration.TXN_ID'=>23)
3412
+				$query_param_sans_stars =
3413
+					$this->_remove_stars_and_anything_after_from_condition_query_param_key($param);
3414
+				if (in_array($query_param_sans_stars, $this->_logic_query_param_keys, true)) {
3415
+					if (! is_array($possibly_array_of_params)) {
3416
+						throw new EE_Error(
3417
+							sprintf(
3418
+								esc_html__(
3419
+									"You used a special where query param %s, but the value isn't an array of where query params, it's just %s'. It should be an array, eg array('EVT_ID'=>23,'OR'=>array('Venue.VNU_ID'=>32,'Venue.VNU_name'=>'monkey_land'))",
3420
+									"event_espresso"
3421
+								),
3422
+								$param,
3423
+								$possibly_array_of_params
3424
+							)
3425
+						);
3426
+					}
3427
+					$this->_extract_related_models_from_sub_params_array_keys(
3428
+						$possibly_array_of_params,
3429
+						$model_query_info_carrier,
3430
+						$query_param_type
3431
+					);
3432
+				} elseif (
3433
+					$query_param_type === 0 // ie WHERE
3434
+					&& is_array($possibly_array_of_params) // need is_array() check so we don't try to explode a string
3435
+					&& isset($possibly_array_of_params[2])
3436
+					&& $possibly_array_of_params[2]
3437
+				) {
3438
+					// then $possible_array_of_params looks something like array('<','DTT_sold',true)
3439
+					// indicating that $possible_array_of_params[1] is actually a field name,
3440
+					// from which we should extract query parameters!
3441
+					if (! isset($possibly_array_of_params[0], $possibly_array_of_params[1])) {
3442
+						throw new EE_Error(
3443
+							sprintf(
3444
+								esc_html__(
3445
+									"Improperly formed query parameter %s. It should be numerically indexed like array('<','DTT_sold',true); but you provided %s",
3446
+									"event_espresso"
3447
+								),
3448
+								$query_param_type,
3449
+								implode(",", $possibly_array_of_params)
3450
+							)
3451
+						);
3452
+					}
3453
+					$this->_extract_related_model_info_from_query_param(
3454
+						$possibly_array_of_params[1],
3455
+						$model_query_info_carrier,
3456
+						$query_param_type
3457
+					);
3458
+				}
3459
+			}
3460
+		}
3461
+		return $model_query_info_carrier;
3462
+	}
3463
+
3464
+
3465
+	/**
3466
+	 * For extracting related models from forced_joins, where the array values contain the info about what
3467
+	 * models to join with. Eg an array like array('Attendee','Price.Price_Type');
3468
+	 *
3469
+	 * @param array                       $sub_query_params @see
3470
+	 *                                                      https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3471
+	 * @param EE_Model_Query_Info_Carrier $model_query_info_carrier
3472
+	 * @param string                      $query_param_type one of $this->_allowed_query_params
3473
+	 * @return EE_Model_Query_Info_Carrier
3474
+	 * @throws EE_Error
3475
+	 */
3476
+	private function _extract_related_models_from_sub_params_array_values(
3477
+		$sub_query_params,
3478
+		EE_Model_Query_Info_Carrier $model_query_info_carrier,
3479
+		$query_param_type
3480
+	) {
3481
+		if (! empty($sub_query_params)) {
3482
+			if (! is_array($sub_query_params)) {
3483
+				throw new EE_Error(
3484
+					sprintf(
3485
+						esc_html__("Query parameter %s should be an array, but it isn't.", "event_espresso"),
3486
+						$sub_query_params
3487
+					)
3488
+				);
3489
+			}
3490
+			foreach ($sub_query_params as $param) {
3491
+				// $param could be simply 'EVT_ID', or it could be 'Registrations.REG_ID', or even 'Registrations.Transactions.Payments.PAY_amount'
3492
+				$this->_extract_related_model_info_from_query_param(
3493
+					$param,
3494
+					$model_query_info_carrier,
3495
+					$query_param_type
3496
+				);
3497
+			}
3498
+		}
3499
+		return $model_query_info_carrier;
3500
+	}
3501
+
3502
+
3503
+	/**
3504
+	 * Extract all the query parts from  model query params
3505
+	 * and put into a EEM_Related_Model_Info_Carrier for easy extraction into a query. We create this object
3506
+	 * instead of directly constructing the SQL because often we need to extract info from the $query_params
3507
+	 * but use them in a different order. Eg, we need to know what models we are querying
3508
+	 * before we know what joins to perform. However, we need to know what data types correspond to which fields on
3509
+	 * other models before we can finalize the where clause SQL.
3510
+	 *
3511
+	 * @param array $query_params
3512
+	 * @return EE_Model_Query_Info_Carrier
3513
+	 * @throws EE_Error
3514
+	 * @throws ModelConfigurationException
3515
+	 * @throws ReflectionException
3516
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
3517
+	 */
3518
+	public function _create_model_query_info_carrier($query_params)
3519
+	{
3520
+		if (! is_array($query_params)) {
3521
+			EE_Error::doing_it_wrong(
3522
+				'EEM_Base::_create_model_query_info_carrier',
3523
+				sprintf(
3524
+					esc_html__(
3525
+						'$query_params should be an array, you passed a variable of type %s',
3526
+						'event_espresso'
3527
+					),
3528
+					gettype($query_params)
3529
+				),
3530
+				'4.6.0'
3531
+			);
3532
+			$query_params = [];
3533
+		}
3534
+		$query_params[0] = $query_params[0] ?? [];
3535
+		// first check if we should alter the query to account for caps or not
3536
+		// because the caps might require us to do extra joins
3537
+		if (isset($query_params['caps']) && $query_params['caps'] !== 'none') {
3538
+			$query_params[0] = array_replace_recursive(
3539
+				$query_params[0],
3540
+				$this->caps_where_conditions($query_params['caps'])
3541
+			);
3542
+		}
3543
+
3544
+		// check if we should alter the query to remove data related to protected
3545
+		// custom post types
3546
+		if (isset($query_params['exclude_protected']) && $query_params['exclude_protected'] === true) {
3547
+			$where_param_key_for_password = $this->modelChainAndPassword();
3548
+			// only include if related to a cpt where no password has been set
3549
+			$query_params[0]['OR*nopassword'] = [
3550
+				$where_param_key_for_password       => '',
3551
+				$where_param_key_for_password . '*' => ['IS_NULL'],
3552
+			];
3553
+		}
3554
+		$query_object = $this->_extract_related_models_from_query($query_params);
3555
+		// verify where_query_params has NO numeric indexes.... that's simply not how you use it!
3556
+		foreach ($query_params[0] as $key => $value) {
3557
+			if (is_int($key)) {
3558
+				throw new EE_Error(
3559
+					sprintf(
3560
+						esc_html__(
3561
+							"WHERE query params must NOT be numerically-indexed. You provided the array key '%s' for value '%s' while querying model %s. All the query params provided were '%s' Please read documentation on EEM_Base::get_all.",
3562
+							"event_espresso"
3563
+						),
3564
+						$key,
3565
+						var_export($value, true),
3566
+						var_export($query_params, true),
3567
+						get_class($this)
3568
+					)
3569
+				);
3570
+			}
3571
+		}
3572
+		if (
3573
+			array_key_exists('default_where_conditions', $query_params)
3574
+			&& ! empty($query_params['default_where_conditions'])
3575
+		) {
3576
+			$use_default_where_conditions = $query_params['default_where_conditions'];
3577
+		} else {
3578
+			$use_default_where_conditions = EEM_Base::default_where_conditions_all;
3579
+		}
3580
+		$query_params[0] = array_merge(
3581
+			$this->_get_default_where_conditions_for_models_in_query(
3582
+				$query_object,
3583
+				$use_default_where_conditions,
3584
+				$query_params[0]
3585
+			),
3586
+			$query_params[0]
3587
+		);
3588
+		$query_object->set_where_sql($this->_construct_where_clause($query_params[0]));
3589
+		// if this is a "on_join_limit" then we are limiting on on a specific table in a multi_table join.
3590
+		// So we need to setup a subquery and use that for the main join.
3591
+		// Note for now this only works on the primary table for the model.
3592
+		// So for instance, you could set the limit array like this:
3593
+		// array( 'on_join_limit' => array('Primary_Table_Alias', array(1,10) ) )
3594
+		if (array_key_exists('on_join_limit', $query_params) && ! empty($query_params['on_join_limit'])) {
3595
+			$query_object->set_main_model_join_sql(
3596
+				$this->_construct_limit_join_select(
3597
+					$query_params['on_join_limit'][0],
3598
+					$query_params['on_join_limit'][1]
3599
+				)
3600
+			);
3601
+		}
3602
+		// set limit
3603
+		if (array_key_exists('limit', $query_params)) {
3604
+			if (is_array($query_params['limit'])) {
3605
+				if (! isset($query_params['limit'][0], $query_params['limit'][1])) {
3606
+					$e = sprintf(
3607
+						esc_html__(
3608
+							"Invalid DB query. You passed '%s' for the LIMIT, but only the following are valid: an integer, string representing an integer, a string like 'int,int', or an array like array(int,int)",
3609
+							"event_espresso"
3610
+						),
3611
+						http_build_query($query_params['limit'])
3612
+					);
3613
+					throw new EE_Error($e . "|" . $e);
3614
+				}
3615
+				// they passed us an array for the limit. Assume it's like array(50,25), meaning offset by 50, and get 25
3616
+				$query_object->set_limit_sql(" LIMIT " . $query_params['limit'][0] . "," . $query_params['limit'][1]);
3617
+			} elseif (! empty($query_params['limit'])) {
3618
+				$query_object->set_limit_sql(" LIMIT " . $query_params['limit']);
3619
+			}
3620
+		}
3621
+		// set order by
3622
+		if (array_key_exists('order_by', $query_params)) {
3623
+			if (is_array($query_params['order_by'])) {
3624
+				// if they're using 'order_by' as an array, they can't use 'order' (because 'order_by' must
3625
+				// specify whether to ascend or descend on each field. Eg 'order_by'=>array('EVT_ID'=>'ASC'). So
3626
+				// including 'order' wouldn't make any sense if 'order_by' has already specified which way to order!
3627
+				if (array_key_exists('order', $query_params)) {
3628
+					throw new EE_Error(
3629
+						sprintf(
3630
+							esc_html__(
3631
+								"In querying %s, we are using query parameter 'order_by' as an array (keys:%s,values:%s), and so we can't use query parameter 'order' (value %s). You should just use the 'order_by' parameter ",
3632
+								"event_espresso"
3633
+							),
3634
+							get_class($this),
3635
+							implode(", ", array_keys($query_params['order_by'])),
3636
+							implode(", ", $query_params['order_by']),
3637
+							$query_params['order']
3638
+						)
3639
+					);
3640
+				}
3641
+				$this->_extract_related_models_from_sub_params_array_keys(
3642
+					$query_params['order_by'],
3643
+					$query_object,
3644
+					'order_by'
3645
+				);
3646
+				// assume it's an array of fields to order by
3647
+				$order_array = [];
3648
+				foreach ($query_params['order_by'] as $field_name_to_order_by => $order) {
3649
+					$order         = $this->_extract_order($order);
3650
+					$order_array[] = $this->_deduce_column_name_from_query_param($field_name_to_order_by) . SP . $order;
3651
+				}
3652
+				$query_object->set_order_by_sql(" ORDER BY " . implode(",", $order_array));
3653
+			} elseif (! empty($query_params['order_by'])) {
3654
+				$this->_extract_related_model_info_from_query_param(
3655
+					$query_params['order_by'],
3656
+					$query_object,
3657
+					'order',
3658
+					$query_params['order_by']
3659
+				);
3660
+				$order = isset($query_params['order'])
3661
+					? $this->_extract_order($query_params['order'])
3662
+					: 'DESC';
3663
+				$query_object->set_order_by_sql(
3664
+					" ORDER BY " . $this->_deduce_column_name_from_query_param($query_params['order_by']) . SP . $order
3665
+				);
3666
+			}
3667
+		}
3668
+		// if 'order_by' wasn't set, maybe they are just using 'order' on its own?
3669
+		if (
3670
+			! array_key_exists('order_by', $query_params)
3671
+			&& array_key_exists('order', $query_params)
3672
+			&& ! empty($query_params['order'])
3673
+		) {
3674
+			$pk_field = $this->get_primary_key_field();
3675
+			$order    = $this->_extract_order($query_params['order']);
3676
+			$query_object->set_order_by_sql(" ORDER BY " . $pk_field->get_qualified_column() . SP . $order);
3677
+		}
3678
+		// set group by
3679
+		if (array_key_exists('group_by', $query_params)) {
3680
+			if (is_array($query_params['group_by'])) {
3681
+				// it's an array, so assume we'll be grouping by a bunch of stuff
3682
+				$group_by_array = [];
3683
+				foreach ($query_params['group_by'] as $field_name_to_group_by) {
3684
+					$group_by_array[] = $this->_deduce_column_name_from_query_param($field_name_to_group_by);
3685
+				}
3686
+				$query_object->set_group_by_sql(" GROUP BY " . implode(", ", $group_by_array));
3687
+			} elseif (! empty($query_params['group_by'])) {
3688
+				$query_object->set_group_by_sql(
3689
+					" GROUP BY " . $this->_deduce_column_name_from_query_param($query_params['group_by'])
3690
+				);
3691
+			}
3692
+		}
3693
+		// set having
3694
+		if (array_key_exists('having', $query_params) && $query_params['having']) {
3695
+			$query_object->set_having_sql($this->_construct_having_clause($query_params['having']));
3696
+		}
3697
+		// now, just verify they didn't pass anything wack
3698
+		foreach ($query_params as $query_key => $query_value) {
3699
+			if (! in_array($query_key, $this->_allowed_query_params, true)) {
3700
+				throw new EE_Error(
3701
+					sprintf(
3702
+						esc_html__(
3703
+							"You passed %s as a query parameter to %s, which is illegal! The allowed query parameters are %s",
3704
+							'event_espresso'
3705
+						),
3706
+						$query_key,
3707
+						get_class($this),
3708
+						//                      print_r( $this->_allowed_query_params, TRUE )
3709
+						implode(',', $this->_allowed_query_params)
3710
+					)
3711
+				);
3712
+			}
3713
+		}
3714
+		$main_model_join_sql = $query_object->get_main_model_join_sql();
3715
+		if (empty($main_model_join_sql)) {
3716
+			$query_object->set_main_model_join_sql($this->_construct_internal_join());
3717
+		}
3718
+		return $query_object;
3719
+	}
3720
+
3721
+
3722
+	/**
3723
+	 * Gets the where conditions that should be imposed on the query based on the
3724
+	 * context (eg reading frontend, backend, edit or delete).
3725
+	 *
3726
+	 * @param string $context one of EEM_Base::valid_cap_contexts()
3727
+	 * @return array @see
3728
+	 *                        https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3729
+	 * @throws EE_Error
3730
+	 */
3731
+	public function caps_where_conditions($context = self::caps_read)
3732
+	{
3733
+		EEM_Base::verify_is_valid_cap_context($context);
3734
+		$cap_where_conditions = [];
3735
+		$cap_restrictions     = $this->caps_missing($context);
3736
+		foreach ($cap_restrictions as $restriction_if_no_cap) {
3737
+			$cap_where_conditions = array_replace_recursive(
3738
+				$cap_where_conditions,
3739
+				$restriction_if_no_cap->get_default_where_conditions()
3740
+			);
3741
+		}
3742
+		return apply_filters(
3743
+			'FHEE__EEM_Base__caps_where_conditions__return',
3744
+			$cap_where_conditions,
3745
+			$this,
3746
+			$context,
3747
+			$cap_restrictions
3748
+		);
3749
+	}
3750
+
3751
+
3752
+	/**
3753
+	 * Verifies that $should_be_order_string is in $this->_allowed_order_values,
3754
+	 * otherwise throws an exception
3755
+	 *
3756
+	 * @param string $should_be_order_string
3757
+	 * @return string either ASC, asc, DESC or desc
3758
+	 * @throws EE_Error
3759
+	 */
3760
+	private function _extract_order($should_be_order_string)
3761
+	{
3762
+		if (in_array($should_be_order_string, $this->_allowed_order_values)) {
3763
+			return $should_be_order_string;
3764
+		}
3765
+		throw new EE_Error(
3766
+			sprintf(
3767
+				esc_html__(
3768
+					"While performing a query on '%s', tried to use '%s' as an order parameter. ",
3769
+					"event_espresso"
3770
+				),
3771
+				get_class($this),
3772
+				$should_be_order_string
3773
+			)
3774
+		);
3775
+	}
3776
+
3777
+
3778
+	/**
3779
+	 * Looks at all the models which are included in this query, and asks each
3780
+	 * for their universal_where_params, and returns them in the same format as $query_params[0] (where),
3781
+	 * so they can be merged
3782
+	 *
3783
+	 * @param EE_Model_Query_Info_Carrier $query_info_carrier
3784
+	 * @param string                      $use_default_where_conditions can be 'none','other_models_only', or 'all'.
3785
+	 *                                                                  'none' means NO default where conditions will
3786
+	 *                                                                  be used AT ALL during this query.
3787
+	 *                                                                  'other_models_only' means default where
3788
+	 *                                                                  conditions from other models will be used, but
3789
+	 *                                                                  not for this primary model. 'all', the default,
3790
+	 *                                                                  means default where conditions will apply as
3791
+	 *                                                                  normal
3792
+	 * @param array                       $where_query_params
3793
+	 * @return array
3794
+	 * @throws EE_Error
3795
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params
3796
+	 *                                                                  .md#0-where-conditions
3797
+	 */
3798
+	private function _get_default_where_conditions_for_models_in_query(
3799
+		EE_Model_Query_Info_Carrier $query_info_carrier,
3800
+		$use_default_where_conditions = EEM_Base::default_where_conditions_all,
3801
+		$where_query_params = []
3802
+	) {
3803
+		$allowed_used_default_where_conditions_values = EEM_Base::valid_default_where_conditions();
3804
+		if (! in_array($use_default_where_conditions, $allowed_used_default_where_conditions_values)) {
3805
+			throw new EE_Error(
3806
+				sprintf(
3807
+					esc_html__(
3808
+						"You passed an invalid value to the query parameter 'default_where_conditions' of '%s'. Allowed values are %s",
3809
+						"event_espresso"
3810
+					),
3811
+					$use_default_where_conditions,
3812
+					implode(", ", $allowed_used_default_where_conditions_values)
3813
+				)
3814
+			);
3815
+		}
3816
+		$universal_query_params = [];
3817
+		if ($this->_should_use_default_where_conditions($use_default_where_conditions, true)) {
3818
+			$universal_query_params = $this->_get_default_where_conditions();
3819
+		} elseif ($this->_should_use_minimum_where_conditions($use_default_where_conditions, true)) {
3820
+			$universal_query_params = $this->_get_minimum_where_conditions();
3821
+		}
3822
+		foreach ($query_info_carrier->get_model_names_included() as $model_relation_path => $model_name) {
3823
+			$related_model = $this->get_related_model_obj($model_name);
3824
+			if ($this->_should_use_default_where_conditions($use_default_where_conditions, false)) {
3825
+				$related_model_universal_where_params =
3826
+					$related_model->_get_default_where_conditions($model_relation_path);
3827
+			} elseif ($this->_should_use_minimum_where_conditions($use_default_where_conditions, false)) {
3828
+				$related_model_universal_where_params =
3829
+					$related_model->_get_minimum_where_conditions($model_relation_path);
3830
+			} else {
3831
+				// we don't want to add full or even minimum default where conditions from this model, so just continue
3832
+				continue;
3833
+			}
3834
+			$overrides              = $this->_override_defaults_or_make_null_friendly(
3835
+				$related_model_universal_where_params,
3836
+				$where_query_params,
3837
+				$related_model,
3838
+				$model_relation_path
3839
+			);
3840
+			$universal_query_params = EEH_Array::merge_arrays_and_overwrite_keys(
3841
+				$universal_query_params,
3842
+				$overrides
3843
+			);
3844
+		}
3845
+		return $universal_query_params;
3846
+	}
3847
+
3848
+
3849
+	/**
3850
+	 * Determines whether or not we should use default where conditions for the model in question
3851
+	 * (this model, or other related models).
3852
+	 * Basically, we should use default where conditions on this model if they have requested to use them on all models,
3853
+	 * this model only, or to use minimum where conditions on all other models and normal where conditions on this one.
3854
+	 * We should use default where conditions on related models when they requested to use default where conditions
3855
+	 * on all models, or specifically just on other related models
3856
+	 *
3857
+	 * @param      $default_where_conditions_value
3858
+	 * @param bool $for_this_model false means this is for OTHER related models
3859
+	 * @return bool
3860
+	 */
3861
+	private function _should_use_default_where_conditions($default_where_conditions_value, $for_this_model = true)
3862
+	{
3863
+		return (
3864
+				   $for_this_model
3865
+				   && in_array(
3866
+					   $default_where_conditions_value,
3867
+					   [
3868
+						   EEM_Base::default_where_conditions_all,
3869
+						   EEM_Base::default_where_conditions_this_only,
3870
+						   EEM_Base::default_where_conditions_minimum_others,
3871
+					   ],
3872
+					   true
3873
+				   )
3874
+			   )
3875
+			   || (
3876
+				   ! $for_this_model
3877
+				   && in_array(
3878
+					   $default_where_conditions_value,
3879
+					   [
3880
+						   EEM_Base::default_where_conditions_all,
3881
+						   EEM_Base::default_where_conditions_others_only,
3882
+					   ],
3883
+					   true
3884
+				   )
3885
+			   );
3886
+	}
3887
+
3888
+
3889
+	/**
3890
+	 * Determines whether or not we should use default minimum conditions for the model in question
3891
+	 * (this model, or other related models).
3892
+	 * Basically, we should use minimum where conditions on this model only if they requested all models to use minimum
3893
+	 * where conditions.
3894
+	 * We should use minimum where conditions on related models if they requested to use minimum where conditions
3895
+	 * on this model or others
3896
+	 *
3897
+	 * @param      $default_where_conditions_value
3898
+	 * @param bool $for_this_model false means this is for OTHER related models
3899
+	 * @return bool
3900
+	 */
3901
+	private function _should_use_minimum_where_conditions($default_where_conditions_value, $for_this_model = true)
3902
+	{
3903
+		return (
3904
+				   $for_this_model
3905
+				   && $default_where_conditions_value === EEM_Base::default_where_conditions_minimum_all
3906
+			   )
3907
+			   || (
3908
+				   ! $for_this_model
3909
+				   && in_array(
3910
+					   $default_where_conditions_value,
3911
+					   [
3912
+						   EEM_Base::default_where_conditions_minimum_others,
3913
+						   EEM_Base::default_where_conditions_minimum_all,
3914
+					   ],
3915
+					   true
3916
+				   )
3917
+			   );
3918
+	}
3919
+
3920
+
3921
+	/**
3922
+	 * Checks if any of the defaults have been overridden. If there are any that AREN'T overridden,
3923
+	 * then we also add a special where condition which allows for that model's primary key
3924
+	 * to be null (which is important for JOINs. Eg, if you want to see all Events ordered by Venue's name,
3925
+	 * then Event's with NO Venue won't appear unless you allow VNU_ID to be NULL)
3926
+	 *
3927
+	 * @param array    $default_where_conditions
3928
+	 * @param array    $provided_where_conditions
3929
+	 * @param EEM_Base $model
3930
+	 * @param string   $model_relation_path like 'Transaction.Payment.'
3931
+	 * @return array @see
3932
+	 *                                      https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3933
+	 * @throws EE_Error
3934
+	 */
3935
+	private function _override_defaults_or_make_null_friendly(
3936
+		$default_where_conditions,
3937
+		$provided_where_conditions,
3938
+		$model,
3939
+		$model_relation_path
3940
+	) {
3941
+		$null_friendly_where_conditions = [];
3942
+		$none_overridden                = true;
3943
+		$or_condition_key_for_defaults  = 'OR*' . get_class($model);
3944
+		foreach ($default_where_conditions as $key => $val) {
3945
+			if (isset($provided_where_conditions[ $key ])) {
3946
+				$none_overridden = false;
3947
+			} else {
3948
+				$null_friendly_where_conditions[ $or_condition_key_for_defaults ]['AND'][ $key ] = $val;
3949
+			}
3950
+		}
3951
+		if ($none_overridden && $default_where_conditions) {
3952
+			if ($model->has_primary_key_field()) {
3953
+				$null_friendly_where_conditions[ $or_condition_key_for_defaults ][ $model_relation_path
3954
+																				   . "."
3955
+																				   . $model->primary_key_name() ] =
3956
+					['IS NULL'];
3957
+			}/*else{
3958 3958
                 //@todo NO PK, use other defaults
3959 3959
             }*/
3960
-        }
3961
-        return $null_friendly_where_conditions;
3962
-    }
3963
-
3964
-
3965
-    /**
3966
-     * Uses the _default_where_conditions_strategy set during __construct() to get
3967
-     * default where conditions on all get_all, update, and delete queries done by this model.
3968
-     * Use the same syntax as client code. Eg on the Event model, use array('Event.EVT_post_type'=>'esp_event'),
3969
-     * NOT array('Event_CPT.post_type'=>'esp_event').
3970
-     *
3971
-     * @param string $model_relation_path eg, path from Event to Payment is "Registration.Transaction.Payment."
3972
-     * @return array @see
3973
-     *                                    https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3974
-     * @throws EE_Error
3975
-     * @throws EE_Error
3976
-     */
3977
-    private function _get_default_where_conditions($model_relation_path = '')
3978
-    {
3979
-        if ($this->_ignore_where_strategy) {
3980
-            return [];
3981
-        }
3982
-        return $this->_default_where_conditions_strategy->get_default_where_conditions($model_relation_path);
3983
-    }
3984
-
3985
-
3986
-    /**
3987
-     * Uses the _minimum_where_conditions_strategy set during __construct() to get
3988
-     * minimum where conditions on all get_all, update, and delete queries done by this model.
3989
-     * Use the same syntax as client code. Eg on the Event model, use array('Event.EVT_post_type'=>'esp_event'),
3990
-     * NOT array('Event_CPT.post_type'=>'esp_event').
3991
-     * Similar to _get_default_where_conditions
3992
-     *
3993
-     * @param string $model_relation_path eg, path from Event to Payment is "Registration.Transaction.Payment."
3994
-     * @return array @see
3995
-     *                                    https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3996
-     * @throws EE_Error
3997
-     * @throws EE_Error
3998
-     */
3999
-    protected function _get_minimum_where_conditions($model_relation_path = '')
4000
-    {
4001
-        if ($this->_ignore_where_strategy) {
4002
-            return [];
4003
-        }
4004
-        return $this->_minimum_where_conditions_strategy->get_default_where_conditions($model_relation_path);
4005
-    }
4006
-
4007
-
4008
-    /**
4009
-     * Creates the string of SQL for the select part of a select query, everything behind SELECT and before FROM.
4010
-     * Eg, "Event.post_id, Event.post_name,Event_Detail.EVT_ID..."
4011
-     *
4012
-     * @param EE_Model_Query_Info_Carrier $model_query_info
4013
-     * @return string
4014
-     * @throws EE_Error
4015
-     */
4016
-    private function _construct_default_select_sql(EE_Model_Query_Info_Carrier $model_query_info)
4017
-    {
4018
-        $selects = $this->_get_columns_to_select_for_this_model();
4019
-        foreach (
4020
-            $model_query_info->get_model_names_included() as $model_relation_chain => $name_of_other_model_included
4021
-        ) {
4022
-            $other_model_included = $this->get_related_model_obj($name_of_other_model_included);
4023
-            $other_model_selects  = $other_model_included->_get_columns_to_select_for_this_model($model_relation_chain);
4024
-            foreach ($other_model_selects as $key => $value) {
4025
-                $selects[] = $value;
4026
-            }
4027
-        }
4028
-        return implode(", ", $selects);
4029
-    }
4030
-
4031
-
4032
-    /**
4033
-     * Gets an array of columns to select for this model, which are necessary for it to create its objects.
4034
-     * So that's going to be the columns for all the fields on the model
4035
-     *
4036
-     * @param string $model_relation_chain like 'Question.Question_Group.Event'
4037
-     * @return array numerically indexed, values are columns to select and rename, eg "Event.ID AS 'Event.ID'"
4038
-     */
4039
-    public function _get_columns_to_select_for_this_model($model_relation_chain = '')
4040
-    {
4041
-        $fields                                       = $this->field_settings();
4042
-        $selects                                      = [];
4043
-        $table_alias_with_model_relation_chain_prefix =
4044
-            EE_Model_Parser::extract_table_alias_model_relation_chain_prefix(
4045
-                $model_relation_chain,
4046
-                $this->get_this_model_name()
4047
-            );
4048
-        foreach ($fields as $field_obj) {
4049
-            $selects[] = $table_alias_with_model_relation_chain_prefix
4050
-                         . $field_obj->get_table_alias()
4051
-                         . "."
4052
-                         . $field_obj->get_table_column()
4053
-                         . " AS '"
4054
-                         . $table_alias_with_model_relation_chain_prefix
4055
-                         . $field_obj->get_table_alias()
4056
-                         . "."
4057
-                         . $field_obj->get_table_column()
4058
-                         . "'";
4059
-        }
4060
-        // make sure we are also getting the PKs of each table
4061
-        $tables = $this->get_tables();
4062
-        if (count($tables) > 1) {
4063
-            foreach ($tables as $table_obj) {
4064
-                $qualified_pk_column = $table_alias_with_model_relation_chain_prefix
4065
-                                       . $table_obj->get_fully_qualified_pk_column();
4066
-                if (! in_array($qualified_pk_column, $selects)) {
4067
-                    $selects[] = "$qualified_pk_column AS '$qualified_pk_column'";
4068
-                }
4069
-            }
4070
-        }
4071
-        return $selects;
4072
-    }
4073
-
4074
-
4075
-    /**
4076
-     * Given a $query_param like 'Registration.Transaction.TXN_ID', pops off 'Registration.',
4077
-     * gets the join statement for it; gets the data types for it; and passes the remaining 'Transaction.TXN_ID'
4078
-     * onto its related Transaction object to do the same. Returns an EE_Join_And_Data_Types object which contains the
4079
-     * SQL for joining, and the data types
4080
-     *
4081
-     * @param null|string                 $original_query_param
4082
-     * @param string                      $query_param          like Registration.Transaction.TXN_ID
4083
-     * @param EE_Model_Query_Info_Carrier $passed_in_query_info
4084
-     * @param string                      $query_param_type     like Registration.Transaction.TXN_ID
4085
-     *                                                          or 'PAY_ID'. Otherwise, we don't expect there to be a
4086
-     *                                                          column name. We only want model names, eg 'Event.Venue'
4087
-     *                                                          or 'Registration's
4088
-     * @param string                      $original_query_param what it originally was (eg
4089
-     *                                                          Registration.Transaction.TXN_ID). If null, we assume it
4090
-     *                                                          matches $query_param
4091
-     * @return void only modifies the EEM_Related_Model_Info_Carrier passed into it
4092
-     * @throws EE_Error
4093
-     */
4094
-    private function _extract_related_model_info_from_query_param(
4095
-        $query_param,
4096
-        EE_Model_Query_Info_Carrier $passed_in_query_info,
4097
-        $query_param_type,
4098
-        $original_query_param = null
4099
-    ) {
4100
-        if ($original_query_param === null) {
4101
-            $original_query_param = $query_param;
4102
-        }
4103
-        $query_param = $this->_remove_stars_and_anything_after_from_condition_query_param_key($query_param);
4104
-        // check to see if we have a field on this model
4105
-        $this_model_fields = $this->field_settings(true);
4106
-        if (array_key_exists($query_param, $this_model_fields)) {
4107
-            $field_is_allowed = in_array(
4108
-                $query_param_type,
4109
-                [0, 'where', 'having', 'order_by', 'group_by', 'order', 'custom_selects'],
4110
-                true
4111
-            );
4112
-            if ($field_is_allowed) {
4113
-                return;
4114
-            }
4115
-            throw new EE_Error(
4116
-                sprintf(
4117
-                    esc_html__(
4118
-                        "Using a field name (%s) on model %s is not allowed on this query param type '%s'. Original query param was %s",
4119
-                        "event_espresso"
4120
-                    ),
4121
-                    $query_param,
4122
-                    get_class($this),
4123
-                    $query_param_type,
4124
-                    $original_query_param
4125
-                )
4126
-            );
4127
-        }
4128
-        // check if this is a special logic query param
4129
-        if (in_array($query_param, $this->_logic_query_param_keys, true)) {
4130
-            $operator_is_allowed = in_array($query_param_type, ['where', 'having', 0, 'custom_selects'], true);
4131
-            if ($operator_is_allowed) {
4132
-                return;
4133
-            }
4134
-            throw new EE_Error(
4135
-                sprintf(
4136
-                    esc_html__(
4137
-                        'Logic query params ("%1$s") are being used incorrectly with the following query param ("%2$s") on model %3$s. %4$sAdditional Info:%4$s%5$s',
4138
-                        'event_espresso'
4139
-                    ),
4140
-                    implode('", "', $this->_logic_query_param_keys),
4141
-                    $query_param,
4142
-                    get_class($this),
4143
-                    '<br />',
4144
-                    "\t"
4145
-                    . ' $passed_in_query_info = <pre>'
4146
-                    . print_r($passed_in_query_info, true)
4147
-                    . '</pre>'
4148
-                    . "\n\t"
4149
-                    . ' $query_param_type = '
4150
-                    . $query_param_type
4151
-                    . "\n\t"
4152
-                    . ' $original_query_param = '
4153
-                    . $original_query_param
4154
-                )
4155
-            );
4156
-        }
4157
-        // check if it's a custom selection
4158
-        if (
4159
-            $this->_custom_selections instanceof CustomSelects
4160
-            && in_array($query_param, $this->_custom_selections->columnAliases(), true)
4161
-        ) {
4162
-            return;
4163
-        }
4164
-        // check if has a model name at the beginning
4165
-        // and
4166
-        // check if it's a field on a related model
4167
-        if (
4168
-            $this->extractJoinModelFromQueryParams(
4169
-                $passed_in_query_info,
4170
-                $query_param,
4171
-                $original_query_param,
4172
-                $query_param_type
4173
-            )
4174
-        ) {
4175
-            return;
4176
-        }
4177
-
4178
-        // ok so $query_param didn't start with a model name
4179
-        // and we previously confirmed it wasn't a logic query param or field on the current model
4180
-        // it's wack, that's what it is
4181
-        throw new EE_Error(
4182
-            sprintf(
4183
-                esc_html__(
4184
-                    "There is no model named '%s' related to %s. Query param type is %s and original query param is %s",
4185
-                    "event_espresso"
4186
-                ),
4187
-                $query_param,
4188
-                get_class($this),
4189
-                $query_param_type,
4190
-                $original_query_param
4191
-            )
4192
-        );
4193
-    }
4194
-
4195
-
4196
-    /**
4197
-     * Extracts any possible join model information from the provided possible_join_string.
4198
-     * This method will read the provided $possible_join_string value and determine if there are any possible model
4199
-     * join
4200
-     * parts that should be added to the query.
4201
-     *
4202
-     * @param EE_Model_Query_Info_Carrier $query_info_carrier
4203
-     * @param string                      $possible_join_string  Such as Registration.REG_ID, or Registration
4204
-     * @param null|string                 $original_query_param
4205
-     * @param string                      $query_parameter_type  The type for the source of the $possible_join_string
4206
-     *                                                           ('where', 'order_by', 'group_by', 'custom_selects'
4207
-     *                                                           etc.)
4208
-     * @return bool  returns true if a join was added and false if not.
4209
-     * @throws EE_Error
4210
-     */
4211
-    private function extractJoinModelFromQueryParams(
4212
-        EE_Model_Query_Info_Carrier $query_info_carrier,
4213
-        $possible_join_string,
4214
-        $original_query_param,
4215
-        $query_parameter_type
4216
-    ) {
4217
-        foreach ($this->_model_relations as $valid_related_model_name => $relation_obj) {
4218
-            if (strpos($possible_join_string, $valid_related_model_name . ".") === 0) {
4219
-                $this->_add_join_to_model($valid_related_model_name, $query_info_carrier, $original_query_param);
4220
-                $possible_join_string = substr($possible_join_string, strlen($valid_related_model_name . "."));
4221
-                if ($possible_join_string === '') {
4222
-                    // nothing left to $query_param
4223
-                    // we should actually end in a field name, not a model like this!
4224
-                    throw new EE_Error(
4225
-                        sprintf(
4226
-                            esc_html__(
4227
-                                "Query param '%s' (of type %s on model %s) shouldn't end on a period (.) ",
4228
-                                "event_espresso"
4229
-                            ),
4230
-                            $possible_join_string,
4231
-                            $query_parameter_type,
4232
-                            get_class($this),
4233
-                            $valid_related_model_name
4234
-                        )
4235
-                    );
4236
-                }
4237
-                $related_model_obj = $this->get_related_model_obj($valid_related_model_name);
4238
-                $related_model_obj->_extract_related_model_info_from_query_param(
4239
-                    $possible_join_string,
4240
-                    $query_info_carrier,
4241
-                    $query_parameter_type,
4242
-                    $original_query_param
4243
-                );
4244
-                return true;
4245
-            }
4246
-            if ($possible_join_string === $valid_related_model_name) {
4247
-                $this->_add_join_to_model(
4248
-                    $valid_related_model_name,
4249
-                    $query_info_carrier,
4250
-                    $original_query_param
4251
-                );
4252
-                return true;
4253
-            }
4254
-        }
4255
-        return false;
4256
-    }
4257
-
4258
-
4259
-    /**
4260
-     * Extracts related models from Custom Selects and sets up any joins for those related models.
4261
-     *
4262
-     * @param EE_Model_Query_Info_Carrier $query_info_carrier
4263
-     * @throws EE_Error
4264
-     */
4265
-    private function extractRelatedModelsFromCustomSelects(EE_Model_Query_Info_Carrier $query_info_carrier)
4266
-    {
4267
-        if (
4268
-            $this->_custom_selections instanceof CustomSelects
4269
-            && (
4270
-                $this->_custom_selections->type() === CustomSelects::TYPE_STRUCTURED
4271
-                || $this->_custom_selections->type() == CustomSelects::TYPE_COMPLEX
4272
-            )
4273
-        ) {
4274
-            $original_selects = $this->_custom_selections->originalSelects();
4275
-            foreach ($original_selects as $alias => $select_configuration) {
4276
-                $this->extractJoinModelFromQueryParams(
4277
-                    $query_info_carrier,
4278
-                    $select_configuration[0],
4279
-                    $select_configuration[0],
4280
-                    'custom_selects'
4281
-                );
4282
-            }
4283
-        }
4284
-    }
4285
-
4286
-
4287
-    /**
4288
-     * Privately used by _extract_related_model_info_from_query_param to add a join to $model_name
4289
-     * and store it on $passed_in_query_info
4290
-     *
4291
-     * @param string                      $model_name
4292
-     * @param EE_Model_Query_Info_Carrier $passed_in_query_info
4293
-     * @param string                      $original_query_param used to extract the relation chain between the queried
4294
-     *                                                          model and $model_name. Eg, if we are querying Event,
4295
-     *                                                          and are adding a join to 'Payment' with the original
4296
-     *                                                          query param key
4297
-     *                                                          'Registration.Transaction.Payment.PAY_amount', we want
4298
-     *                                                          to extract 'Registration.Transaction.Payment', in case
4299
-     *                                                          Payment wants to add default query params so that it
4300
-     *                                                          will know what models to prepend onto its default query
4301
-     *                                                          params or in case it wants to rename tables (in case
4302
-     *                                                          there are multiple joins to the same table)
4303
-     * @return void
4304
-     * @throws EE_Error
4305
-     */
4306
-    private function _add_join_to_model(
4307
-        $model_name,
4308
-        EE_Model_Query_Info_Carrier $passed_in_query_info,
4309
-        $original_query_param
4310
-    ) {
4311
-        $relation_obj         = $this->related_settings_for($model_name);
4312
-        $model_relation_chain = EE_Model_Parser::extract_model_relation_chain($model_name, $original_query_param);
4313
-        // check if the relation is HABTM, because then we're essentially doing two joins
4314
-        // If so, join first to the JOIN table, and add its data types, and then continue as normal
4315
-        if ($relation_obj instanceof EE_HABTM_Relation) {
4316
-            $join_model_obj = $relation_obj->get_join_model();
4317
-            // replace the model specified with the join model for this relation chain, whi
4318
-            $relation_chain_to_join_model =
4319
-                EE_Model_Parser::replace_model_name_with_join_model_name_in_model_relation_chain(
4320
-                    $model_name,
4321
-                    $join_model_obj->get_this_model_name(),
4322
-                    $model_relation_chain
4323
-                );
4324
-            $passed_in_query_info->merge(
4325
-                new EE_Model_Query_Info_Carrier(
4326
-                    [$relation_chain_to_join_model => $join_model_obj->get_this_model_name()],
4327
-                    $relation_obj->get_join_to_intermediate_model_statement($relation_chain_to_join_model)
4328
-                )
4329
-            );
4330
-        }
4331
-        // now just join to the other table pointed to by the relation object, and add its data types
4332
-        $passed_in_query_info->merge(
4333
-            new EE_Model_Query_Info_Carrier(
4334
-                [$model_relation_chain => $model_name],
4335
-                $relation_obj->get_join_statement($model_relation_chain)
4336
-            )
4337
-        );
4338
-    }
4339
-
4340
-
4341
-    /**
4342
-     * Constructs SQL for where clause, like "WHERE Event.ID = 23 AND Transaction.amount > 100" etc.
4343
-     *
4344
-     * @param array $where_params @see
4345
-     *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
4346
-     * @return string of SQL
4347
-     * @throws EE_Error
4348
-     */
4349
-    private function _construct_where_clause($where_params)
4350
-    {
4351
-        $SQL = $this->_construct_condition_clause_recursive($where_params, ' AND ');
4352
-        if ($SQL) {
4353
-            return " WHERE " . $SQL;
4354
-        }
4355
-        return '';
4356
-    }
4357
-
4358
-
4359
-    /**
4360
-     * Just like the _construct_where_clause, except prepends 'HAVING' instead of 'WHERE',
4361
-     * and should be passed HAVING parameters, not WHERE parameters
4362
-     *
4363
-     * @param array $having_params
4364
-     * @return string
4365
-     * @throws EE_Error
4366
-     */
4367
-    private function _construct_having_clause($having_params)
4368
-    {
4369
-        $SQL = $this->_construct_condition_clause_recursive($having_params, ' AND ');
4370
-        if ($SQL) {
4371
-            return " HAVING " . $SQL;
4372
-        }
4373
-        return '';
4374
-    }
4375
-
4376
-
4377
-    /**
4378
-     * Used for creating nested WHERE conditions. Eg "WHERE ! (Event.ID = 3 OR ( Event_Meta.meta_key = 'bob' AND
4379
-     * Event_Meta.meta_value = 'foo'))"
4380
-     *
4381
-     * @param array  $where_params @see
4382
-     *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
4383
-     * @param string $glue         joins each subclause together. Should really only be " AND " or " OR "...
4384
-     * @return string of SQL
4385
-     * @throws EE_Error
4386
-     */
4387
-    private function _construct_condition_clause_recursive($where_params, $glue = ' AND')
4388
-    {
4389
-        $where_clauses = [];
4390
-        foreach ($where_params as $query_param => $op_and_value_or_sub_condition) {
4391
-            $query_param = $this->_remove_stars_and_anything_after_from_condition_query_param_key($query_param);
4392
-            if (in_array($query_param, $this->_logic_query_param_keys, true)) {
4393
-                switch ($query_param) {
4394
-                    case 'not':
4395
-                    case 'NOT':
4396
-                        $where_clauses[] = "! ("
4397
-                                           . $this->_construct_condition_clause_recursive(
4398
-                                $op_and_value_or_sub_condition,
4399
-                                $glue
4400
-                            )
4401
-                                           . ")";
4402
-                        break;
4403
-                    case 'and':
4404
-                    case 'AND':
4405
-                        $where_clauses[] = " ("
4406
-                                           . $this->_construct_condition_clause_recursive(
4407
-                                $op_and_value_or_sub_condition,
4408
-                                ' AND '
4409
-                            )
4410
-                                           . ")";
4411
-                        break;
4412
-                    case 'or':
4413
-                    case 'OR':
4414
-                        $where_clauses[] = " ("
4415
-                                           . $this->_construct_condition_clause_recursive(
4416
-                                $op_and_value_or_sub_condition,
4417
-                                ' OR '
4418
-                            )
4419
-                                           . ")";
4420
-                        break;
4421
-                }
4422
-            } else {
4423
-                $field_obj = $this->_deduce_field_from_query_param($query_param);
4424
-                // if it's not a normal field, maybe it's a custom selection?
4425
-                if (! $field_obj) {
4426
-                    if ($this->_custom_selections instanceof CustomSelects) {
4427
-                        $field_obj = $this->_custom_selections->getDataTypeForAlias($query_param);
4428
-                    } else {
4429
-                        throw new EE_Error(
4430
-                            sprintf(
4431
-                                esc_html__(
4432
-                                    "%s is neither a valid model field name, nor a custom selection",
4433
-                                    "event_espresso"
4434
-                                ),
4435
-                                $query_param
4436
-                            )
4437
-                        );
4438
-                    }
4439
-                }
4440
-                $op_and_value_sql = $this->_construct_op_and_value($op_and_value_or_sub_condition, $field_obj);
4441
-                $where_clauses[]  = $this->_deduce_column_name_from_query_param($query_param) . SP . $op_and_value_sql;
4442
-            }
4443
-        }
4444
-        return $where_clauses
4445
-            ? implode($glue, $where_clauses)
4446
-            : '';
4447
-    }
4448
-
4449
-
4450
-    /**
4451
-     * Takes the input parameter and extract the table name (alias) and column name
4452
-     *
4453
-     * @param string $query_param like Registration.Transaction.TXN_ID, Event.Datetime.start_time, or REG_ID
4454
-     * @return string table alias and column name for SQL, eg "Transaction.TXN_ID"
4455
-     * @throws EE_Error
4456
-     */
4457
-    private function _deduce_column_name_from_query_param($query_param)
4458
-    {
4459
-        $field = $this->_deduce_field_from_query_param($query_param);
4460
-        if ($field) {
4461
-            $table_alias_prefix = EE_Model_Parser::extract_table_alias_model_relation_chain_from_query_param(
4462
-                $field->get_model_name(),
4463
-                $query_param
4464
-            );
4465
-            return $table_alias_prefix . $field->get_qualified_column();
4466
-        }
4467
-        if (
4468
-            $this->_custom_selections instanceof CustomSelects
4469
-            && in_array($query_param, $this->_custom_selections->columnAliases(), true)
4470
-        ) {
4471
-            // maybe it's custom selection item?
4472
-            // if so, just use it as the "column name"
4473
-            return $query_param;
4474
-        }
4475
-        $custom_select_aliases = $this->_custom_selections instanceof CustomSelects
4476
-            ? implode(',', $this->_custom_selections->columnAliases())
4477
-            : '';
4478
-        throw new EE_Error(
4479
-            sprintf(
4480
-                esc_html__(
4481
-                    "%s is not a valid field on this model, nor a custom selection (%s)",
4482
-                    "event_espresso"
4483
-                ),
4484
-                $query_param,
4485
-                $custom_select_aliases
4486
-            )
4487
-        );
4488
-    }
4489
-
4490
-
4491
-    /**
4492
-     * Removes the * and anything after it from the condition query param key. It is useful to add the * to condition
4493
-     * query param keys (eg, 'OR*', 'EVT_ID') in order for the array keys to still be unique, so that they don't get
4494
-     * overwritten Takes a string like 'Event.EVT_ID*', 'TXN_total**', 'OR*1st', and 'DTT_reg_start*foobar' to
4495
-     * 'Event.EVT_ID', 'TXN_total', 'OR', and 'DTT_reg_start', respectively.
4496
-     *
4497
-     * @param string $condition_query_param_key
4498
-     * @return string
4499
-     */
4500
-    private function _remove_stars_and_anything_after_from_condition_query_param_key($condition_query_param_key)
4501
-    {
4502
-        $pos_of_star = strpos($condition_query_param_key, '*');
4503
-        if ($pos_of_star === false) {
4504
-            return $condition_query_param_key;
4505
-        }
4506
-        return substr($condition_query_param_key, 0, $pos_of_star);
4507
-    }
4508
-
4509
-
4510
-    /**
4511
-     * creates the SQL for the operator and the value in a WHERE clause, eg "< 23" or "LIKE '%monkey%'"
4512
-     *
4513
-     * @param array|string               $op_and_value
4514
-     * @param EE_Model_Field_Base|string $field_obj . If string, should be one of EEM_Base::_valid_wpdb_data_types
4515
-     * @return string
4516
-     * @throws EE_Error
4517
-     */
4518
-    private function _construct_op_and_value($op_and_value, $field_obj)
4519
-    {
4520
-        $operator = '=';
4521
-        $value    = $op_and_value;
4522
-        if (is_array($op_and_value)) {
4523
-            $operator = isset($op_and_value[0])
4524
-                ? $this->_prepare_operator_for_sql($op_and_value[0])
4525
-                : null;
4526
-            if (! $operator) {
4527
-                $php_array_like_string = [];
4528
-                foreach ($op_and_value as $key => $value) {
4529
-                    $php_array_like_string[] = "$key=>$value";
4530
-                }
4531
-                throw new EE_Error(
4532
-                    sprintf(
4533
-                        esc_html__(
4534
-                            "You setup a query parameter like you were going to specify an operator, but didn't. You provided '(%s)', but the operator should be at array key index 0 (eg array('>',32))",
4535
-                            "event_espresso"
4536
-                        ),
4537
-                        implode(",", $php_array_like_string)
4538
-                    )
4539
-                );
4540
-            }
4541
-            $value = $op_and_value[1] ?? null;
4542
-        }
4543
-
4544
-        // check to see if the value is actually another field
4545
-        if (is_array($op_and_value) && isset($op_and_value[2]) && $op_and_value[2]) {
4546
-            return $operator . SP . $this->_deduce_column_name_from_query_param($value);
4547
-        }
4548
-        if (in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4549
-            // in this case, the value should be an array, or at least a comma-separated list
4550
-            // it will need to handle a little differently
4551
-            $cleaned_value = $this->_construct_in_value($value, $field_obj);
4552
-            // note: $cleaned_value has already been run through $wpdb->prepare()
4553
-            return $operator . SP . $cleaned_value;
4554
-        }
4555
-        if (in_array($operator, $this->valid_between_style_operators()) && is_array($value)) {
4556
-            // the value should be an array with count of two.
4557
-            if (count($value) !== 2) {
4558
-                throw new EE_Error(
4559
-                    sprintf(
4560
-                        esc_html__(
4561
-                            "The '%s' operator must be used with an array of values and there must be exactly TWO values in that array.",
4562
-                            'event_espresso'
4563
-                        ),
4564
-                        "BETWEEN"
4565
-                    )
4566
-                );
4567
-            }
4568
-            $cleaned_value = $this->_construct_between_value($value, $field_obj);
4569
-            return $operator . SP . $cleaned_value;
4570
-        }
4571
-        if (in_array($operator, $this->valid_null_style_operators())) {
4572
-            if ($value !== null) {
4573
-                throw new EE_Error(
4574
-                    sprintf(
4575
-                        esc_html__(
4576
-                            "You attempted to give a value  (%s) while using a NULL-style operator (%s). That isn't valid",
4577
-                            "event_espresso"
4578
-                        ),
4579
-                        $value,
4580
-                        $operator
4581
-                    )
4582
-                );
4583
-            }
4584
-            return $operator;
4585
-        }
4586
-        if (in_array($operator, $this->valid_like_style_operators()) && ! is_array($value)) {
4587
-            // if the operator is 'LIKE', we want to allow percent signs (%) and not
4588
-            // remove other junk. So just treat it as a string.
4589
-            return $operator . SP . $this->_wpdb_prepare_using_field($value, '%s');
4590
-        }
4591
-        if (! in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4592
-            return $operator . SP . $this->_wpdb_prepare_using_field($value, $field_obj);
4593
-        }
4594
-        if (in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4595
-            throw new EE_Error(
4596
-                sprintf(
4597
-                    esc_html__(
4598
-                        "Operator '%s' must be used with an array of values, eg 'Registration.REG_ID' => array('%s',array(1,2,3))",
4599
-                        'event_espresso'
4600
-                    ),
4601
-                    $operator,
4602
-                    $operator
4603
-                )
4604
-            );
4605
-        }
4606
-        if (! in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4607
-            throw new EE_Error(
4608
-                sprintf(
4609
-                    esc_html__(
4610
-                        "Operator '%s' must be used with a single value, not an array. Eg 'Registration.REG_ID => array('%s',23))",
4611
-                        'event_espresso'
4612
-                    ),
4613
-                    $operator,
4614
-                    $operator
4615
-                )
4616
-            );
4617
-        }
4618
-        throw new EE_Error(
4619
-            sprintf(
4620
-                esc_html__(
4621
-                    "It appears you've provided some totally invalid query parameters. Operator and value were:'%s', which isn't right at all",
4622
-                    "event_espresso"
4623
-                ),
4624
-                http_build_query($op_and_value)
4625
-            )
4626
-        );
4627
-    }
4628
-
4629
-
4630
-    /**
4631
-     * Creates the operands to be used in a BETWEEN query, eg "'2014-12-31 20:23:33' AND '2015-01-23 12:32:54'"
4632
-     *
4633
-     * @param array                      $values
4634
-     * @param EE_Model_Field_Base|string $field_obj if string, it should be the datatype to be used when querying, eg
4635
-     *                                              '%s'
4636
-     * @return string
4637
-     * @throws EE_Error
4638
-     */
4639
-    public function _construct_between_value($values, $field_obj)
4640
-    {
4641
-        $cleaned_values = [];
4642
-        foreach ($values as $value) {
4643
-            $cleaned_values[] = $this->_wpdb_prepare_using_field($value, $field_obj);
4644
-        }
4645
-        return $cleaned_values[0] . " AND " . $cleaned_values[1];
4646
-    }
4647
-
4648
-
4649
-    /**
4650
-     * Takes an array or a comma-separated list of $values and cleans them
4651
-     * according to $data_type using $wpdb->prepare, and then makes the list a
4652
-     * string surrounded by ( and ). Eg, _construct_in_value(array(1,2,3),'%d') would
4653
-     * return '(1,2,3)'; _construct_in_value("1,2,hack",'%d') would return '(1,2,1)' (assuming
4654
-     * I'm right that a string, when interpreted as a digit, becomes a 1. It might become a 0)
4655
-     *
4656
-     * @param mixed                      $values    array or comma-separated string
4657
-     * @param EE_Model_Field_Base|string $field_obj if string, it should be a wpdb data type like '%s', or '%d'
4658
-     * @return string of SQL to follow an 'IN' or 'NOT IN' operator
4659
-     * @throws EE_Error
4660
-     */
4661
-    public function _construct_in_value($values, $field_obj)
4662
-    {
4663
-        $prepped = [];
4664
-        // check if the value is a CSV list
4665
-        if (is_string($values)) {
4666
-            // in which case, turn it into an array
4667
-            $values = explode(',', $values);
4668
-        }
4669
-        // make sure we only have one of each value in the list
4670
-        $values = array_unique($values);
4671
-        foreach ($values as $value) {
4672
-            $prepped[] = $this->_wpdb_prepare_using_field($value, $field_obj);
4673
-        }
4674
-        // we would just LOVE to leave $cleaned_values as an empty array, and return the value as "()",
4675
-        // but unfortunately that's invalid SQL. So instead we return a string which we KNOW will evaluate to be the empty set
4676
-        // which is effectively equivalent to returning "()". We don't return "(0)" because that only works for auto-incrementing columns
4677
-        if (empty($prepped)) {
4678
-            $all_fields  = $this->field_settings();
4679
-            $first_field = reset($all_fields);
4680
-            $main_table  = $this->_get_main_table();
4681
-            $prepped[]   = "SELECT {$first_field->get_table_column()} FROM {$main_table->get_table_name()} WHERE FALSE";
4682
-        }
4683
-        return '(' . implode(',', $prepped) . ')';
4684
-    }
4685
-
4686
-
4687
-    /**
4688
-     * @param mixed                      $value
4689
-     * @param EE_Model_Field_Base|string $field_obj if string it should be a wpdb data type like '%d'
4690
-     * @return false|null|string
4691
-     * @throws EE_Error
4692
-     */
4693
-    private function _wpdb_prepare_using_field($value, $field_obj)
4694
-    {
4695
-        /** @type WPDB $wpdb */
4696
-        global $wpdb;
4697
-        if ($field_obj instanceof EE_Model_Field_Base) {
4698
-            return $wpdb->prepare(
4699
-                $field_obj->get_wpdb_data_type(),
4700
-                $this->_prepare_value_for_use_in_db($value, $field_obj)
4701
-            );
4702
-        } //$field_obj should really just be a data type
4703
-        if (! in_array($field_obj, $this->_valid_wpdb_data_types)) {
4704
-            throw new EE_Error(
4705
-                sprintf(
4706
-                    esc_html__("%s is not a valid wpdb datatype. Valid ones are %s", "event_espresso"),
4707
-                    $field_obj,
4708
-                    implode(",", $this->_valid_wpdb_data_types)
4709
-                )
4710
-            );
4711
-        }
4712
-        return $wpdb->prepare($field_obj, $value);
4713
-    }
4714
-
4715
-
4716
-    /**
4717
-     * Takes the input parameter and finds the model field that it indicates.
4718
-     *
4719
-     * @param string $query_param_name like Registration.Transaction.TXN_ID, Event.Datetime.start_time, or REG_ID
4720
-     * @return EE_Model_Field_Base
4721
-     * @throws EE_Error
4722
-     */
4723
-    protected function _deduce_field_from_query_param($query_param_name)
4724
-    {
4725
-        // ok, now proceed with deducing which part is the model's name, and which is the field's name
4726
-        // which will help us find the database table and column
4727
-        $query_param_parts = explode(".", $query_param_name);
4728
-        if (empty($query_param_parts)) {
4729
-            throw new EE_Error(
4730
-                sprintf(
4731
-                    esc_html__(
4732
-                        "_extract_column_name is empty when trying to extract column and table name from %s",
4733
-                        'event_espresso'
4734
-                    ),
4735
-                    $query_param_name
4736
-                )
4737
-            );
4738
-        }
4739
-        $number_of_parts       = count($query_param_parts);
4740
-        $last_query_param_part = $query_param_parts[ count($query_param_parts) - 1 ];
4741
-        if ($number_of_parts === 1) {
4742
-            $field_name = $last_query_param_part;
4743
-            $model_obj  = $this;
4744
-        } else {// $number_of_parts >= 2
4745
-            // the last part is the column name, and there are only 2parts. therefore...
4746
-            $field_name = $last_query_param_part;
4747
-            $model_obj  = $this->get_related_model_obj($query_param_parts[ $number_of_parts - 2 ]);
4748
-        }
4749
-        try {
4750
-            return $model_obj->field_settings_for($field_name);
4751
-        } catch (EE_Error $e) {
4752
-            return null;
4753
-        }
4754
-    }
4755
-
4756
-
4757
-    /**
4758
-     * Given a field's name (ie, a key in $this->field_settings()), uses the EE_Model_Field object to get the table's
4759
-     * alias and column which corresponds to it
4760
-     *
4761
-     * @param string $field_name
4762
-     * @return string
4763
-     * @throws EE_Error
4764
-     */
4765
-    public function _get_qualified_column_for_field($field_name)
4766
-    {
4767
-        $all_fields = $this->field_settings();
4768
-        $field      = $all_fields[ $field_name ] ?? false;
4769
-        if ($field) {
4770
-            return $field->get_qualified_column();
4771
-        }
4772
-        throw new EE_Error(
4773
-            sprintf(
4774
-                esc_html__(
4775
-                    "There is no field titled %s on model %s. Either the query trying to use it is bad, or you need to add it to the list of fields on the model.",
4776
-                    'event_espresso'
4777
-                ),
4778
-                $field_name,
4779
-                get_class($this)
4780
-            )
4781
-        );
4782
-    }
4783
-
4784
-
4785
-    /**
4786
-     * similar to \EEM_Base::_get_qualified_column_for_field() but returns an array with data for ALL fields.
4787
-     * Example usage:
4788
-     * EEM_Ticket::instance()->get_all_wpdb_results(
4789
-     *      array(),
4790
-     *      ARRAY_A,
4791
-     *      EEM_Ticket::instance()->get_qualified_columns_for_all_fields()
4792
-     *  );
4793
-     * is equivalent to
4794
-     *  EEM_Ticket::instance()->get_all_wpdb_results( array(), ARRAY_A, '*' );
4795
-     * and
4796
-     *  EEM_Event::instance()->get_all_wpdb_results(
4797
-     *      array(
4798
-     *          array(
4799
-     *              'Datetime.Ticket.TKT_ID' => array( '<', 100 ),
4800
-     *          ),
4801
-     *          ARRAY_A,
4802
-     *          implode(
4803
-     *              ', ',
4804
-     *              array_merge(
4805
-     *                  EEM_Event::instance()->get_qualified_columns_for_all_fields( '', false ),
4806
-     *                  EEM_Ticket::instance()->get_qualified_columns_for_all_fields( 'Datetime', false )
4807
-     *              )
4808
-     *          )
4809
-     *      )
4810
-     *  );
4811
-     * selects rows from the database, selecting all the event and ticket columns, where the ticket ID is below 100
4812
-     *
4813
-     * @param string $model_relation_chain        the chain of models used to join between the model you want to query
4814
-     *                                            and the one whose fields you are selecting for example: when querying
4815
-     *                                            tickets model and selecting fields from the tickets model you would
4816
-     *                                            leave this parameter empty, because no models are needed to join
4817
-     *                                            between the queried model and the selected one. Likewise when
4818
-     *                                            querying the datetime model and selecting fields from the tickets
4819
-     *                                            model, it would also be left empty, because there is a direct
4820
-     *                                            relation from datetimes to tickets, so no model is needed to join
4821
-     *                                            them together. However, when querying from the event model and
4822
-     *                                            selecting fields from the ticket model, you should provide the string
4823
-     *                                            'Datetime', indicating that the event model must first join to the
4824
-     *                                            datetime model in order to find its relation to ticket model.
4825
-     *                                            Also, when querying from the venue model and selecting fields from
4826
-     *                                            the ticket model, you should provide the string 'Event.Datetime',
4827
-     *                                            indicating you need to join the venue model to the event model,
4828
-     *                                            to the datetime model, in order to find its relation to the ticket
4829
-     *                                            model. This string is used to deduce the prefix that gets added onto
4830
-     *                                            the models' tables qualified columns
4831
-     * @param bool   $return_string               if true, will return a string with qualified column names separated
4832
-     *                                            by ', ' if false, will simply return a numerically indexed array of
4833
-     *                                            qualified column names
4834
-     * @return array|string
4835
-     */
4836
-    public function get_qualified_columns_for_all_fields($model_relation_chain = '', $return_string = true)
4837
-    {
4838
-        $table_prefix      = str_replace('.', '__', $model_relation_chain) . (empty($model_relation_chain)
4839
-                ? ''
4840
-                : '__');
4841
-        $qualified_columns = [];
4842
-        foreach ($this->field_settings() as $field_name => $field) {
4843
-            $qualified_columns[] = $table_prefix . $field->get_qualified_column();
4844
-        }
4845
-        return $return_string
4846
-            ? implode(', ', $qualified_columns)
4847
-            : $qualified_columns;
4848
-    }
4849
-
4850
-
4851
-    /**
4852
-     * constructs the select use on special limit joins
4853
-     * NOTE: for now this has only been tested and will work when the  table alias is for the PRIMARY table. Although
4854
-     * its setup so the select query will be setup on and just doing the special select join off of the primary table
4855
-     * (as that is typically where the limits would be set).
4856
-     *
4857
-     * @param string       $table_alias The table the select is being built for
4858
-     * @param mixed|string $limit       The limit for this select
4859
-     * @return string                The final select join element for the query.
4860
-     * @throws EE_Error
4861
-     * @throws EE_Error
4862
-     */
4863
-    public function _construct_limit_join_select($table_alias, $limit)
4864
-    {
4865
-        $SQL = '';
4866
-        foreach ($this->_tables as $table_obj) {
4867
-            if ($table_obj instanceof EE_Primary_Table) {
4868
-                $SQL .= $table_alias === $table_obj->get_table_alias()
4869
-                    ? $table_obj->get_select_join_limit($limit)
4870
-                    : SP . $table_obj->get_table_name() . " AS " . $table_obj->get_table_alias() . SP;
4871
-            } elseif ($table_obj instanceof EE_Secondary_Table) {
4872
-                $SQL .= $table_alias === $table_obj->get_table_alias()
4873
-                    ? $table_obj->get_select_join_limit_join($limit)
4874
-                    : SP . $table_obj->get_join_sql($table_alias) . SP;
4875
-            }
4876
-        }
4877
-        return $SQL;
4878
-    }
4879
-
4880
-
4881
-    /**
4882
-     * Constructs the internal join if there are multiple tables, or simply the table's name and alias
4883
-     * Eg "wp_post AS Event" or "wp_post AS Event INNER JOIN wp_postmeta Event_Meta ON Event.ID = Event_Meta.post_id"
4884
-     *
4885
-     * @return string SQL
4886
-     * @throws EE_Error
4887
-     */
4888
-    public function _construct_internal_join()
4889
-    {
4890
-        $SQL = $this->_get_main_table()->get_table_sql();
4891
-        $SQL .= $this->_construct_internal_join_to_table_with_alias($this->_get_main_table()->get_table_alias());
4892
-        return $SQL;
4893
-    }
4894
-
4895
-
4896
-    /**
4897
-     * Constructs the SQL for joining all the tables on this model.
4898
-     * Normally $alias should be the primary table's alias, but in cases where
4899
-     * we have already joined to a secondary table (eg, the secondary table has a foreign key and is joined before the
4900
-     * primary table) then we should provide that secondary table's alias. Eg, with $alias being the primary table's
4901
-     * alias, this will construct SQL like:
4902
-     * " INNER JOIN wp_esp_secondary_table AS Secondary_Table ON Primary_Table.pk = Secondary_Table.fk".
4903
-     * With $alias being a secondary table's alias, this will construct SQL like:
4904
-     * " INNER JOIN wp_esp_primary_table AS Primary_Table ON Primary_Table.pk = Secondary_Table.fk".
4905
-     *
4906
-     * @param string $alias_prefixed table alias to join to (this table should already be in the FROM SQL clause)
4907
-     * @return string
4908
-     * @throws EE_Error
4909
-     * @throws EE_Error
4910
-     */
4911
-    public function _construct_internal_join_to_table_with_alias($alias_prefixed)
4912
-    {
4913
-        $SQL               = '';
4914
-        $alias_sans_prefix = EE_Model_Parser::remove_table_alias_model_relation_chain_prefix($alias_prefixed);
4915
-        foreach ($this->_tables as $table_obj) {
4916
-            if ($table_obj instanceof EE_Secondary_Table) {// table is secondary table
4917
-                if ($alias_sans_prefix === $table_obj->get_table_alias()) {
4918
-                    // so we're joining to this table, meaning the table is already in
4919
-                    // the FROM statement, BUT the primary table isn't. So we want
4920
-                    // to add the inverse join sql
4921
-                    $SQL .= $table_obj->get_inverse_join_sql($alias_prefixed);
4922
-                } else {
4923
-                    // just add a regular JOIN to this table from the primary table
4924
-                    $SQL .= $table_obj->get_join_sql($alias_prefixed);
4925
-                }
4926
-            }// if it's a primary table, dont add any SQL. it should already be in the FROM statement
4927
-        }
4928
-        return $SQL;
4929
-    }
4930
-
4931
-
4932
-    /**
4933
-     * Gets an array for storing all the data types on the next-to-be-executed-query.
4934
-     * This should be a growing array of keys being table-columns (eg 'EVT_ID' and 'Event.EVT_ID'), and values being
4935
-     * their data type (eg, '%s', '%d', etc)
4936
-     *
4937
-     * @return array
4938
-     */
4939
-    public function _get_data_types()
4940
-    {
4941
-        $data_types = [];
4942
-        foreach ($this->field_settings() as $field_obj) {
4943
-            // $data_types[$field_obj->get_table_column()] = $field_obj->get_wpdb_data_type();
4944
-            /** @var $field_obj EE_Model_Field_Base */
4945
-            $data_types[ $field_obj->get_qualified_column() ] = $field_obj->get_wpdb_data_type();
4946
-        }
4947
-        return $data_types;
4948
-    }
4949
-
4950
-
4951
-    /**
4952
-     * Gets the model object given the relation's name / model's name (eg, 'Event', 'Registration',etc. Always singular)
4953
-     *
4954
-     * @param string $model_name
4955
-     * @return EEM_Base
4956
-     * @throws EE_Error
4957
-     */
4958
-    public function get_related_model_obj($model_name)
4959
-    {
4960
-        $model_classname = "EEM_" . $model_name;
4961
-        if (! class_exists($model_classname)) {
4962
-            throw new EE_Error(
4963
-                sprintf(
4964
-                    esc_html__(
4965
-                        "You specified a related model named %s in your query. No such model exists, if it did, it would have the classname %s",
4966
-                        'event_espresso'
4967
-                    ),
4968
-                    $model_name,
4969
-                    $model_classname
4970
-                )
4971
-            );
4972
-        }
4973
-        return call_user_func($model_classname . "::instance");
4974
-    }
4975
-
4976
-
4977
-    /**
4978
-     * Returns the array of EE_ModelRelations for this model.
4979
-     *
4980
-     * @return EE_Model_Relation_Base[]
4981
-     */
4982
-    public function relation_settings()
4983
-    {
4984
-        return $this->_model_relations;
4985
-    }
4986
-
4987
-
4988
-    /**
4989
-     * Gets all related models that this model BELONGS TO. Handy to know sometimes
4990
-     * because without THOSE models, this model probably doesn't have much purpose.
4991
-     * (Eg, without an event, datetimes have little purpose.)
4992
-     *
4993
-     * @return EE_Belongs_To_Relation[]
4994
-     */
4995
-    public function belongs_to_relations()
4996
-    {
4997
-        $belongs_to_relations = [];
4998
-        foreach ($this->relation_settings() as $model_name => $relation_obj) {
4999
-            if ($relation_obj instanceof EE_Belongs_To_Relation) {
5000
-                $belongs_to_relations[ $model_name ] = $relation_obj;
5001
-            }
5002
-        }
5003
-        return $belongs_to_relations;
5004
-    }
5005
-
5006
-
5007
-    /**
5008
-     * Returns the specified EE_Model_Relation, or throws an exception
5009
-     *
5010
-     * @param string $relation_name name of relation, key in $this->_relatedModels
5011
-     * @return EE_Model_Relation_Base
5012
-     * @throws EE_Error
5013
-     */
5014
-    public function related_settings_for($relation_name)
5015
-    {
5016
-        $relatedModels = $this->relation_settings();
5017
-        if (! array_key_exists($relation_name, $relatedModels)) {
5018
-            throw new EE_Error(
5019
-                sprintf(
5020
-                    esc_html__(
5021
-                        'Cannot get %s related to %s. There is no model relation of that type. There is, however, %s...',
5022
-                        'event_espresso'
5023
-                    ),
5024
-                    $relation_name,
5025
-                    $this->_get_class_name(),
5026
-                    implode(', ', array_keys($relatedModels))
5027
-                )
5028
-            );
5029
-        }
5030
-        return $relatedModels[ $relation_name ];
5031
-    }
5032
-
5033
-
5034
-    /**
5035
-     * A convenience method for getting a specific field's settings, instead of getting all field settings for all
5036
-     * fields
5037
-     *
5038
-     * @param string  $fieldName
5039
-     * @param boolean $include_db_only_fields
5040
-     * @return EE_Model_Field_Base
5041
-     * @throws EE_Error
5042
-     */
5043
-    public function field_settings_for($fieldName, $include_db_only_fields = true)
5044
-    {
5045
-        $fieldSettings = $this->field_settings($include_db_only_fields);
5046
-        if (! array_key_exists($fieldName, $fieldSettings)) {
5047
-            throw new EE_Error(
5048
-                sprintf(
5049
-                    esc_html__("There is no field/column '%s' on '%s'", 'event_espresso'),
5050
-                    $fieldName,
5051
-                    get_class($this)
5052
-                )
5053
-            );
5054
-        }
5055
-        return $fieldSettings[ $fieldName ];
5056
-    }
5057
-
5058
-
5059
-    /**
5060
-     * Checks if this field exists on this model
5061
-     *
5062
-     * @param string $fieldName a key in the model's _field_settings array
5063
-     * @return boolean
5064
-     */
5065
-    public function has_field($fieldName)
5066
-    {
5067
-        $fieldSettings = $this->field_settings(true);
5068
-        if (isset($fieldSettings[ $fieldName ])) {
5069
-            return true;
5070
-        }
5071
-        return false;
5072
-    }
5073
-
5074
-
5075
-    /**
5076
-     * Returns whether or not this model has a relation to the specified model
5077
-     *
5078
-     * @param string $relation_name possibly one of the keys in the relation_settings array
5079
-     * @return boolean
5080
-     */
5081
-    public function has_relation($relation_name)
5082
-    {
5083
-        $relations = $this->relation_settings();
5084
-        if (isset($relations[ $relation_name ])) {
5085
-            return true;
5086
-        }
5087
-        return false;
5088
-    }
5089
-
5090
-
5091
-    /**
5092
-     * gets the field object of type 'primary_key' from the fieldsSettings attribute.
5093
-     * Eg, on EE_Answer that would be ANS_ID field object
5094
-     *
5095
-     * @param $field_obj
5096
-     * @return boolean
5097
-     */
5098
-    public function is_primary_key_field($field_obj): bool
5099
-    {
5100
-        return $field_obj instanceof EE_Primary_Key_Field_Base;
5101
-    }
5102
-
5103
-
5104
-    /**
5105
-     * gets the field object of type 'primary_key' from the fieldsSettings attribute.
5106
-     * Eg, on EE_Answer that would be ANS_ID field object
5107
-     *
5108
-     * @return EE_Primary_Key_Field_Base
5109
-     * @throws EE_Error
5110
-     */
5111
-    public function get_primary_key_field()
5112
-    {
5113
-        if ($this->_primary_key_field === null) {
5114
-            foreach ($this->field_settings(true) as $field_obj) {
5115
-                if ($this->is_primary_key_field($field_obj)) {
5116
-                    $this->_primary_key_field = $field_obj;
5117
-                    break;
5118
-                }
5119
-            }
5120
-            if (! $this->_primary_key_field instanceof EE_Primary_Key_Field_Base) {
5121
-                throw new EE_Error(
5122
-                    sprintf(
5123
-                        esc_html__("There is no Primary Key defined on model %s", 'event_espresso'),
5124
-                        get_class($this)
5125
-                    )
5126
-                );
5127
-            }
5128
-        }
5129
-        return $this->_primary_key_field;
5130
-    }
5131
-
5132
-
5133
-    /**
5134
-     * Returns whether or not not there is a primary key on this model.
5135
-     * Internally does some caching.
5136
-     *
5137
-     * @return boolean
5138
-     */
5139
-    public function has_primary_key_field()
5140
-    {
5141
-        if ($this->_has_primary_key_field === null) {
5142
-            try {
5143
-                $this->get_primary_key_field();
5144
-                $this->_has_primary_key_field = true;
5145
-            } catch (EE_Error $e) {
5146
-                $this->_has_primary_key_field = false;
5147
-            }
5148
-        }
5149
-        return $this->_has_primary_key_field;
5150
-    }
5151
-
5152
-
5153
-    /**
5154
-     * Finds the first field of type $field_class_name.
5155
-     *
5156
-     * @param string $field_class_name class name of field that you want to find. Eg, EE_Datetime_Field,
5157
-     *                                 EE_Foreign_Key_Field, etc
5158
-     * @return EE_Model_Field_Base or null if none is found
5159
-     */
5160
-    public function get_a_field_of_type($field_class_name)
5161
-    {
5162
-        foreach ($this->field_settings() as $field) {
5163
-            if ($field instanceof $field_class_name) {
5164
-                return $field;
5165
-            }
5166
-        }
5167
-        return null;
5168
-    }
5169
-
5170
-
5171
-    /**
5172
-     * Gets a foreign key field pointing to model.
5173
-     *
5174
-     * @param string $model_name eg Event, Registration, not EEM_Event
5175
-     * @return EE_Foreign_Key_Field_Base
5176
-     * @throws EE_Error
5177
-     */
5178
-    public function get_foreign_key_to($model_name)
5179
-    {
5180
-        if (! isset($this->_cache_foreign_key_to_fields[ $model_name ])) {
5181
-            foreach ($this->field_settings() as $field) {
5182
-                if (
5183
-                    $field instanceof EE_Foreign_Key_Field_Base
5184
-                    && in_array($model_name, $field->get_model_names_pointed_to())
5185
-                ) {
5186
-                    $this->_cache_foreign_key_to_fields[ $model_name ] = $field;
5187
-                    break;
5188
-                }
5189
-            }
5190
-            if (! isset($this->_cache_foreign_key_to_fields[ $model_name ])) {
5191
-                throw new EE_Error(
5192
-                    sprintf(
5193
-                        esc_html__(
5194
-                            "There is no foreign key field pointing to model %s on model %s",
5195
-                            'event_espresso'
5196
-                        ),
5197
-                        $model_name,
5198
-                        get_class($this)
5199
-                    )
5200
-                );
5201
-            }
5202
-        }
5203
-        return $this->_cache_foreign_key_to_fields[ $model_name ];
5204
-    }
5205
-
5206
-
5207
-    /**
5208
-     * Gets the table name (including $wpdb->prefix) for the table alias
5209
-     *
5210
-     * @param string $table_alias eg Event, Event_Meta, Registration, Transaction, but maybe
5211
-     *                            a table alias with a model chain prefix, like 'Venue__Event_Venue___Event_Meta'.
5212
-     *                            Either one works
5213
-     * @return string
5214
-     */
5215
-    public function get_table_for_alias($table_alias)
5216
-    {
5217
-        $table_alias_sans_model_relation_chain_prefix =
5218
-            EE_Model_Parser::remove_table_alias_model_relation_chain_prefix($table_alias);
5219
-        return $this->_tables[ $table_alias_sans_model_relation_chain_prefix ]->get_table_name();
5220
-    }
5221
-
5222
-
5223
-    /**
5224
-     * Returns a flat array of all field son this model, instead of organizing them
5225
-     * by table_alias as they are in the constructor.
5226
-     *
5227
-     * @param bool $include_db_only_fields flag indicating whether or not to include the db-only fields
5228
-     * @return EE_Model_Field_Base[] where the keys are the field's name
5229
-     */
5230
-    public function field_settings($include_db_only_fields = false)
5231
-    {
5232
-        if ($include_db_only_fields) {
5233
-            if ($this->_cached_fields === null) {
5234
-                $this->_cached_fields = [];
5235
-                foreach ($this->_fields as $fields_corresponding_to_table) {
5236
-                    foreach ($fields_corresponding_to_table as $field_name => $field_obj) {
5237
-                        $this->_cached_fields[ $field_name ] = $field_obj;
5238
-                    }
5239
-                }
5240
-            }
5241
-            return $this->_cached_fields;
5242
-        }
5243
-        if ($this->_cached_fields_non_db_only === null) {
5244
-            $this->_cached_fields_non_db_only = [];
5245
-            foreach ($this->_fields as $fields_corresponding_to_table) {
5246
-                foreach ($fields_corresponding_to_table as $field_name => $field_obj) {
5247
-                    /** @var $field_obj EE_Model_Field_Base */
5248
-                    if (! $field_obj->is_db_only_field()) {
5249
-                        $this->_cached_fields_non_db_only[ $field_name ] = $field_obj;
5250
-                    }
5251
-                }
5252
-            }
5253
-        }
5254
-        return $this->_cached_fields_non_db_only;
5255
-    }
5256
-
5257
-
5258
-    /**
5259
-     *        cycle though array of attendees and create objects out of each item
5260
-     *
5261
-     * @access        private
5262
-     * @param array $rows        of results of $wpdb->get_results($query,ARRAY_A)
5263
-     * @return EE_Base_Class[] array keys are primary keys (if there is a primary key on the model. if not,
5264
-     *                           numerically indexed)
5265
-     * @throws EE_Error
5266
-     * @throws ReflectionException
5267
-     */
5268
-    protected function _create_objects($rows = [])
5269
-    {
5270
-        $array_of_objects = [];
5271
-        if (empty($rows)) {
5272
-            return [];
5273
-        }
5274
-        $count_if_model_has_no_primary_key = 0;
5275
-        $has_primary_key                   = $this->has_primary_key_field();
5276
-        $primary_key_field                 = $has_primary_key
5277
-            ? $this->get_primary_key_field()
5278
-            : null;
5279
-        foreach ((array) $rows as $row) {
5280
-            if (empty($row)) {
5281
-                // wp did its weird thing where it returns an array like array(0=>null), which is totally not helpful...
5282
-                return [];
5283
-            }
5284
-            // check if we've already set this object in the results array,
5285
-            // in which case there's no need to process it further (again)
5286
-            if ($has_primary_key) {
5287
-                $table_pk_value = $this->_get_column_value_with_table_alias_or_not(
5288
-                    $row,
5289
-                    $primary_key_field->get_qualified_column(),
5290
-                    $primary_key_field->get_table_column()
5291
-                );
5292
-                if ($table_pk_value && isset($array_of_objects[ $table_pk_value ])) {
5293
-                    continue;
5294
-                }
5295
-            }
5296
-            $classInstance = $this->instantiate_class_from_array_or_object($row);
5297
-            if (! $classInstance) {
5298
-                throw new EE_Error(
5299
-                    sprintf(
5300
-                        esc_html__('Could not create instance of class %s from row %s', 'event_espresso'),
5301
-                        $this->get_this_model_name(),
5302
-                        http_build_query($row)
5303
-                    )
5304
-                );
5305
-            }
5306
-            // set the timezone on the instantiated objects
5307
-            $classInstance->set_timezone($this->_timezone);
5308
-            // make sure if there is any timezone setting present that we set the timezone for the object
5309
-            $key                      = $has_primary_key
5310
-                ? $classInstance->ID()
5311
-                : $count_if_model_has_no_primary_key++;
5312
-            $array_of_objects[ $key ] = $classInstance;
5313
-            // also, for all the relations of type BelongsTo, see if we can cache
5314
-            // those related models
5315
-            // (we could do this for other relations too, but if there are conditions
5316
-            // that filtered out some fo the results, then we'd be caching an incomplete set
5317
-            // so it requires a little more thought than just caching them immediately...)
5318
-            foreach ($this->_model_relations as $modelName => $relation_obj) {
5319
-                if ($relation_obj instanceof EE_Belongs_To_Relation) {
5320
-                    // check if this model's INFO is present. If so, cache it on the model
5321
-                    $other_model           = $relation_obj->get_other_model();
5322
-                    $other_model_obj_maybe = $other_model->instantiate_class_from_array_or_object($row);
5323
-                    // if we managed to make a model object from the results, cache it on the main model object
5324
-                    if ($other_model_obj_maybe) {
5325
-                        // set timezone on these other model objects if they are present
5326
-                        $other_model_obj_maybe->set_timezone($this->_timezone);
5327
-                        $classInstance->cache($modelName, $other_model_obj_maybe);
5328
-                    }
5329
-                }
5330
-            }
5331
-            // also, if this was a custom select query, let's see if there are any results for the custom select fields
5332
-            // and add them to the object as well.  We'll convert according to the set data_type if there's any set for
5333
-            // the field in the CustomSelects object
5334
-            if ($this->_custom_selections instanceof CustomSelects) {
5335
-                $classInstance->setCustomSelectsValues(
5336
-                    $this->getValuesForCustomSelectAliasesFromResults($row)
5337
-                );
5338
-            }
5339
-        }
5340
-        return $array_of_objects;
5341
-    }
5342
-
5343
-
5344
-    /**
5345
-     * This will parse a given row of results from the db and see if any keys in the results match an alias within the
5346
-     * current CustomSelects object. This will be used to build an array of values indexed by those keys.
5347
-     *
5348
-     * @param array $db_results_row
5349
-     * @return array
5350
-     */
5351
-    protected function getValuesForCustomSelectAliasesFromResults(array $db_results_row)
5352
-    {
5353
-        $results = [];
5354
-        if ($this->_custom_selections instanceof CustomSelects) {
5355
-            foreach ($this->_custom_selections->columnAliases() as $alias) {
5356
-                if (isset($db_results_row[ $alias ])) {
5357
-                    $results[ $alias ] = $this->convertValueToDataType(
5358
-                        $db_results_row[ $alias ],
5359
-                        $this->_custom_selections->getDataTypeForAlias($alias)
5360
-                    );
5361
-                }
5362
-            }
5363
-        }
5364
-        return $results;
5365
-    }
5366
-
5367
-
5368
-    /**
5369
-     * This will set the value for the given alias
5370
-     *
5371
-     * @param string $value
5372
-     * @param string $datatype (one of %d, %s, %f)
5373
-     * @return int|string|float (int for %d, string for %s, float for %f)
5374
-     */
5375
-    protected function convertValueToDataType($value, $datatype)
5376
-    {
5377
-        switch ($datatype) {
5378
-            case '%f':
5379
-                return (float) $value;
5380
-            case '%d':
5381
-                return (int) $value;
5382
-            default:
5383
-                return (string) $value;
5384
-        }
5385
-    }
5386
-
5387
-
5388
-    /**
5389
-     * The purpose of this method is to allow us to create a model object that is not in the db that holds default
5390
-     * values. A typical example of where this is used is when creating a new item and the initial load of a form.  We
5391
-     * dont' necessarily want to test for if the object is present but just assume it is BUT load the defaults from the
5392
-     * object (as set in the model_field!).
5393
-     *
5394
-     * @return EE_Base_Class single EE_Base_Class object with default values for the properties.
5395
-     * @throws EE_Error
5396
-     * @throws ReflectionException
5397
-     */
5398
-    public function create_default_object()
5399
-    {
5400
-        $this_model_fields_and_values = [];
5401
-        // setup the row using default values;
5402
-        foreach ($this->field_settings() as $field_name => $field_obj) {
5403
-            $this_model_fields_and_values[ $field_name ] = $field_obj->get_default_value();
5404
-        }
5405
-        $className = $this->_get_class_name();
5406
-        return EE_Registry::instance()->load_class($className, [$this_model_fields_and_values], false, false);
5407
-    }
5408
-
5409
-
5410
-    /**
5411
-     * @param mixed $cols_n_values either an array of where each key is the name of a field, and the value is its value
5412
-     *                             or an stdClass where each property is the name of a column,
5413
-     * @return EE_Base_Class
5414
-     * @throws EE_Error
5415
-     * @throws ReflectionException
5416
-     */
5417
-    public function instantiate_class_from_array_or_object($cols_n_values)
5418
-    {
5419
-        if (! is_array($cols_n_values) && is_object($cols_n_values)) {
5420
-            $cols_n_values = get_object_vars($cols_n_values);
5421
-        }
5422
-        $primary_key = null;
5423
-        // make sure the array only has keys that are fields/columns on this model
5424
-        $this_model_fields_n_values = $this->_deduce_fields_n_values_from_cols_n_values($cols_n_values);
5425
-        if ($this->has_primary_key_field() && isset($this_model_fields_n_values[ $this->primary_key_name() ])) {
5426
-            $primary_key = $this_model_fields_n_values[ $this->primary_key_name() ];
5427
-        }
5428
-        $className = $this->_get_class_name();
5429
-        // check we actually found results that we can use to build our model object
5430
-        // if not, return null
5431
-        if ($this->has_primary_key_field()) {
5432
-            if (empty($this_model_fields_n_values[ $this->primary_key_name() ])) {
5433
-                return null;
5434
-            }
5435
-        } elseif ($this->unique_indexes()) {
5436
-            $first_column = reset($this_model_fields_n_values);
5437
-            if (empty($first_column)) {
5438
-                return null;
5439
-            }
5440
-        }
5441
-        // if there is no primary key or the object doesn't already exist in the entity map, then create a new instance
5442
-        if ($primary_key) {
5443
-            $classInstance = $this->get_from_entity_map($primary_key);
5444
-            if (! $classInstance) {
5445
-                $classInstance = EE_Registry::instance()
5446
-                                            ->load_class(
5447
-                                                $className,
5448
-                                                [$this_model_fields_n_values, $this->_timezone],
5449
-                                                true,
5450
-                                                false
5451
-                                            );
5452
-                // add this new object to the entity map
5453
-                $classInstance = $this->add_to_entity_map($classInstance);
5454
-            }
5455
-        } else {
5456
-            $classInstance = EE_Registry::instance()->load_class(
5457
-                $className,
5458
-                [$this_model_fields_n_values, $this->_timezone],
5459
-                true,
5460
-                false
5461
-            );
5462
-        }
5463
-        return $classInstance;
5464
-    }
5465
-
5466
-
5467
-    /**
5468
-     * Gets the model object from the  entity map if it exists
5469
-     *
5470
-     * @param int|string $id the ID of the model object
5471
-     * @return EE_Base_Class
5472
-     */
5473
-    public function get_from_entity_map($id)
5474
-    {
5475
-        return $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ] ?? null;
5476
-    }
5477
-
5478
-
5479
-    /**
5480
-     * add_to_entity_map
5481
-     * Adds the object to the model's entity mappings
5482
-     *        Effectively tells the models "Hey, this model object is the most up-to-date representation of the data,
5483
-     *        and for the remainder of the request, it's even more up-to-date than what's in the database.
5484
-     *        So, if the database doesn't agree with what's in the entity mapper, ignore the database"
5485
-     *        If the database gets updated directly and you want the entity mapper to reflect that change,
5486
-     *        then this method should be called immediately after the update query
5487
-     * Note: The map is indexed by whatever the current blog id is set (via EEM_Base::$_model_query_blog_id).  This is
5488
-     * so on multisite, the entity map is specific to the query being done for a specific site.
5489
-     *
5490
-     * @param EE_Base_Class $object
5491
-     * @return EE_Base_Class
5492
-     * @throws EE_Error
5493
-     * @throws ReflectionException
5494
-     */
5495
-    public function add_to_entity_map(EE_Base_Class $object)
5496
-    {
5497
-        $className = $this->_get_class_name();
5498
-        if (! $object instanceof $className) {
5499
-            throw new EE_Error(
5500
-                sprintf(
5501
-                    esc_html__("You tried adding a %s to a mapping of %ss", "event_espresso"),
5502
-                    is_object($object)
5503
-                        ? get_class($object)
5504
-                        : $object,
5505
-                    $className
5506
-                )
5507
-            );
5508
-        }
5509
-
5510
-        if (! $object->ID()) {
5511
-            throw new EE_Error(
5512
-                sprintf(
5513
-                    esc_html__(
5514
-                        "You tried storing a model object with NO ID in the %s entity mapper.",
5515
-                        "event_espresso"
5516
-                    ),
5517
-                    get_class($this)
5518
-                )
5519
-            );
5520
-        }
5521
-        // double check it's not already there
5522
-        $classInstance = $this->get_from_entity_map($object->ID());
5523
-        if ($classInstance) {
5524
-            return $classInstance;
5525
-        }
5526
-        $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $object->ID() ] = $object;
5527
-        return $object;
5528
-    }
5529
-
5530
-
5531
-    /**
5532
-     * if a valid identifier is provided, then that entity is unset from the entity map,
5533
-     * if no identifier is provided, then the entire entity map is emptied
5534
-     *
5535
-     * @param int|string $id the ID of the model object
5536
-     * @return boolean
5537
-     */
5538
-    public function clear_entity_map($id = null)
5539
-    {
5540
-        if (empty($id)) {
5541
-            $this->_entity_map[ EEM_Base::$_model_query_blog_id ] = [];
5542
-            return true;
5543
-        }
5544
-        if (isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])) {
5545
-            unset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ]);
5546
-            return true;
5547
-        }
5548
-        return false;
5549
-    }
5550
-
5551
-
5552
-    /**
5553
-     * Public wrapper for _deduce_fields_n_values_from_cols_n_values.
5554
-     * Given an array where keys are column (or column alias) names and values,
5555
-     * returns an array of their corresponding field names and database values
5556
-     *
5557
-     * @param array $cols_n_values
5558
-     * @return array
5559
-     * @throws EE_Error
5560
-     * @throws ReflectionException
5561
-     */
5562
-    public function deduce_fields_n_values_from_cols_n_values(array $cols_n_values): array
5563
-    {
5564
-        return $this->_deduce_fields_n_values_from_cols_n_values($cols_n_values);
5565
-    }
5566
-
5567
-
5568
-    /**
5569
-     * _deduce_fields_n_values_from_cols_n_values
5570
-     * Given an array where keys are column (or column alias) names and values,
5571
-     * returns an array of their corresponding field names and database values
5572
-     *
5573
-     * @param array|stdClass $cols_n_values
5574
-     * @return array
5575
-     * @throws EE_Error
5576
-     * @throws ReflectionException
5577
-     */
5578
-    protected function _deduce_fields_n_values_from_cols_n_values($cols_n_values): array
5579
-    {
5580
-        if ($cols_n_values instanceof stdClass) {
5581
-            $cols_n_values = get_object_vars($cols_n_values);
5582
-        }
5583
-        $this_model_fields_n_values = [];
5584
-        foreach ($this->get_tables() as $table_alias => $table_obj) {
5585
-            $table_pk_value = $this->_get_column_value_with_table_alias_or_not(
5586
-                $cols_n_values,
5587
-                $table_obj->get_fully_qualified_pk_column(),
5588
-                $table_obj->get_pk_column()
5589
-            );
5590
-            // there is a primary key on this table and its not set. Use defaults for all its columns
5591
-            if ($table_pk_value === null && $table_obj->get_pk_column()) {
5592
-                foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5593
-                    if (! $field_obj->is_db_only_field()) {
5594
-                        // prepare field as if its coming from db
5595
-                        $prepared_value                            =
5596
-                            $field_obj->prepare_for_set($field_obj->get_default_value());
5597
-                        $this_model_fields_n_values[ $field_name ] = $field_obj->prepare_for_use_in_db($prepared_value);
5598
-                    }
5599
-                }
5600
-            } else {
5601
-                // the table's rows existed. Use their values
5602
-                foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5603
-                    if (! $field_obj->is_db_only_field()) {
5604
-                        $this_model_fields_n_values[ $field_name ] = $this->_get_column_value_with_table_alias_or_not(
5605
-                            $cols_n_values,
5606
-                            $field_obj->get_qualified_column(),
5607
-                            $field_obj->get_table_column()
5608
-                        );
5609
-                    }
5610
-                }
5611
-            }
5612
-        }
5613
-        return $this_model_fields_n_values;
5614
-    }
5615
-
5616
-
5617
-    /**
5618
-     * @param $cols_n_values
5619
-     * @param $qualified_column
5620
-     * @param $regular_column
5621
-     * @return null
5622
-     * @throws EE_Error
5623
-     * @throws ReflectionException
5624
-     */
5625
-    protected function _get_column_value_with_table_alias_or_not($cols_n_values, $qualified_column, $regular_column)
5626
-    {
5627
-        $value = null;
5628
-        // ask the field what it think it's table_name.column_name should be, and call it the "qualified column"
5629
-        // does the field on the model relate to this column retrieved from the db?
5630
-        // or is it a db-only field? (not relating to the model)
5631
-        if (isset($cols_n_values[ $qualified_column ])) {
5632
-            $value = $cols_n_values[ $qualified_column ];
5633
-        } elseif (isset($cols_n_values[ $regular_column ])) {
5634
-            $value = $cols_n_values[ $regular_column ];
5635
-        } elseif (! empty($this->foreign_key_aliases)) {
5636
-            // no PK?  ok check if there is a foreign key alias set for this table
5637
-            // then check if that alias exists in the incoming data
5638
-            // AND that the actual PK the $FK_alias represents matches the $qualified_column (full PK)
5639
-            foreach ($this->foreign_key_aliases as $FK_alias => $PK_column) {
5640
-                if ($PK_column === $qualified_column && !empty($cols_n_values[ $FK_alias ])) {
5641
-                    $value = $cols_n_values[ $FK_alias ];
5642
-                    [$pk_class] = explode('.', $PK_column);
5643
-                    $pk_model_name = "EEM_{$pk_class}";
5644
-                    /** @var EEM_Base $pk_model */
5645
-                    $pk_model = EE_Registry::instance()->load_model($pk_model_name);
5646
-                    if ($pk_model instanceof EEM_Base) {
5647
-                        // make sure object is pulled from db and added to entity map
5648
-                        $pk_model->get_one_by_ID($value);
5649
-                    }
5650
-                    break;
5651
-                }
5652
-            }
5653
-        }
5654
-        return $value;
5655
-    }
5656
-
5657
-
5658
-    /**
5659
-     * refresh_entity_map_from_db
5660
-     * Makes sure the model object in the entity map at $id assumes the values
5661
-     * of the database (opposite of EE_base_Class::save())
5662
-     *
5663
-     * @param int|string $id
5664
-     * @return EE_Base_Class|EE_Soft_Delete_Base_Class|mixed|null
5665
-     * @throws EE_Error
5666
-     * @throws ReflectionException
5667
-     */
5668
-    public function refresh_entity_map_from_db($id)
5669
-    {
5670
-        $obj_in_map = $this->get_from_entity_map($id);
5671
-        if ($obj_in_map) {
5672
-            $wpdb_results = $this->_get_all_wpdb_results(
5673
-                [[$this->get_primary_key_field()->get_name() => $id], 'limit' => 1]
5674
-            );
5675
-            if ($wpdb_results && is_array($wpdb_results)) {
5676
-                $one_row = reset($wpdb_results);
5677
-                foreach ($this->_deduce_fields_n_values_from_cols_n_values($one_row) as $field_name => $db_value) {
5678
-                    $obj_in_map->set_from_db($field_name, $db_value);
5679
-                }
5680
-                // clear the cache of related model objects
5681
-                foreach ($this->relation_settings() as $relation_name => $relation_obj) {
5682
-                    $obj_in_map->clear_cache($relation_name, null, true);
5683
-                }
5684
-            }
5685
-            $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ] = $obj_in_map;
5686
-            return $obj_in_map;
5687
-        }
5688
-        return $this->get_one_by_ID($id);
5689
-    }
5690
-
5691
-
5692
-    /**
5693
-     * refresh_entity_map_with
5694
-     * Leaves the entry in the entity map alone, but updates it to match the provided
5695
-     * $replacing_model_obj (which we assume to be its equivalent but somehow NOT in the entity map).
5696
-     * This is useful if you have a model object you want to make authoritative over what's in the entity map currently.
5697
-     * Note: The old $replacing_model_obj should now be destroyed as it's now un-authoritative
5698
-     *
5699
-     * @param int|string    $id
5700
-     * @param EE_Base_Class $replacing_model_obj
5701
-     * @return EE_Base_Class
5702
-     * @throws EE_Error
5703
-     * @throws ReflectionException
5704
-     */
5705
-    public function refresh_entity_map_with($id, $replacing_model_obj)
5706
-    {
5707
-        $obj_in_map = $this->get_from_entity_map($id);
5708
-        if ($obj_in_map) {
5709
-            if ($replacing_model_obj instanceof EE_Base_Class) {
5710
-                foreach ($replacing_model_obj->model_field_array() as $field_name => $value) {
5711
-                    $obj_in_map->set($field_name, $value);
5712
-                }
5713
-                // make the model object in the entity map's cache match the $replacing_model_obj
5714
-                foreach ($this->relation_settings() as $relation_name => $relation_obj) {
5715
-                    $obj_in_map->clear_cache($relation_name, null, true);
5716
-                    foreach ($replacing_model_obj->get_all_from_cache($relation_name) as $cache_id => $cached_obj) {
5717
-                        $obj_in_map->cache($relation_name, $cached_obj, $cache_id);
5718
-                    }
5719
-                }
5720
-            }
5721
-            return $obj_in_map;
5722
-        }
5723
-        $this->add_to_entity_map($replacing_model_obj);
5724
-        return $replacing_model_obj;
5725
-    }
5726
-
5727
-
5728
-    /**
5729
-     * Gets the EE class that corresponds to this model. Eg, for EEM_Answer that
5730
-     * would be EE_Answer.To import that class, you'd just add ".class.php" to the name, like so
5731
-     * require_once($this->_getClassName().".class.php");
5732
-     *
5733
-     * @return string
5734
-     */
5735
-    private function _get_class_name()
5736
-    {
5737
-        return "EE_" . $this->get_this_model_name();
5738
-    }
5739
-
5740
-
5741
-    /**
5742
-     * Get the name of the items this model represents, for the quantity specified. Eg,
5743
-     * if $quantity==1, on EEM_Event, it would 'Event' (internationalized), otherwise
5744
-     * it would be 'Events'.
5745
-     *
5746
-     * @param int|float|null $quantity
5747
-     * @return string
5748
-     */
5749
-    public function item_name($quantity = 1): string
5750
-    {
5751
-        $quantity = floor($quantity);
5752
-        return apply_filters(
5753
-            'FHEE__EEM_Base__item_name__plural_or_singular',
5754
-            $quantity > 1
5755
-                ? $this->plural_item
5756
-                : $this->singular_item,
5757
-            $quantity,
5758
-            $this->plural_item,
5759
-            $this->singular_item
5760
-        );
5761
-    }
5762
-
5763
-
5764
-    /**
5765
-     * Very handy general function to allow for plugins to extend any child of EE_TempBase.
5766
-     * If a method is called on a child of EE_TempBase that doesn't exist, this function is called
5767
-     * (http://www.garfieldtech.com/blog/php-magic-call) and passed the method's name and arguments. Instead of
5768
-     * requiring a plugin to extend the EE_TempBase (which works fine is there's only 1 plugin, but when will that
5769
-     * happen?) they can add a hook onto 'filters_hook_espresso__{className}__{methodName}' (eg,
5770
-     * filters_hook_espresso__EE_Answer__my_great_function) and accepts 2 arguments: the object on which the function
5771
-     * was called, and an array of the original arguments passed to the function. Whatever their callback function
5772
-     * returns will be returned by this function. Example: in functions.php (or in a plugin):
5773
-     * add_filter('FHEE__EE_Answer__my_callback','my_callback',10,3); function
5774
-     * my_callback($previousReturnValue,EE_TempBase $object,$argsArray){
5775
-     * $returnString= "you called my_callback! and passed args:".implode(",",$argsArray);
5776
-     *        return $previousReturnValue.$returnString;
5777
-     * }
5778
-     * require('EEM_Answer.model.php');
5779
-     * echo EEM_Answer::instance()->my_callback('monkeys',100);
5780
-     * // will output "you called my_callback! and passed args:monkeys,100"
5781
-     *
5782
-     * @param string $methodName name of method which was called on a child of EE_TempBase, but which
5783
-     * @param array  $args       array of original arguments passed to the function
5784
-     * @return mixed whatever the plugin which calls add_filter decides
5785
-     * @throws EE_Error
5786
-     */
5787
-    public function __call($methodName, $args)
5788
-    {
5789
-        $className = get_class($this);
5790
-        $tagName   = "FHEE__{$className}__{$methodName}";
5791
-        if (! has_filter($tagName)) {
5792
-            throw new EE_Error(
5793
-                sprintf(
5794
-                    esc_html__(
5795
-                        'Method %1$s on model %2$s does not exist! You can create one with the following code in functions.php or in a plugin: %4$s function my_callback(%4$s \$previousReturnValue, EEM_Base \$object\ $argsArray=NULL ){%4$s     /*function body*/%4$s      return \$whatever;%4$s }%4$s add_filter( \'%3$s\', \'my_callback\', 10, 3 );',
5796
-                        'event_espresso'
5797
-                    ),
5798
-                    $methodName,
5799
-                    $className,
5800
-                    $tagName,
5801
-                    '<br />'
5802
-                )
5803
-            );
5804
-        }
5805
-        return apply_filters($tagName, null, $this, $args);
5806
-    }
5807
-
5808
-
5809
-    /**
5810
-     * Ensures $base_class_obj_or_id is of the EE_Base_Class child that corresponds ot this model.
5811
-     * If not, assumes its an ID, and uses $this->get_one_by_ID() to get the EE_Base_Class.
5812
-     *
5813
-     * @param EE_Base_Class|string|int $base_class_obj_or_id either:
5814
-     *                                                       the EE_Base_Class object that corresponds to this Model,
5815
-     *                                                       the object's class name
5816
-     *                                                       or object's ID
5817
-     * @param boolean                  $ensure_is_in_db      if set, we will also verify this model object
5818
-     *                                                       exists in the database. If it does not, we add it
5819
-     * @return EE_Base_Class
5820
-     * @throws EE_Error
5821
-     * @throws ReflectionException
5822
-     */
5823
-    public function ensure_is_obj($base_class_obj_or_id, $ensure_is_in_db = false)
5824
-    {
5825
-        $className = $this->_get_class_name();
5826
-        if ($base_class_obj_or_id instanceof $className) {
5827
-            $model_object = $base_class_obj_or_id;
5828
-        } else {
5829
-            $primary_key_field = $this->get_primary_key_field();
5830
-            if (
5831
-                $primary_key_field instanceof EE_Primary_Key_Int_Field
5832
-                && (
5833
-                    is_int($base_class_obj_or_id)
5834
-                    || is_string($base_class_obj_or_id)
5835
-                )
5836
-            ) {
5837
-                // assume it's an ID.
5838
-                // either a proper integer or a string representing an integer (eg "101" instead of 101)
5839
-                $model_object = $this->get_one_by_ID($base_class_obj_or_id);
5840
-            } elseif (
5841
-                $primary_key_field instanceof EE_Primary_Key_String_Field
5842
-                && is_string($base_class_obj_or_id)
5843
-            ) {
5844
-                // assume its a string representation of the object
5845
-                $model_object = $this->get_one_by_ID($base_class_obj_or_id);
5846
-            } else {
5847
-                throw new EE_Error(
5848
-                    sprintf(
5849
-                        esc_html__(
5850
-                            "'%s' is neither an object of type %s, nor an ID! Its full value is '%s'",
5851
-                            'event_espresso'
5852
-                        ),
5853
-                        $base_class_obj_or_id,
5854
-                        $this->_get_class_name(),
5855
-                        print_r($base_class_obj_or_id, true)
5856
-                    )
5857
-                );
5858
-            }
5859
-        }
5860
-        if ($ensure_is_in_db && $model_object->ID() !== null) {
5861
-            $model_object->save();
5862
-        }
5863
-        return $model_object;
5864
-    }
5865
-
5866
-
5867
-    /**
5868
-     * Similar to ensure_is_obj(), this method makes sure $base_class_obj_or_id
5869
-     * is a value of the this model's primary key. If it's an EE_Base_Class child,
5870
-     * returns it ID.
5871
-     *
5872
-     * @param EE_Base_Class|int|string $base_class_obj_or_id
5873
-     * @return int|string depending on the type of this model object's ID
5874
-     * @throws EE_Error
5875
-     * @throws ReflectionException
5876
-     */
5877
-    public function ensure_is_ID($base_class_obj_or_id)
5878
-    {
5879
-        $className = $this->_get_class_name();
5880
-        if ($base_class_obj_or_id instanceof $className) {
5881
-            /** @var $base_class_obj_or_id EE_Base_Class */
5882
-            $id = $base_class_obj_or_id->ID();
5883
-        } elseif (is_int($base_class_obj_or_id)) {
5884
-            // assume it's an ID
5885
-            $id = $base_class_obj_or_id;
5886
-        } elseif (is_string($base_class_obj_or_id)) {
5887
-            // assume its a string representation of the object
5888
-            $id = $base_class_obj_or_id;
5889
-        } else {
5890
-            throw new EE_Error(
5891
-                sprintf(
5892
-                    esc_html__(
5893
-                        "'%s' is neither an object of type %s, nor an ID! Its full value is '%s'",
5894
-                        'event_espresso'
5895
-                    ),
5896
-                    $base_class_obj_or_id,
5897
-                    $this->_get_class_name(),
5898
-                    print_r($base_class_obj_or_id, true)
5899
-                )
5900
-            );
5901
-        }
5902
-        return $id;
5903
-    }
5904
-
5905
-
5906
-    /**
5907
-     * Sets whether the values passed to the model (eg, values in WHERE, values in INSERT, UPDATE, etc)
5908
-     * have already been ran through the appropriate model field's prepare_for_use_in_db method. IE, they have
5909
-     * been sanitized and converted into the appropriate domain.
5910
-     * Usually the only place you'll want to change the default (which is to assume values have NOT been sanitized by
5911
-     * the model object/model field) is when making a method call from WITHIN a model object, which has direct access
5912
-     * to its sanitized values. Note: after changing this setting, you should set it back to its previous value (using
5913
-     * get_assumption_concerning_values_already_prepared_by_model_object()) eg.
5914
-     * $EVT = EEM_Event::instance(); $old_setting =
5915
-     * $EVT->get_assumption_concerning_values_already_prepared_by_model_object();
5916
-     * $EVT->assume_values_already_prepared_by_model_object(true);
5917
-     * $EVT->update(array('foo'=>'bar'),array(array('foo'=>'monkey')));
5918
-     * $EVT->assume_values_already_prepared_by_model_object($old_setting);
5919
-     *
5920
-     * @param int $values_already_prepared like one of the constants on EEM_Base
5921
-     * @return void
5922
-     */
5923
-    public function assume_values_already_prepared_by_model_object(
5924
-        $values_already_prepared = self::not_prepared_by_model_object
5925
-    ) {
5926
-        $this->_values_already_prepared_by_model_object = $values_already_prepared;
5927
-    }
5928
-
5929
-
5930
-    /**
5931
-     * Read comments for assume_values_already_prepared_by_model_object()
5932
-     *
5933
-     * @return int
5934
-     */
5935
-    public function get_assumption_concerning_values_already_prepared_by_model_object()
5936
-    {
5937
-        return $this->_values_already_prepared_by_model_object;
5938
-    }
5939
-
5940
-
5941
-    /**
5942
-     * Gets all the indexes on this model
5943
-     *
5944
-     * @return EE_Index[]
5945
-     */
5946
-    public function indexes()
5947
-    {
5948
-        return $this->_indexes;
5949
-    }
5950
-
5951
-
5952
-    /**
5953
-     * Gets all the Unique Indexes on this model
5954
-     *
5955
-     * @return EE_Unique_Index[]
5956
-     */
5957
-    public function unique_indexes()
5958
-    {
5959
-        $unique_indexes = [];
5960
-        foreach ($this->_indexes as $name => $index) {
5961
-            if ($index instanceof EE_Unique_Index) {
5962
-                $unique_indexes [ $name ] = $index;
5963
-            }
5964
-        }
5965
-        return $unique_indexes;
5966
-    }
5967
-
5968
-
5969
-    /**
5970
-     * Gets all the fields which, when combined, make the primary key.
5971
-     * This is usually just an array with 1 element (the primary key), but in cases
5972
-     * where there is no primary key, it's a combination of fields as defined
5973
-     * on a primary index
5974
-     *
5975
-     * @return EE_Model_Field_Base[] indexed by the field's name
5976
-     * @throws EE_Error
5977
-     */
5978
-    public function get_combined_primary_key_fields()
5979
-    {
5980
-        foreach ($this->indexes() as $index) {
5981
-            if ($index instanceof EE_Primary_Key_Index) {
5982
-                return $index->fields();
5983
-            }
5984
-        }
5985
-        return [$this->primary_key_name() => $this->get_primary_key_field()];
5986
-    }
5987
-
5988
-
5989
-    /**
5990
-     * Used to build a primary key string (when the model has no primary key),
5991
-     * which can be used a unique string to identify this model object.
5992
-     *
5993
-     * @param array $fields_n_values keys are field names, values are their values.
5994
-     *                               Note: if you have results from `EEM_Base::get_all_wpdb_results()`, you need to
5995
-     *                               run it through `EEM_Base::deduce_fields_n_values_from_cols_n_values()`
5996
-     *                               before passing it to this function (that will convert it from columns-n-values
5997
-     *                               to field-names-n-values).
5998
-     * @return string
5999
-     * @throws EE_Error
6000
-     */
6001
-    public function get_index_primary_key_string($fields_n_values)
6002
-    {
6003
-        $cols_n_values_for_primary_key_index = array_intersect_key(
6004
-            $fields_n_values,
6005
-            $this->get_combined_primary_key_fields()
6006
-        );
6007
-        return http_build_query($cols_n_values_for_primary_key_index);
6008
-    }
6009
-
6010
-
6011
-    /**
6012
-     * Gets the field values from the primary key string
6013
-     *
6014
-     * @param string $index_primary_key_string
6015
-     * @return null|array
6016
-     * @throws EE_Error
6017
-     * @see EEM_Base::get_combined_primary_key_fields() and EEM_Base::get_index_primary_key_string()
6018
-     */
6019
-    public function parse_index_primary_key_string($index_primary_key_string)
6020
-    {
6021
-        $key_fields = $this->get_combined_primary_key_fields();
6022
-        // check all of them are in the $id
6023
-        $key_vals_in_combined_pk = [];
6024
-        parse_str($index_primary_key_string, $key_vals_in_combined_pk);
6025
-        foreach ($key_fields as $key_field_name => $field_obj) {
6026
-            if (! isset($key_vals_in_combined_pk[ $key_field_name ])) {
6027
-                return null;
6028
-            }
6029
-        }
6030
-        return $key_vals_in_combined_pk;
6031
-    }
6032
-
6033
-
6034
-    /**
6035
-     * verifies that an array of key-value pairs for model fields has a key
6036
-     * for each field comprising the primary key index
6037
-     *
6038
-     * @param array $key_vals
6039
-     * @return boolean
6040
-     * @throws EE_Error
6041
-     */
6042
-    public function has_all_combined_primary_key_fields($key_vals)
6043
-    {
6044
-        $keys_it_should_have = array_keys($this->get_combined_primary_key_fields());
6045
-        foreach ($keys_it_should_have as $key) {
6046
-            if (! isset($key_vals[ $key ])) {
6047
-                return false;
6048
-            }
6049
-        }
6050
-        return true;
6051
-    }
6052
-
6053
-
6054
-    /**
6055
-     * Finds all model objects in the DB that appear to be a copy of $model_object_or_attributes_array.
6056
-     * We consider something to be a copy if all the attributes match (except the ID, of course).
6057
-     *
6058
-     * @param array|EE_Base_Class $model_object_or_attributes_array If its an array, it's field-value pairs
6059
-     * @param array               $query_params                     @see
6060
-     *                                                              https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
6061
-     * @throws EE_Error
6062
-     * @throws ReflectionException
6063
-     * @return EE_Base_Class[] Array keys are object IDs (if there is a primary key on the model. if not, numerically
6064
-     *                                                              indexed)
6065
-     */
6066
-    public function get_all_copies($model_object_or_attributes_array, $query_params = [])
6067
-    {
6068
-        if ($model_object_or_attributes_array instanceof EE_Base_Class) {
6069
-            $attributes_array = $model_object_or_attributes_array->model_field_array();
6070
-        } elseif (is_array($model_object_or_attributes_array)) {
6071
-            $attributes_array = $model_object_or_attributes_array;
6072
-        } else {
6073
-            throw new EE_Error(
6074
-                sprintf(
6075
-                    esc_html__(
6076
-                        "get_all_copies should be provided with either a model object or an array of field-value-pairs, but was given %s",
6077
-                        "event_espresso"
6078
-                    ),
6079
-                    $model_object_or_attributes_array
6080
-                )
6081
-            );
6082
-        }
6083
-        // even copies obviously won't have the same ID, so remove the primary key
6084
-        // from the WHERE conditions for finding copies (if there is a primary key, of course)
6085
-        if ($this->has_primary_key_field() && isset($attributes_array[ $this->primary_key_name() ])) {
6086
-            unset($attributes_array[ $this->primary_key_name() ]);
6087
-        }
6088
-        if (isset($query_params[0])) {
6089
-            $query_params[0] = array_merge($attributes_array, $query_params);
6090
-        } else {
6091
-            $query_params[0] = $attributes_array;
6092
-        }
6093
-        return $this->get_all($query_params);
6094
-    }
6095
-
6096
-
6097
-    /**
6098
-     * Gets the first copy we find. See get_all_copies for more details
6099
-     *
6100
-     * @param mixed EE_Base_Class | array        $model_object_or_attributes_array
6101
-     * @param array $query_params
6102
-     * @return EE_Base_Class
6103
-     * @throws EE_Error
6104
-     * @throws ReflectionException
6105
-     */
6106
-    public function get_one_copy($model_object_or_attributes_array, $query_params = [])
6107
-    {
6108
-        if (! is_array($query_params)) {
6109
-            EE_Error::doing_it_wrong(
6110
-                'EEM_Base::get_one_copy',
6111
-                sprintf(
6112
-                    esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
6113
-                    gettype($query_params)
6114
-                ),
6115
-                '4.6.0'
6116
-            );
6117
-            $query_params = [];
6118
-        }
6119
-        $query_params['limit'] = 1;
6120
-        $copies                = $this->get_all_copies($model_object_or_attributes_array, $query_params);
6121
-        if (is_array($copies)) {
6122
-            return array_shift($copies);
6123
-        }
6124
-        return null;
6125
-    }
6126
-
6127
-
6128
-    /**
6129
-     * Updates the item with the specified id. Ignores default query parameters because
6130
-     * we have specified the ID, and its assumed we KNOW what we're doing
6131
-     *
6132
-     * @param array      $fields_n_values keys are field names, values are their new values
6133
-     * @param int|string $id              the value of the primary key to update
6134
-     * @return int number of rows updated
6135
-     * @throws EE_Error
6136
-     * @throws ReflectionException
6137
-     */
6138
-    public function update_by_ID($fields_n_values, $id)
6139
-    {
6140
-        $query_params = [
6141
-            0                          => [$this->get_primary_key_field()->get_name() => $id],
6142
-            'default_where_conditions' => EEM_Base::default_where_conditions_others_only,
6143
-        ];
6144
-        return $this->update($fields_n_values, $query_params);
6145
-    }
6146
-
6147
-
6148
-    /**
6149
-     * Changes an operator which was supplied to the models into one usable in SQL
6150
-     *
6151
-     * @param string $operator_supplied
6152
-     * @return string an operator which can be used in SQL
6153
-     * @throws EE_Error
6154
-     */
6155
-    private function _prepare_operator_for_sql($operator_supplied)
6156
-    {
6157
-        $sql_operator = $this->_valid_operators[ $operator_supplied ] ?? null;
6158
-        if ($sql_operator) {
6159
-            return $sql_operator;
6160
-        }
6161
-        throw new EE_Error(
6162
-            sprintf(
6163
-                esc_html__(
6164
-                    "The operator '%s' is not in the list of valid operators: %s",
6165
-                    "event_espresso"
6166
-                ),
6167
-                $operator_supplied,
6168
-                implode(",", array_keys($this->_valid_operators))
6169
-            )
6170
-        );
6171
-    }
6172
-
6173
-
6174
-    /**
6175
-     * Gets the valid operators
6176
-     *
6177
-     * @return array keys are accepted strings, values are the SQL they are converted to
6178
-     */
6179
-    public function valid_operators()
6180
-    {
6181
-        return $this->_valid_operators;
6182
-    }
6183
-
6184
-
6185
-    /**
6186
-     * Gets the between-style operators (take 2 arguments).
6187
-     *
6188
-     * @return array keys are accepted strings, values are the SQL they are converted to
6189
-     */
6190
-    public function valid_between_style_operators()
6191
-    {
6192
-        return array_intersect(
6193
-            $this->valid_operators(),
6194
-            $this->_between_style_operators
6195
-        );
6196
-    }
6197
-
6198
-
6199
-    /**
6200
-     * Gets the "like"-style operators (take a single argument, but it may contain wildcards)
6201
-     *
6202
-     * @return array keys are accepted strings, values are the SQL they are converted to
6203
-     */
6204
-    public function valid_like_style_operators()
6205
-    {
6206
-        return array_intersect(
6207
-            $this->valid_operators(),
6208
-            $this->_like_style_operators
6209
-        );
6210
-    }
6211
-
6212
-
6213
-    /**
6214
-     * Gets the "in"-style operators
6215
-     *
6216
-     * @return array keys are accepted strings, values are the SQL they are converted to
6217
-     */
6218
-    public function valid_in_style_operators()
6219
-    {
6220
-        return array_intersect(
6221
-            $this->valid_operators(),
6222
-            $this->_in_style_operators
6223
-        );
6224
-    }
6225
-
6226
-
6227
-    /**
6228
-     * Gets the "null"-style operators (accept no arguments)
6229
-     *
6230
-     * @return array keys are accepted strings, values are the SQL they are converted to
6231
-     */
6232
-    public function valid_null_style_operators()
6233
-    {
6234
-        return array_intersect(
6235
-            $this->valid_operators(),
6236
-            $this->_null_style_operators
6237
-        );
6238
-    }
6239
-
6240
-
6241
-    /**
6242
-     * Gets an array where keys are the primary keys and values are their 'names'
6243
-     * (as determined by the model object's name() function, which is often overridden)
6244
-     *
6245
-     * @param array $query_params like get_all's
6246
-     * @return string[]
6247
-     * @throws EE_Error
6248
-     * @throws ReflectionException
6249
-     */
6250
-    public function get_all_names($query_params = [])
6251
-    {
6252
-        $objs  = $this->get_all($query_params);
6253
-        $names = [];
6254
-        foreach ($objs as $obj) {
6255
-            $names[ $obj->ID() ] = $obj->name();
6256
-        }
6257
-        return $names;
6258
-    }
6259
-
6260
-
6261
-    /**
6262
-     * Gets an array of primary keys from the model objects. If you acquired the model objects
6263
-     * using EEM_Base::get_all() you don't need to call this (and probably shouldn't because
6264
-     * this is duplicated effort and reduces efficiency) you would be better to use
6265
-     * array_keys() on $model_objects.
6266
-     *
6267
-     * @param \EE_Base_Class[] $model_objects
6268
-     * @param boolean          $filter_out_empty_ids if a model object has an ID of '' or 0, don't bother including it
6269
-     *                                               in the returned array
6270
-     * @return array
6271
-     * @throws EE_Error
6272
-     * @throws ReflectionException
6273
-     */
6274
-    public function get_IDs($model_objects, $filter_out_empty_ids = false)
6275
-    {
6276
-        if (! $this->has_primary_key_field()) {
6277
-            if (WP_DEBUG) {
6278
-                EE_Error::add_error(
6279
-                    esc_html__('Trying to get IDs from a model than has no primary key', 'event_espresso'),
6280
-                    __FILE__,
6281
-                    __FUNCTION__,
6282
-                    __LINE__
6283
-                );
6284
-            }
6285
-        }
6286
-        $IDs = [];
6287
-        foreach ($model_objects as $model_object) {
6288
-            $id = $model_object->ID();
6289
-            if (! $id) {
6290
-                if ($filter_out_empty_ids) {
6291
-                    continue;
6292
-                }
6293
-                if (WP_DEBUG) {
6294
-                    EE_Error::add_error(
6295
-                        esc_html__(
6296
-                            'Called %1$s on a model object that has no ID and so probably hasn\'t been saved to the database',
6297
-                            'event_espresso'
6298
-                        ),
6299
-                        __FILE__,
6300
-                        __FUNCTION__,
6301
-                        __LINE__
6302
-                    );
6303
-                }
6304
-            }
6305
-            $IDs[] = $id;
6306
-        }
6307
-        return $IDs;
6308
-    }
6309
-
6310
-
6311
-    /**
6312
-     * Returns the string used in capabilities relating to this model. If there
6313
-     * are no capabilities that relate to this model returns false
6314
-     *
6315
-     * @return string|false
6316
-     */
6317
-    public function cap_slug()
6318
-    {
6319
-        return apply_filters('FHEE__EEM_Base__cap_slug', $this->_caps_slug, $this);
6320
-    }
6321
-
6322
-
6323
-    /**
6324
-     * Returns the capability-restrictions array (@param string $context
6325
-     *
6326
-     * @return EE_Default_Where_Conditions[] indexed by associated capability
6327
-     * @throws EE_Error
6328
-     * @see EEM_Base::_cap_restrictions).
6329
-     *      If $context is provided (which should be set to one of EEM_Base::valid_cap_contexts())
6330
-     *      only returns the cap restrictions array in that context (ie, the array
6331
-     *      at that key)
6332
-     */
6333
-    public function cap_restrictions($context = EEM_Base::caps_read)
6334
-    {
6335
-        EEM_Base::verify_is_valid_cap_context($context);
6336
-        // check if we ought to run the restriction generator first
6337
-        if (
6338
-            isset($this->_cap_restriction_generators[ $context ])
6339
-            && $this->_cap_restriction_generators[ $context ] instanceof EE_Restriction_Generator_Base
6340
-            && ! $this->_cap_restriction_generators[ $context ]->has_generated_cap_restrictions()
6341
-        ) {
6342
-            $this->_cap_restrictions[ $context ] = array_merge(
6343
-                $this->_cap_restrictions[ $context ],
6344
-                $this->_cap_restriction_generators[ $context ]->generate_restrictions()
6345
-            );
6346
-        }
6347
-        // and make sure we've finalized the construction of each restriction
6348
-        foreach ($this->_cap_restrictions[ $context ] as $where_conditions_obj) {
6349
-            if ($where_conditions_obj instanceof EE_Default_Where_Conditions) {
6350
-                $where_conditions_obj->_finalize_construct($this);
6351
-            }
6352
-        }
6353
-        return $this->_cap_restrictions[ $context ];
6354
-    }
6355
-
6356
-
6357
-    /**
6358
-     * Indicating whether or not this model thinks its a wp core model
6359
-     *
6360
-     * @return boolean
6361
-     */
6362
-    public function is_wp_core_model()
6363
-    {
6364
-        return $this->_wp_core_model;
6365
-    }
6366
-
6367
-
6368
-    /**
6369
-     * Gets all the caps that are missing which impose a restriction on
6370
-     * queries made in this context
6371
-     *
6372
-     * @param string $context one of EEM_Base::caps_ constants
6373
-     * @return EE_Default_Where_Conditions[] indexed by capability name
6374
-     * @throws EE_Error
6375
-     */
6376
-    public function caps_missing($context = EEM_Base::caps_read)
6377
-    {
6378
-        $missing_caps     = [];
6379
-        $cap_restrictions = $this->cap_restrictions($context);
6380
-        foreach ($cap_restrictions as $cap => $restriction_if_no_cap) {
6381
-            if (
6382
-                ! EE_Capabilities::instance()
6383
-                                 ->current_user_can($cap, $this->get_this_model_name() . '_model_applying_caps')
6384
-            ) {
6385
-                $missing_caps[ $cap ] = $restriction_if_no_cap;
6386
-            }
6387
-        }
6388
-        return $missing_caps;
6389
-    }
6390
-
6391
-
6392
-    /**
6393
-     * Gets the mapping from capability contexts to action strings used in capability names
6394
-     *
6395
-     * @return array keys are one of EEM_Base::valid_cap_contexts(), and values are usually
6396
-     * one of 'read', 'edit', or 'delete'
6397
-     */
6398
-    public function cap_contexts_to_cap_action_map()
6399
-    {
6400
-        return apply_filters(
6401
-            'FHEE__EEM_Base__cap_contexts_to_cap_action_map',
6402
-            $this->_cap_contexts_to_cap_action_map,
6403
-            $this
6404
-        );
6405
-    }
6406
-
6407
-
6408
-    /**
6409
-     * Gets the action string for the specified capability context
6410
-     *
6411
-     * @param string $context
6412
-     * @return string one of EEM_Base::cap_contexts_to_cap_action_map() values
6413
-     * @throws EE_Error
6414
-     */
6415
-    public function cap_action_for_context($context)
6416
-    {
6417
-        $mapping = $this->cap_contexts_to_cap_action_map();
6418
-        if (isset($mapping[ $context ])) {
6419
-            return $mapping[ $context ];
6420
-        }
6421
-        if ($action = apply_filters('FHEE__EEM_Base__cap_action_for_context', null, $this, $mapping, $context)) {
6422
-            return $action;
6423
-        }
6424
-        throw new EE_Error(
6425
-            sprintf(
6426
-                esc_html__(
6427
-                    'Cannot find capability restrictions for context "%1$s", allowed values are:%2$s',
6428
-                    'event_espresso'
6429
-                ),
6430
-                $context,
6431
-                implode(',', array_keys($this->cap_contexts_to_cap_action_map()))
6432
-            )
6433
-        );
6434
-    }
6435
-
6436
-
6437
-    /**
6438
-     * Returns all the capability contexts which are valid when querying models
6439
-     *
6440
-     * @return array
6441
-     */
6442
-    public static function valid_cap_contexts(): array
6443
-    {
6444
-        return (array) apply_filters(
6445
-            'FHEE__EEM_Base__valid_cap_contexts',
6446
-            [
6447
-                self::caps_read,
6448
-                self::caps_read_admin,
6449
-                self::caps_edit,
6450
-                self::caps_delete,
6451
-            ]
6452
-        );
6453
-    }
6454
-
6455
-
6456
-    /**
6457
-     * Returns all valid options for 'default_where_conditions'
6458
-     *
6459
-     * @return array
6460
-     */
6461
-    public static function valid_default_where_conditions(): array
6462
-    {
6463
-        return [
6464
-            EEM_Base::default_where_conditions_all,
6465
-            EEM_Base::default_where_conditions_this_only,
6466
-            EEM_Base::default_where_conditions_others_only,
6467
-            EEM_Base::default_where_conditions_minimum_all,
6468
-            EEM_Base::default_where_conditions_minimum_others,
6469
-            EEM_Base::default_where_conditions_none,
6470
-        ];
6471
-    }
6472
-
6473
-    // public static function default_where_conditions_full
6474
-
6475
-
6476
-    /**
6477
-     * Verifies $context is one of EEM_Base::valid_cap_contexts(), if not it throws an exception
6478
-     *
6479
-     * @param string $context
6480
-     * @return bool
6481
-     * @throws EE_Error
6482
-     */
6483
-    public static function verify_is_valid_cap_context($context): bool
6484
-    {
6485
-        $valid_cap_contexts = EEM_Base::valid_cap_contexts();
6486
-        if (in_array($context, $valid_cap_contexts)) {
6487
-            return true;
6488
-        }
6489
-        throw new EE_Error(
6490
-            sprintf(
6491
-                esc_html__(
6492
-                    'Context "%1$s" passed into model "%2$s" is not a valid context. They are: %3$s',
6493
-                    'event_espresso'
6494
-                ),
6495
-                $context,
6496
-                'EEM_Base',
6497
-                implode(',', $valid_cap_contexts)
6498
-            )
6499
-        );
6500
-    }
6501
-
6502
-
6503
-    /**
6504
-     * Clears all the models field caches. This is only useful when a sub-class
6505
-     * might have added a field or something and these caches might be invalidated
6506
-     */
6507
-    protected function _invalidate_field_caches()
6508
-    {
6509
-        $this->_cache_foreign_key_to_fields = [];
6510
-        $this->_cached_fields               = null;
6511
-        $this->_cached_fields_non_db_only   = null;
6512
-    }
6513
-
6514
-
6515
-    /**
6516
-     * Gets the list of all the where query param keys that relate to logic instead of field names
6517
-     * (eg "and", "or", "not").
6518
-     *
6519
-     * @return array
6520
-     */
6521
-    public function logic_query_param_keys(): array
6522
-    {
6523
-        return $this->_logic_query_param_keys;
6524
-    }
6525
-
6526
-
6527
-    /**
6528
-     * Determines whether or not the where query param array key is for a logic query param.
6529
-     * Eg 'OR', 'not*', and 'and*because-i-say-so' should all return true, whereas
6530
-     * 'ATT_fname', 'EVT_name*not-you-or-me', and 'ORG_name' should return false
6531
-     *
6532
-     * @param $query_param_key
6533
-     * @return bool
6534
-     */
6535
-    public function is_logic_query_param_key($query_param_key): bool
6536
-    {
6537
-        foreach ($this->logic_query_param_keys() as $logic_query_param_key) {
6538
-            if (
6539
-                $query_param_key === $logic_query_param_key
6540
-                || strpos($query_param_key, $logic_query_param_key . '*') === 0
6541
-            ) {
6542
-                return true;
6543
-            }
6544
-        }
6545
-        return false;
6546
-    }
6547
-
6548
-
6549
-    /**
6550
-     * Returns true if this model has a password field on it (regardless of whether that password field has any content)
6551
-     *
6552
-     * @return boolean
6553
-     * @since 4.9.74.p
6554
-     */
6555
-    public function hasPassword(): bool
6556
-    {
6557
-        // if we don't yet know if there's a password field, find out and remember it for next time.
6558
-        if ($this->has_password_field === null) {
6559
-            $password_field           = $this->getPasswordField();
6560
-            $this->has_password_field = $password_field instanceof EE_Password_Field;
6561
-        }
6562
-        return $this->has_password_field;
6563
-    }
6564
-
6565
-
6566
-    /**
6567
-     * Returns the password field on this model, if there is one
6568
-     *
6569
-     * @return EE_Password_Field|null
6570
-     * @since 4.9.74.p
6571
-     */
6572
-    public function getPasswordField()
6573
-    {
6574
-        // if we definetely already know there is a password field or not (because has_password_field is true or false)
6575
-        // there's no need to search for it. If we don't know yet, then find out
6576
-        if ($this->has_password_field === null && $this->password_field === null) {
6577
-            $this->password_field = $this->get_a_field_of_type('EE_Password_Field');
6578
-        }
6579
-        // don't bother setting has_password_field because that's hasPassword()'s job.
6580
-        return $this->password_field;
6581
-    }
6582
-
6583
-
6584
-    /**
6585
-     * Returns the list of field (as EE_Model_Field_Bases) that are protected by the password
6586
-     *
6587
-     * @return EE_Model_Field_Base[]
6588
-     * @throws EE_Error
6589
-     * @since 4.9.74.p
6590
-     */
6591
-    public function getPasswordProtectedFields()
6592
-    {
6593
-        $password_field = $this->getPasswordField();
6594
-        $fields         = [];
6595
-        if ($password_field instanceof EE_Password_Field) {
6596
-            $field_names = $password_field->protectedFields();
6597
-            foreach ($field_names as $field_name) {
6598
-                $fields[ $field_name ] = $this->field_settings_for($field_name);
6599
-            }
6600
-        }
6601
-        return $fields;
6602
-    }
6603
-
6604
-
6605
-    /**
6606
-     * Checks if the current user can perform the requested action on this model
6607
-     *
6608
-     * @param string              $cap_to_check one of the array keys from _cap_contexts_to_cap_action_map
6609
-     * @param EE_Base_Class|array $model_obj_or_fields_n_values
6610
-     * @return bool
6611
-     * @throws EE_Error
6612
-     * @throws InvalidArgumentException
6613
-     * @throws InvalidDataTypeException
6614
-     * @throws InvalidInterfaceException
6615
-     * @throws ReflectionException
6616
-     * @throws UnexpectedEntityException
6617
-     * @since 4.9.74.p
6618
-     */
6619
-    public function currentUserCan($cap_to_check, $model_obj_or_fields_n_values)
6620
-    {
6621
-        if ($model_obj_or_fields_n_values instanceof EE_Base_Class) {
6622
-            $model_obj_or_fields_n_values = $model_obj_or_fields_n_values->model_field_array();
6623
-        }
6624
-        if (! is_array($model_obj_or_fields_n_values)) {
6625
-            throw new UnexpectedEntityException(
6626
-                $model_obj_or_fields_n_values,
6627
-                'EE_Base_Class',
6628
-                sprintf(
6629
-                    esc_html__(
6630
-                        '%1$s must be passed an `EE_Base_Class or an array of fields names with their values. You passed in something different.',
6631
-                        'event_espresso'
6632
-                    ),
6633
-                    __FUNCTION__
6634
-                )
6635
-            );
6636
-        }
6637
-        return $this->exists(
6638
-            $this->alter_query_params_to_restrict_by_ID(
6639
-                $this->get_index_primary_key_string($model_obj_or_fields_n_values),
6640
-                [
6641
-                    'default_where_conditions' => 'none',
6642
-                    'caps'                     => $cap_to_check,
6643
-                ]
6644
-            )
6645
-        );
6646
-    }
6647
-
6648
-
6649
-    /**
6650
-     * Returns the query param where conditions key to the password affecting this model.
6651
-     * Eg on EEM_Event this would just be "password", on EEM_Datetime this would be "Event.password", etc.
6652
-     *
6653
-     * @return null|string
6654
-     * @throws EE_Error
6655
-     * @throws InvalidArgumentException
6656
-     * @throws InvalidDataTypeException
6657
-     * @throws InvalidInterfaceException
6658
-     * @throws ModelConfigurationException
6659
-     * @throws ReflectionException
6660
-     * @since 4.9.74.p
6661
-     */
6662
-    public function modelChainAndPassword()
6663
-    {
6664
-        if ($this->model_chain_to_password === null) {
6665
-            throw new ModelConfigurationException(
6666
-                $this,
6667
-                esc_html_x(
6668
-                // @codingStandardsIgnoreStart
6669
-                    'Cannot exclude protected data because the model has not specified which model has the password.',
6670
-                    // @codingStandardsIgnoreEnd
6671
-                    '1: model name',
6672
-                    'event_espresso'
6673
-                )
6674
-            );
6675
-        }
6676
-        if ($this->model_chain_to_password === '') {
6677
-            $model_with_password = $this;
6678
-        } else {
6679
-            if ($pos_of_period = strrpos($this->model_chain_to_password, '.')) {
6680
-                $last_model_in_chain = substr($this->model_chain_to_password, $pos_of_period + 1);
6681
-            } else {
6682
-                $last_model_in_chain = $this->model_chain_to_password;
6683
-            }
6684
-            $model_with_password = EE_Registry::instance()->load_model($last_model_in_chain);
6685
-        }
6686
-
6687
-        $password_field = $model_with_password->getPasswordField();
6688
-        if ($password_field instanceof EE_Password_Field) {
6689
-            $password_field_name = $password_field->get_name();
6690
-        } else {
6691
-            throw new ModelConfigurationException(
6692
-                $this,
6693
-                sprintf(
6694
-                    esc_html_x(
6695
-                        'This model claims related model "%1$s" should have a password field on it, but none was found. The model relation chain is "%2$s"',
6696
-                        '1: model name, 2: special string',
6697
-                        'event_espresso'
6698
-                    ),
6699
-                    $model_with_password->get_this_model_name(),
6700
-                    $this->model_chain_to_password
6701
-                )
6702
-            );
6703
-        }
6704
-        return (
6705
-               $this->model_chain_to_password
6706
-                   ? $this->model_chain_to_password . '.'
6707
-                   : ''
6708
-               ) . $password_field_name;
6709
-    }
6710
-
6711
-
6712
-    /**
6713
-     * Returns true if there is a password on a related model which restricts access to some of this model's rows,
6714
-     * or if this model itself has a password affecting access to some of its other fields.
6715
-     *
6716
-     * @return boolean
6717
-     * @since 4.9.74.p
6718
-     */
6719
-    public function restrictedByRelatedModelPassword(): bool
6720
-    {
6721
-        return $this->model_chain_to_password !== null;
6722
-    }
3960
+		}
3961
+		return $null_friendly_where_conditions;
3962
+	}
3963
+
3964
+
3965
+	/**
3966
+	 * Uses the _default_where_conditions_strategy set during __construct() to get
3967
+	 * default where conditions on all get_all, update, and delete queries done by this model.
3968
+	 * Use the same syntax as client code. Eg on the Event model, use array('Event.EVT_post_type'=>'esp_event'),
3969
+	 * NOT array('Event_CPT.post_type'=>'esp_event').
3970
+	 *
3971
+	 * @param string $model_relation_path eg, path from Event to Payment is "Registration.Transaction.Payment."
3972
+	 * @return array @see
3973
+	 *                                    https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3974
+	 * @throws EE_Error
3975
+	 * @throws EE_Error
3976
+	 */
3977
+	private function _get_default_where_conditions($model_relation_path = '')
3978
+	{
3979
+		if ($this->_ignore_where_strategy) {
3980
+			return [];
3981
+		}
3982
+		return $this->_default_where_conditions_strategy->get_default_where_conditions($model_relation_path);
3983
+	}
3984
+
3985
+
3986
+	/**
3987
+	 * Uses the _minimum_where_conditions_strategy set during __construct() to get
3988
+	 * minimum where conditions on all get_all, update, and delete queries done by this model.
3989
+	 * Use the same syntax as client code. Eg on the Event model, use array('Event.EVT_post_type'=>'esp_event'),
3990
+	 * NOT array('Event_CPT.post_type'=>'esp_event').
3991
+	 * Similar to _get_default_where_conditions
3992
+	 *
3993
+	 * @param string $model_relation_path eg, path from Event to Payment is "Registration.Transaction.Payment."
3994
+	 * @return array @see
3995
+	 *                                    https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3996
+	 * @throws EE_Error
3997
+	 * @throws EE_Error
3998
+	 */
3999
+	protected function _get_minimum_where_conditions($model_relation_path = '')
4000
+	{
4001
+		if ($this->_ignore_where_strategy) {
4002
+			return [];
4003
+		}
4004
+		return $this->_minimum_where_conditions_strategy->get_default_where_conditions($model_relation_path);
4005
+	}
4006
+
4007
+
4008
+	/**
4009
+	 * Creates the string of SQL for the select part of a select query, everything behind SELECT and before FROM.
4010
+	 * Eg, "Event.post_id, Event.post_name,Event_Detail.EVT_ID..."
4011
+	 *
4012
+	 * @param EE_Model_Query_Info_Carrier $model_query_info
4013
+	 * @return string
4014
+	 * @throws EE_Error
4015
+	 */
4016
+	private function _construct_default_select_sql(EE_Model_Query_Info_Carrier $model_query_info)
4017
+	{
4018
+		$selects = $this->_get_columns_to_select_for_this_model();
4019
+		foreach (
4020
+			$model_query_info->get_model_names_included() as $model_relation_chain => $name_of_other_model_included
4021
+		) {
4022
+			$other_model_included = $this->get_related_model_obj($name_of_other_model_included);
4023
+			$other_model_selects  = $other_model_included->_get_columns_to_select_for_this_model($model_relation_chain);
4024
+			foreach ($other_model_selects as $key => $value) {
4025
+				$selects[] = $value;
4026
+			}
4027
+		}
4028
+		return implode(", ", $selects);
4029
+	}
4030
+
4031
+
4032
+	/**
4033
+	 * Gets an array of columns to select for this model, which are necessary for it to create its objects.
4034
+	 * So that's going to be the columns for all the fields on the model
4035
+	 *
4036
+	 * @param string $model_relation_chain like 'Question.Question_Group.Event'
4037
+	 * @return array numerically indexed, values are columns to select and rename, eg "Event.ID AS 'Event.ID'"
4038
+	 */
4039
+	public function _get_columns_to_select_for_this_model($model_relation_chain = '')
4040
+	{
4041
+		$fields                                       = $this->field_settings();
4042
+		$selects                                      = [];
4043
+		$table_alias_with_model_relation_chain_prefix =
4044
+			EE_Model_Parser::extract_table_alias_model_relation_chain_prefix(
4045
+				$model_relation_chain,
4046
+				$this->get_this_model_name()
4047
+			);
4048
+		foreach ($fields as $field_obj) {
4049
+			$selects[] = $table_alias_with_model_relation_chain_prefix
4050
+						 . $field_obj->get_table_alias()
4051
+						 . "."
4052
+						 . $field_obj->get_table_column()
4053
+						 . " AS '"
4054
+						 . $table_alias_with_model_relation_chain_prefix
4055
+						 . $field_obj->get_table_alias()
4056
+						 . "."
4057
+						 . $field_obj->get_table_column()
4058
+						 . "'";
4059
+		}
4060
+		// make sure we are also getting the PKs of each table
4061
+		$tables = $this->get_tables();
4062
+		if (count($tables) > 1) {
4063
+			foreach ($tables as $table_obj) {
4064
+				$qualified_pk_column = $table_alias_with_model_relation_chain_prefix
4065
+									   . $table_obj->get_fully_qualified_pk_column();
4066
+				if (! in_array($qualified_pk_column, $selects)) {
4067
+					$selects[] = "$qualified_pk_column AS '$qualified_pk_column'";
4068
+				}
4069
+			}
4070
+		}
4071
+		return $selects;
4072
+	}
4073
+
4074
+
4075
+	/**
4076
+	 * Given a $query_param like 'Registration.Transaction.TXN_ID', pops off 'Registration.',
4077
+	 * gets the join statement for it; gets the data types for it; and passes the remaining 'Transaction.TXN_ID'
4078
+	 * onto its related Transaction object to do the same. Returns an EE_Join_And_Data_Types object which contains the
4079
+	 * SQL for joining, and the data types
4080
+	 *
4081
+	 * @param null|string                 $original_query_param
4082
+	 * @param string                      $query_param          like Registration.Transaction.TXN_ID
4083
+	 * @param EE_Model_Query_Info_Carrier $passed_in_query_info
4084
+	 * @param string                      $query_param_type     like Registration.Transaction.TXN_ID
4085
+	 *                                                          or 'PAY_ID'. Otherwise, we don't expect there to be a
4086
+	 *                                                          column name. We only want model names, eg 'Event.Venue'
4087
+	 *                                                          or 'Registration's
4088
+	 * @param string                      $original_query_param what it originally was (eg
4089
+	 *                                                          Registration.Transaction.TXN_ID). If null, we assume it
4090
+	 *                                                          matches $query_param
4091
+	 * @return void only modifies the EEM_Related_Model_Info_Carrier passed into it
4092
+	 * @throws EE_Error
4093
+	 */
4094
+	private function _extract_related_model_info_from_query_param(
4095
+		$query_param,
4096
+		EE_Model_Query_Info_Carrier $passed_in_query_info,
4097
+		$query_param_type,
4098
+		$original_query_param = null
4099
+	) {
4100
+		if ($original_query_param === null) {
4101
+			$original_query_param = $query_param;
4102
+		}
4103
+		$query_param = $this->_remove_stars_and_anything_after_from_condition_query_param_key($query_param);
4104
+		// check to see if we have a field on this model
4105
+		$this_model_fields = $this->field_settings(true);
4106
+		if (array_key_exists($query_param, $this_model_fields)) {
4107
+			$field_is_allowed = in_array(
4108
+				$query_param_type,
4109
+				[0, 'where', 'having', 'order_by', 'group_by', 'order', 'custom_selects'],
4110
+				true
4111
+			);
4112
+			if ($field_is_allowed) {
4113
+				return;
4114
+			}
4115
+			throw new EE_Error(
4116
+				sprintf(
4117
+					esc_html__(
4118
+						"Using a field name (%s) on model %s is not allowed on this query param type '%s'. Original query param was %s",
4119
+						"event_espresso"
4120
+					),
4121
+					$query_param,
4122
+					get_class($this),
4123
+					$query_param_type,
4124
+					$original_query_param
4125
+				)
4126
+			);
4127
+		}
4128
+		// check if this is a special logic query param
4129
+		if (in_array($query_param, $this->_logic_query_param_keys, true)) {
4130
+			$operator_is_allowed = in_array($query_param_type, ['where', 'having', 0, 'custom_selects'], true);
4131
+			if ($operator_is_allowed) {
4132
+				return;
4133
+			}
4134
+			throw new EE_Error(
4135
+				sprintf(
4136
+					esc_html__(
4137
+						'Logic query params ("%1$s") are being used incorrectly with the following query param ("%2$s") on model %3$s. %4$sAdditional Info:%4$s%5$s',
4138
+						'event_espresso'
4139
+					),
4140
+					implode('", "', $this->_logic_query_param_keys),
4141
+					$query_param,
4142
+					get_class($this),
4143
+					'<br />',
4144
+					"\t"
4145
+					. ' $passed_in_query_info = <pre>'
4146
+					. print_r($passed_in_query_info, true)
4147
+					. '</pre>'
4148
+					. "\n\t"
4149
+					. ' $query_param_type = '
4150
+					. $query_param_type
4151
+					. "\n\t"
4152
+					. ' $original_query_param = '
4153
+					. $original_query_param
4154
+				)
4155
+			);
4156
+		}
4157
+		// check if it's a custom selection
4158
+		if (
4159
+			$this->_custom_selections instanceof CustomSelects
4160
+			&& in_array($query_param, $this->_custom_selections->columnAliases(), true)
4161
+		) {
4162
+			return;
4163
+		}
4164
+		// check if has a model name at the beginning
4165
+		// and
4166
+		// check if it's a field on a related model
4167
+		if (
4168
+			$this->extractJoinModelFromQueryParams(
4169
+				$passed_in_query_info,
4170
+				$query_param,
4171
+				$original_query_param,
4172
+				$query_param_type
4173
+			)
4174
+		) {
4175
+			return;
4176
+		}
4177
+
4178
+		// ok so $query_param didn't start with a model name
4179
+		// and we previously confirmed it wasn't a logic query param or field on the current model
4180
+		// it's wack, that's what it is
4181
+		throw new EE_Error(
4182
+			sprintf(
4183
+				esc_html__(
4184
+					"There is no model named '%s' related to %s. Query param type is %s and original query param is %s",
4185
+					"event_espresso"
4186
+				),
4187
+				$query_param,
4188
+				get_class($this),
4189
+				$query_param_type,
4190
+				$original_query_param
4191
+			)
4192
+		);
4193
+	}
4194
+
4195
+
4196
+	/**
4197
+	 * Extracts any possible join model information from the provided possible_join_string.
4198
+	 * This method will read the provided $possible_join_string value and determine if there are any possible model
4199
+	 * join
4200
+	 * parts that should be added to the query.
4201
+	 *
4202
+	 * @param EE_Model_Query_Info_Carrier $query_info_carrier
4203
+	 * @param string                      $possible_join_string  Such as Registration.REG_ID, or Registration
4204
+	 * @param null|string                 $original_query_param
4205
+	 * @param string                      $query_parameter_type  The type for the source of the $possible_join_string
4206
+	 *                                                           ('where', 'order_by', 'group_by', 'custom_selects'
4207
+	 *                                                           etc.)
4208
+	 * @return bool  returns true if a join was added and false if not.
4209
+	 * @throws EE_Error
4210
+	 */
4211
+	private function extractJoinModelFromQueryParams(
4212
+		EE_Model_Query_Info_Carrier $query_info_carrier,
4213
+		$possible_join_string,
4214
+		$original_query_param,
4215
+		$query_parameter_type
4216
+	) {
4217
+		foreach ($this->_model_relations as $valid_related_model_name => $relation_obj) {
4218
+			if (strpos($possible_join_string, $valid_related_model_name . ".") === 0) {
4219
+				$this->_add_join_to_model($valid_related_model_name, $query_info_carrier, $original_query_param);
4220
+				$possible_join_string = substr($possible_join_string, strlen($valid_related_model_name . "."));
4221
+				if ($possible_join_string === '') {
4222
+					// nothing left to $query_param
4223
+					// we should actually end in a field name, not a model like this!
4224
+					throw new EE_Error(
4225
+						sprintf(
4226
+							esc_html__(
4227
+								"Query param '%s' (of type %s on model %s) shouldn't end on a period (.) ",
4228
+								"event_espresso"
4229
+							),
4230
+							$possible_join_string,
4231
+							$query_parameter_type,
4232
+							get_class($this),
4233
+							$valid_related_model_name
4234
+						)
4235
+					);
4236
+				}
4237
+				$related_model_obj = $this->get_related_model_obj($valid_related_model_name);
4238
+				$related_model_obj->_extract_related_model_info_from_query_param(
4239
+					$possible_join_string,
4240
+					$query_info_carrier,
4241
+					$query_parameter_type,
4242
+					$original_query_param
4243
+				);
4244
+				return true;
4245
+			}
4246
+			if ($possible_join_string === $valid_related_model_name) {
4247
+				$this->_add_join_to_model(
4248
+					$valid_related_model_name,
4249
+					$query_info_carrier,
4250
+					$original_query_param
4251
+				);
4252
+				return true;
4253
+			}
4254
+		}
4255
+		return false;
4256
+	}
4257
+
4258
+
4259
+	/**
4260
+	 * Extracts related models from Custom Selects and sets up any joins for those related models.
4261
+	 *
4262
+	 * @param EE_Model_Query_Info_Carrier $query_info_carrier
4263
+	 * @throws EE_Error
4264
+	 */
4265
+	private function extractRelatedModelsFromCustomSelects(EE_Model_Query_Info_Carrier $query_info_carrier)
4266
+	{
4267
+		if (
4268
+			$this->_custom_selections instanceof CustomSelects
4269
+			&& (
4270
+				$this->_custom_selections->type() === CustomSelects::TYPE_STRUCTURED
4271
+				|| $this->_custom_selections->type() == CustomSelects::TYPE_COMPLEX
4272
+			)
4273
+		) {
4274
+			$original_selects = $this->_custom_selections->originalSelects();
4275
+			foreach ($original_selects as $alias => $select_configuration) {
4276
+				$this->extractJoinModelFromQueryParams(
4277
+					$query_info_carrier,
4278
+					$select_configuration[0],
4279
+					$select_configuration[0],
4280
+					'custom_selects'
4281
+				);
4282
+			}
4283
+		}
4284
+	}
4285
+
4286
+
4287
+	/**
4288
+	 * Privately used by _extract_related_model_info_from_query_param to add a join to $model_name
4289
+	 * and store it on $passed_in_query_info
4290
+	 *
4291
+	 * @param string                      $model_name
4292
+	 * @param EE_Model_Query_Info_Carrier $passed_in_query_info
4293
+	 * @param string                      $original_query_param used to extract the relation chain between the queried
4294
+	 *                                                          model and $model_name. Eg, if we are querying Event,
4295
+	 *                                                          and are adding a join to 'Payment' with the original
4296
+	 *                                                          query param key
4297
+	 *                                                          'Registration.Transaction.Payment.PAY_amount', we want
4298
+	 *                                                          to extract 'Registration.Transaction.Payment', in case
4299
+	 *                                                          Payment wants to add default query params so that it
4300
+	 *                                                          will know what models to prepend onto its default query
4301
+	 *                                                          params or in case it wants to rename tables (in case
4302
+	 *                                                          there are multiple joins to the same table)
4303
+	 * @return void
4304
+	 * @throws EE_Error
4305
+	 */
4306
+	private function _add_join_to_model(
4307
+		$model_name,
4308
+		EE_Model_Query_Info_Carrier $passed_in_query_info,
4309
+		$original_query_param
4310
+	) {
4311
+		$relation_obj         = $this->related_settings_for($model_name);
4312
+		$model_relation_chain = EE_Model_Parser::extract_model_relation_chain($model_name, $original_query_param);
4313
+		// check if the relation is HABTM, because then we're essentially doing two joins
4314
+		// If so, join first to the JOIN table, and add its data types, and then continue as normal
4315
+		if ($relation_obj instanceof EE_HABTM_Relation) {
4316
+			$join_model_obj = $relation_obj->get_join_model();
4317
+			// replace the model specified with the join model for this relation chain, whi
4318
+			$relation_chain_to_join_model =
4319
+				EE_Model_Parser::replace_model_name_with_join_model_name_in_model_relation_chain(
4320
+					$model_name,
4321
+					$join_model_obj->get_this_model_name(),
4322
+					$model_relation_chain
4323
+				);
4324
+			$passed_in_query_info->merge(
4325
+				new EE_Model_Query_Info_Carrier(
4326
+					[$relation_chain_to_join_model => $join_model_obj->get_this_model_name()],
4327
+					$relation_obj->get_join_to_intermediate_model_statement($relation_chain_to_join_model)
4328
+				)
4329
+			);
4330
+		}
4331
+		// now just join to the other table pointed to by the relation object, and add its data types
4332
+		$passed_in_query_info->merge(
4333
+			new EE_Model_Query_Info_Carrier(
4334
+				[$model_relation_chain => $model_name],
4335
+				$relation_obj->get_join_statement($model_relation_chain)
4336
+			)
4337
+		);
4338
+	}
4339
+
4340
+
4341
+	/**
4342
+	 * Constructs SQL for where clause, like "WHERE Event.ID = 23 AND Transaction.amount > 100" etc.
4343
+	 *
4344
+	 * @param array $where_params @see
4345
+	 *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
4346
+	 * @return string of SQL
4347
+	 * @throws EE_Error
4348
+	 */
4349
+	private function _construct_where_clause($where_params)
4350
+	{
4351
+		$SQL = $this->_construct_condition_clause_recursive($where_params, ' AND ');
4352
+		if ($SQL) {
4353
+			return " WHERE " . $SQL;
4354
+		}
4355
+		return '';
4356
+	}
4357
+
4358
+
4359
+	/**
4360
+	 * Just like the _construct_where_clause, except prepends 'HAVING' instead of 'WHERE',
4361
+	 * and should be passed HAVING parameters, not WHERE parameters
4362
+	 *
4363
+	 * @param array $having_params
4364
+	 * @return string
4365
+	 * @throws EE_Error
4366
+	 */
4367
+	private function _construct_having_clause($having_params)
4368
+	{
4369
+		$SQL = $this->_construct_condition_clause_recursive($having_params, ' AND ');
4370
+		if ($SQL) {
4371
+			return " HAVING " . $SQL;
4372
+		}
4373
+		return '';
4374
+	}
4375
+
4376
+
4377
+	/**
4378
+	 * Used for creating nested WHERE conditions. Eg "WHERE ! (Event.ID = 3 OR ( Event_Meta.meta_key = 'bob' AND
4379
+	 * Event_Meta.meta_value = 'foo'))"
4380
+	 *
4381
+	 * @param array  $where_params @see
4382
+	 *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
4383
+	 * @param string $glue         joins each subclause together. Should really only be " AND " or " OR "...
4384
+	 * @return string of SQL
4385
+	 * @throws EE_Error
4386
+	 */
4387
+	private function _construct_condition_clause_recursive($where_params, $glue = ' AND')
4388
+	{
4389
+		$where_clauses = [];
4390
+		foreach ($where_params as $query_param => $op_and_value_or_sub_condition) {
4391
+			$query_param = $this->_remove_stars_and_anything_after_from_condition_query_param_key($query_param);
4392
+			if (in_array($query_param, $this->_logic_query_param_keys, true)) {
4393
+				switch ($query_param) {
4394
+					case 'not':
4395
+					case 'NOT':
4396
+						$where_clauses[] = "! ("
4397
+										   . $this->_construct_condition_clause_recursive(
4398
+								$op_and_value_or_sub_condition,
4399
+								$glue
4400
+							)
4401
+										   . ")";
4402
+						break;
4403
+					case 'and':
4404
+					case 'AND':
4405
+						$where_clauses[] = " ("
4406
+										   . $this->_construct_condition_clause_recursive(
4407
+								$op_and_value_or_sub_condition,
4408
+								' AND '
4409
+							)
4410
+										   . ")";
4411
+						break;
4412
+					case 'or':
4413
+					case 'OR':
4414
+						$where_clauses[] = " ("
4415
+										   . $this->_construct_condition_clause_recursive(
4416
+								$op_and_value_or_sub_condition,
4417
+								' OR '
4418
+							)
4419
+										   . ")";
4420
+						break;
4421
+				}
4422
+			} else {
4423
+				$field_obj = $this->_deduce_field_from_query_param($query_param);
4424
+				// if it's not a normal field, maybe it's a custom selection?
4425
+				if (! $field_obj) {
4426
+					if ($this->_custom_selections instanceof CustomSelects) {
4427
+						$field_obj = $this->_custom_selections->getDataTypeForAlias($query_param);
4428
+					} else {
4429
+						throw new EE_Error(
4430
+							sprintf(
4431
+								esc_html__(
4432
+									"%s is neither a valid model field name, nor a custom selection",
4433
+									"event_espresso"
4434
+								),
4435
+								$query_param
4436
+							)
4437
+						);
4438
+					}
4439
+				}
4440
+				$op_and_value_sql = $this->_construct_op_and_value($op_and_value_or_sub_condition, $field_obj);
4441
+				$where_clauses[]  = $this->_deduce_column_name_from_query_param($query_param) . SP . $op_and_value_sql;
4442
+			}
4443
+		}
4444
+		return $where_clauses
4445
+			? implode($glue, $where_clauses)
4446
+			: '';
4447
+	}
4448
+
4449
+
4450
+	/**
4451
+	 * Takes the input parameter and extract the table name (alias) and column name
4452
+	 *
4453
+	 * @param string $query_param like Registration.Transaction.TXN_ID, Event.Datetime.start_time, or REG_ID
4454
+	 * @return string table alias and column name for SQL, eg "Transaction.TXN_ID"
4455
+	 * @throws EE_Error
4456
+	 */
4457
+	private function _deduce_column_name_from_query_param($query_param)
4458
+	{
4459
+		$field = $this->_deduce_field_from_query_param($query_param);
4460
+		if ($field) {
4461
+			$table_alias_prefix = EE_Model_Parser::extract_table_alias_model_relation_chain_from_query_param(
4462
+				$field->get_model_name(),
4463
+				$query_param
4464
+			);
4465
+			return $table_alias_prefix . $field->get_qualified_column();
4466
+		}
4467
+		if (
4468
+			$this->_custom_selections instanceof CustomSelects
4469
+			&& in_array($query_param, $this->_custom_selections->columnAliases(), true)
4470
+		) {
4471
+			// maybe it's custom selection item?
4472
+			// if so, just use it as the "column name"
4473
+			return $query_param;
4474
+		}
4475
+		$custom_select_aliases = $this->_custom_selections instanceof CustomSelects
4476
+			? implode(',', $this->_custom_selections->columnAliases())
4477
+			: '';
4478
+		throw new EE_Error(
4479
+			sprintf(
4480
+				esc_html__(
4481
+					"%s is not a valid field on this model, nor a custom selection (%s)",
4482
+					"event_espresso"
4483
+				),
4484
+				$query_param,
4485
+				$custom_select_aliases
4486
+			)
4487
+		);
4488
+	}
4489
+
4490
+
4491
+	/**
4492
+	 * Removes the * and anything after it from the condition query param key. It is useful to add the * to condition
4493
+	 * query param keys (eg, 'OR*', 'EVT_ID') in order for the array keys to still be unique, so that they don't get
4494
+	 * overwritten Takes a string like 'Event.EVT_ID*', 'TXN_total**', 'OR*1st', and 'DTT_reg_start*foobar' to
4495
+	 * 'Event.EVT_ID', 'TXN_total', 'OR', and 'DTT_reg_start', respectively.
4496
+	 *
4497
+	 * @param string $condition_query_param_key
4498
+	 * @return string
4499
+	 */
4500
+	private function _remove_stars_and_anything_after_from_condition_query_param_key($condition_query_param_key)
4501
+	{
4502
+		$pos_of_star = strpos($condition_query_param_key, '*');
4503
+		if ($pos_of_star === false) {
4504
+			return $condition_query_param_key;
4505
+		}
4506
+		return substr($condition_query_param_key, 0, $pos_of_star);
4507
+	}
4508
+
4509
+
4510
+	/**
4511
+	 * creates the SQL for the operator and the value in a WHERE clause, eg "< 23" or "LIKE '%monkey%'"
4512
+	 *
4513
+	 * @param array|string               $op_and_value
4514
+	 * @param EE_Model_Field_Base|string $field_obj . If string, should be one of EEM_Base::_valid_wpdb_data_types
4515
+	 * @return string
4516
+	 * @throws EE_Error
4517
+	 */
4518
+	private function _construct_op_and_value($op_and_value, $field_obj)
4519
+	{
4520
+		$operator = '=';
4521
+		$value    = $op_and_value;
4522
+		if (is_array($op_and_value)) {
4523
+			$operator = isset($op_and_value[0])
4524
+				? $this->_prepare_operator_for_sql($op_and_value[0])
4525
+				: null;
4526
+			if (! $operator) {
4527
+				$php_array_like_string = [];
4528
+				foreach ($op_and_value as $key => $value) {
4529
+					$php_array_like_string[] = "$key=>$value";
4530
+				}
4531
+				throw new EE_Error(
4532
+					sprintf(
4533
+						esc_html__(
4534
+							"You setup a query parameter like you were going to specify an operator, but didn't. You provided '(%s)', but the operator should be at array key index 0 (eg array('>',32))",
4535
+							"event_espresso"
4536
+						),
4537
+						implode(",", $php_array_like_string)
4538
+					)
4539
+				);
4540
+			}
4541
+			$value = $op_and_value[1] ?? null;
4542
+		}
4543
+
4544
+		// check to see if the value is actually another field
4545
+		if (is_array($op_and_value) && isset($op_and_value[2]) && $op_and_value[2]) {
4546
+			return $operator . SP . $this->_deduce_column_name_from_query_param($value);
4547
+		}
4548
+		if (in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4549
+			// in this case, the value should be an array, or at least a comma-separated list
4550
+			// it will need to handle a little differently
4551
+			$cleaned_value = $this->_construct_in_value($value, $field_obj);
4552
+			// note: $cleaned_value has already been run through $wpdb->prepare()
4553
+			return $operator . SP . $cleaned_value;
4554
+		}
4555
+		if (in_array($operator, $this->valid_between_style_operators()) && is_array($value)) {
4556
+			// the value should be an array with count of two.
4557
+			if (count($value) !== 2) {
4558
+				throw new EE_Error(
4559
+					sprintf(
4560
+						esc_html__(
4561
+							"The '%s' operator must be used with an array of values and there must be exactly TWO values in that array.",
4562
+							'event_espresso'
4563
+						),
4564
+						"BETWEEN"
4565
+					)
4566
+				);
4567
+			}
4568
+			$cleaned_value = $this->_construct_between_value($value, $field_obj);
4569
+			return $operator . SP . $cleaned_value;
4570
+		}
4571
+		if (in_array($operator, $this->valid_null_style_operators())) {
4572
+			if ($value !== null) {
4573
+				throw new EE_Error(
4574
+					sprintf(
4575
+						esc_html__(
4576
+							"You attempted to give a value  (%s) while using a NULL-style operator (%s). That isn't valid",
4577
+							"event_espresso"
4578
+						),
4579
+						$value,
4580
+						$operator
4581
+					)
4582
+				);
4583
+			}
4584
+			return $operator;
4585
+		}
4586
+		if (in_array($operator, $this->valid_like_style_operators()) && ! is_array($value)) {
4587
+			// if the operator is 'LIKE', we want to allow percent signs (%) and not
4588
+			// remove other junk. So just treat it as a string.
4589
+			return $operator . SP . $this->_wpdb_prepare_using_field($value, '%s');
4590
+		}
4591
+		if (! in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4592
+			return $operator . SP . $this->_wpdb_prepare_using_field($value, $field_obj);
4593
+		}
4594
+		if (in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4595
+			throw new EE_Error(
4596
+				sprintf(
4597
+					esc_html__(
4598
+						"Operator '%s' must be used with an array of values, eg 'Registration.REG_ID' => array('%s',array(1,2,3))",
4599
+						'event_espresso'
4600
+					),
4601
+					$operator,
4602
+					$operator
4603
+				)
4604
+			);
4605
+		}
4606
+		if (! in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4607
+			throw new EE_Error(
4608
+				sprintf(
4609
+					esc_html__(
4610
+						"Operator '%s' must be used with a single value, not an array. Eg 'Registration.REG_ID => array('%s',23))",
4611
+						'event_espresso'
4612
+					),
4613
+					$operator,
4614
+					$operator
4615
+				)
4616
+			);
4617
+		}
4618
+		throw new EE_Error(
4619
+			sprintf(
4620
+				esc_html__(
4621
+					"It appears you've provided some totally invalid query parameters. Operator and value were:'%s', which isn't right at all",
4622
+					"event_espresso"
4623
+				),
4624
+				http_build_query($op_and_value)
4625
+			)
4626
+		);
4627
+	}
4628
+
4629
+
4630
+	/**
4631
+	 * Creates the operands to be used in a BETWEEN query, eg "'2014-12-31 20:23:33' AND '2015-01-23 12:32:54'"
4632
+	 *
4633
+	 * @param array                      $values
4634
+	 * @param EE_Model_Field_Base|string $field_obj if string, it should be the datatype to be used when querying, eg
4635
+	 *                                              '%s'
4636
+	 * @return string
4637
+	 * @throws EE_Error
4638
+	 */
4639
+	public function _construct_between_value($values, $field_obj)
4640
+	{
4641
+		$cleaned_values = [];
4642
+		foreach ($values as $value) {
4643
+			$cleaned_values[] = $this->_wpdb_prepare_using_field($value, $field_obj);
4644
+		}
4645
+		return $cleaned_values[0] . " AND " . $cleaned_values[1];
4646
+	}
4647
+
4648
+
4649
+	/**
4650
+	 * Takes an array or a comma-separated list of $values and cleans them
4651
+	 * according to $data_type using $wpdb->prepare, and then makes the list a
4652
+	 * string surrounded by ( and ). Eg, _construct_in_value(array(1,2,3),'%d') would
4653
+	 * return '(1,2,3)'; _construct_in_value("1,2,hack",'%d') would return '(1,2,1)' (assuming
4654
+	 * I'm right that a string, when interpreted as a digit, becomes a 1. It might become a 0)
4655
+	 *
4656
+	 * @param mixed                      $values    array or comma-separated string
4657
+	 * @param EE_Model_Field_Base|string $field_obj if string, it should be a wpdb data type like '%s', or '%d'
4658
+	 * @return string of SQL to follow an 'IN' or 'NOT IN' operator
4659
+	 * @throws EE_Error
4660
+	 */
4661
+	public function _construct_in_value($values, $field_obj)
4662
+	{
4663
+		$prepped = [];
4664
+		// check if the value is a CSV list
4665
+		if (is_string($values)) {
4666
+			// in which case, turn it into an array
4667
+			$values = explode(',', $values);
4668
+		}
4669
+		// make sure we only have one of each value in the list
4670
+		$values = array_unique($values);
4671
+		foreach ($values as $value) {
4672
+			$prepped[] = $this->_wpdb_prepare_using_field($value, $field_obj);
4673
+		}
4674
+		// we would just LOVE to leave $cleaned_values as an empty array, and return the value as "()",
4675
+		// but unfortunately that's invalid SQL. So instead we return a string which we KNOW will evaluate to be the empty set
4676
+		// which is effectively equivalent to returning "()". We don't return "(0)" because that only works for auto-incrementing columns
4677
+		if (empty($prepped)) {
4678
+			$all_fields  = $this->field_settings();
4679
+			$first_field = reset($all_fields);
4680
+			$main_table  = $this->_get_main_table();
4681
+			$prepped[]   = "SELECT {$first_field->get_table_column()} FROM {$main_table->get_table_name()} WHERE FALSE";
4682
+		}
4683
+		return '(' . implode(',', $prepped) . ')';
4684
+	}
4685
+
4686
+
4687
+	/**
4688
+	 * @param mixed                      $value
4689
+	 * @param EE_Model_Field_Base|string $field_obj if string it should be a wpdb data type like '%d'
4690
+	 * @return false|null|string
4691
+	 * @throws EE_Error
4692
+	 */
4693
+	private function _wpdb_prepare_using_field($value, $field_obj)
4694
+	{
4695
+		/** @type WPDB $wpdb */
4696
+		global $wpdb;
4697
+		if ($field_obj instanceof EE_Model_Field_Base) {
4698
+			return $wpdb->prepare(
4699
+				$field_obj->get_wpdb_data_type(),
4700
+				$this->_prepare_value_for_use_in_db($value, $field_obj)
4701
+			);
4702
+		} //$field_obj should really just be a data type
4703
+		if (! in_array($field_obj, $this->_valid_wpdb_data_types)) {
4704
+			throw new EE_Error(
4705
+				sprintf(
4706
+					esc_html__("%s is not a valid wpdb datatype. Valid ones are %s", "event_espresso"),
4707
+					$field_obj,
4708
+					implode(",", $this->_valid_wpdb_data_types)
4709
+				)
4710
+			);
4711
+		}
4712
+		return $wpdb->prepare($field_obj, $value);
4713
+	}
4714
+
4715
+
4716
+	/**
4717
+	 * Takes the input parameter and finds the model field that it indicates.
4718
+	 *
4719
+	 * @param string $query_param_name like Registration.Transaction.TXN_ID, Event.Datetime.start_time, or REG_ID
4720
+	 * @return EE_Model_Field_Base
4721
+	 * @throws EE_Error
4722
+	 */
4723
+	protected function _deduce_field_from_query_param($query_param_name)
4724
+	{
4725
+		// ok, now proceed with deducing which part is the model's name, and which is the field's name
4726
+		// which will help us find the database table and column
4727
+		$query_param_parts = explode(".", $query_param_name);
4728
+		if (empty($query_param_parts)) {
4729
+			throw new EE_Error(
4730
+				sprintf(
4731
+					esc_html__(
4732
+						"_extract_column_name is empty when trying to extract column and table name from %s",
4733
+						'event_espresso'
4734
+					),
4735
+					$query_param_name
4736
+				)
4737
+			);
4738
+		}
4739
+		$number_of_parts       = count($query_param_parts);
4740
+		$last_query_param_part = $query_param_parts[ count($query_param_parts) - 1 ];
4741
+		if ($number_of_parts === 1) {
4742
+			$field_name = $last_query_param_part;
4743
+			$model_obj  = $this;
4744
+		} else {// $number_of_parts >= 2
4745
+			// the last part is the column name, and there are only 2parts. therefore...
4746
+			$field_name = $last_query_param_part;
4747
+			$model_obj  = $this->get_related_model_obj($query_param_parts[ $number_of_parts - 2 ]);
4748
+		}
4749
+		try {
4750
+			return $model_obj->field_settings_for($field_name);
4751
+		} catch (EE_Error $e) {
4752
+			return null;
4753
+		}
4754
+	}
4755
+
4756
+
4757
+	/**
4758
+	 * Given a field's name (ie, a key in $this->field_settings()), uses the EE_Model_Field object to get the table's
4759
+	 * alias and column which corresponds to it
4760
+	 *
4761
+	 * @param string $field_name
4762
+	 * @return string
4763
+	 * @throws EE_Error
4764
+	 */
4765
+	public function _get_qualified_column_for_field($field_name)
4766
+	{
4767
+		$all_fields = $this->field_settings();
4768
+		$field      = $all_fields[ $field_name ] ?? false;
4769
+		if ($field) {
4770
+			return $field->get_qualified_column();
4771
+		}
4772
+		throw new EE_Error(
4773
+			sprintf(
4774
+				esc_html__(
4775
+					"There is no field titled %s on model %s. Either the query trying to use it is bad, or you need to add it to the list of fields on the model.",
4776
+					'event_espresso'
4777
+				),
4778
+				$field_name,
4779
+				get_class($this)
4780
+			)
4781
+		);
4782
+	}
4783
+
4784
+
4785
+	/**
4786
+	 * similar to \EEM_Base::_get_qualified_column_for_field() but returns an array with data for ALL fields.
4787
+	 * Example usage:
4788
+	 * EEM_Ticket::instance()->get_all_wpdb_results(
4789
+	 *      array(),
4790
+	 *      ARRAY_A,
4791
+	 *      EEM_Ticket::instance()->get_qualified_columns_for_all_fields()
4792
+	 *  );
4793
+	 * is equivalent to
4794
+	 *  EEM_Ticket::instance()->get_all_wpdb_results( array(), ARRAY_A, '*' );
4795
+	 * and
4796
+	 *  EEM_Event::instance()->get_all_wpdb_results(
4797
+	 *      array(
4798
+	 *          array(
4799
+	 *              'Datetime.Ticket.TKT_ID' => array( '<', 100 ),
4800
+	 *          ),
4801
+	 *          ARRAY_A,
4802
+	 *          implode(
4803
+	 *              ', ',
4804
+	 *              array_merge(
4805
+	 *                  EEM_Event::instance()->get_qualified_columns_for_all_fields( '', false ),
4806
+	 *                  EEM_Ticket::instance()->get_qualified_columns_for_all_fields( 'Datetime', false )
4807
+	 *              )
4808
+	 *          )
4809
+	 *      )
4810
+	 *  );
4811
+	 * selects rows from the database, selecting all the event and ticket columns, where the ticket ID is below 100
4812
+	 *
4813
+	 * @param string $model_relation_chain        the chain of models used to join between the model you want to query
4814
+	 *                                            and the one whose fields you are selecting for example: when querying
4815
+	 *                                            tickets model and selecting fields from the tickets model you would
4816
+	 *                                            leave this parameter empty, because no models are needed to join
4817
+	 *                                            between the queried model and the selected one. Likewise when
4818
+	 *                                            querying the datetime model and selecting fields from the tickets
4819
+	 *                                            model, it would also be left empty, because there is a direct
4820
+	 *                                            relation from datetimes to tickets, so no model is needed to join
4821
+	 *                                            them together. However, when querying from the event model and
4822
+	 *                                            selecting fields from the ticket model, you should provide the string
4823
+	 *                                            'Datetime', indicating that the event model must first join to the
4824
+	 *                                            datetime model in order to find its relation to ticket model.
4825
+	 *                                            Also, when querying from the venue model and selecting fields from
4826
+	 *                                            the ticket model, you should provide the string 'Event.Datetime',
4827
+	 *                                            indicating you need to join the venue model to the event model,
4828
+	 *                                            to the datetime model, in order to find its relation to the ticket
4829
+	 *                                            model. This string is used to deduce the prefix that gets added onto
4830
+	 *                                            the models' tables qualified columns
4831
+	 * @param bool   $return_string               if true, will return a string with qualified column names separated
4832
+	 *                                            by ', ' if false, will simply return a numerically indexed array of
4833
+	 *                                            qualified column names
4834
+	 * @return array|string
4835
+	 */
4836
+	public function get_qualified_columns_for_all_fields($model_relation_chain = '', $return_string = true)
4837
+	{
4838
+		$table_prefix      = str_replace('.', '__', $model_relation_chain) . (empty($model_relation_chain)
4839
+				? ''
4840
+				: '__');
4841
+		$qualified_columns = [];
4842
+		foreach ($this->field_settings() as $field_name => $field) {
4843
+			$qualified_columns[] = $table_prefix . $field->get_qualified_column();
4844
+		}
4845
+		return $return_string
4846
+			? implode(', ', $qualified_columns)
4847
+			: $qualified_columns;
4848
+	}
4849
+
4850
+
4851
+	/**
4852
+	 * constructs the select use on special limit joins
4853
+	 * NOTE: for now this has only been tested and will work when the  table alias is for the PRIMARY table. Although
4854
+	 * its setup so the select query will be setup on and just doing the special select join off of the primary table
4855
+	 * (as that is typically where the limits would be set).
4856
+	 *
4857
+	 * @param string       $table_alias The table the select is being built for
4858
+	 * @param mixed|string $limit       The limit for this select
4859
+	 * @return string                The final select join element for the query.
4860
+	 * @throws EE_Error
4861
+	 * @throws EE_Error
4862
+	 */
4863
+	public function _construct_limit_join_select($table_alias, $limit)
4864
+	{
4865
+		$SQL = '';
4866
+		foreach ($this->_tables as $table_obj) {
4867
+			if ($table_obj instanceof EE_Primary_Table) {
4868
+				$SQL .= $table_alias === $table_obj->get_table_alias()
4869
+					? $table_obj->get_select_join_limit($limit)
4870
+					: SP . $table_obj->get_table_name() . " AS " . $table_obj->get_table_alias() . SP;
4871
+			} elseif ($table_obj instanceof EE_Secondary_Table) {
4872
+				$SQL .= $table_alias === $table_obj->get_table_alias()
4873
+					? $table_obj->get_select_join_limit_join($limit)
4874
+					: SP . $table_obj->get_join_sql($table_alias) . SP;
4875
+			}
4876
+		}
4877
+		return $SQL;
4878
+	}
4879
+
4880
+
4881
+	/**
4882
+	 * Constructs the internal join if there are multiple tables, or simply the table's name and alias
4883
+	 * Eg "wp_post AS Event" or "wp_post AS Event INNER JOIN wp_postmeta Event_Meta ON Event.ID = Event_Meta.post_id"
4884
+	 *
4885
+	 * @return string SQL
4886
+	 * @throws EE_Error
4887
+	 */
4888
+	public function _construct_internal_join()
4889
+	{
4890
+		$SQL = $this->_get_main_table()->get_table_sql();
4891
+		$SQL .= $this->_construct_internal_join_to_table_with_alias($this->_get_main_table()->get_table_alias());
4892
+		return $SQL;
4893
+	}
4894
+
4895
+
4896
+	/**
4897
+	 * Constructs the SQL for joining all the tables on this model.
4898
+	 * Normally $alias should be the primary table's alias, but in cases where
4899
+	 * we have already joined to a secondary table (eg, the secondary table has a foreign key and is joined before the
4900
+	 * primary table) then we should provide that secondary table's alias. Eg, with $alias being the primary table's
4901
+	 * alias, this will construct SQL like:
4902
+	 * " INNER JOIN wp_esp_secondary_table AS Secondary_Table ON Primary_Table.pk = Secondary_Table.fk".
4903
+	 * With $alias being a secondary table's alias, this will construct SQL like:
4904
+	 * " INNER JOIN wp_esp_primary_table AS Primary_Table ON Primary_Table.pk = Secondary_Table.fk".
4905
+	 *
4906
+	 * @param string $alias_prefixed table alias to join to (this table should already be in the FROM SQL clause)
4907
+	 * @return string
4908
+	 * @throws EE_Error
4909
+	 * @throws EE_Error
4910
+	 */
4911
+	public function _construct_internal_join_to_table_with_alias($alias_prefixed)
4912
+	{
4913
+		$SQL               = '';
4914
+		$alias_sans_prefix = EE_Model_Parser::remove_table_alias_model_relation_chain_prefix($alias_prefixed);
4915
+		foreach ($this->_tables as $table_obj) {
4916
+			if ($table_obj instanceof EE_Secondary_Table) {// table is secondary table
4917
+				if ($alias_sans_prefix === $table_obj->get_table_alias()) {
4918
+					// so we're joining to this table, meaning the table is already in
4919
+					// the FROM statement, BUT the primary table isn't. So we want
4920
+					// to add the inverse join sql
4921
+					$SQL .= $table_obj->get_inverse_join_sql($alias_prefixed);
4922
+				} else {
4923
+					// just add a regular JOIN to this table from the primary table
4924
+					$SQL .= $table_obj->get_join_sql($alias_prefixed);
4925
+				}
4926
+			}// if it's a primary table, dont add any SQL. it should already be in the FROM statement
4927
+		}
4928
+		return $SQL;
4929
+	}
4930
+
4931
+
4932
+	/**
4933
+	 * Gets an array for storing all the data types on the next-to-be-executed-query.
4934
+	 * This should be a growing array of keys being table-columns (eg 'EVT_ID' and 'Event.EVT_ID'), and values being
4935
+	 * their data type (eg, '%s', '%d', etc)
4936
+	 *
4937
+	 * @return array
4938
+	 */
4939
+	public function _get_data_types()
4940
+	{
4941
+		$data_types = [];
4942
+		foreach ($this->field_settings() as $field_obj) {
4943
+			// $data_types[$field_obj->get_table_column()] = $field_obj->get_wpdb_data_type();
4944
+			/** @var $field_obj EE_Model_Field_Base */
4945
+			$data_types[ $field_obj->get_qualified_column() ] = $field_obj->get_wpdb_data_type();
4946
+		}
4947
+		return $data_types;
4948
+	}
4949
+
4950
+
4951
+	/**
4952
+	 * Gets the model object given the relation's name / model's name (eg, 'Event', 'Registration',etc. Always singular)
4953
+	 *
4954
+	 * @param string $model_name
4955
+	 * @return EEM_Base
4956
+	 * @throws EE_Error
4957
+	 */
4958
+	public function get_related_model_obj($model_name)
4959
+	{
4960
+		$model_classname = "EEM_" . $model_name;
4961
+		if (! class_exists($model_classname)) {
4962
+			throw new EE_Error(
4963
+				sprintf(
4964
+					esc_html__(
4965
+						"You specified a related model named %s in your query. No such model exists, if it did, it would have the classname %s",
4966
+						'event_espresso'
4967
+					),
4968
+					$model_name,
4969
+					$model_classname
4970
+				)
4971
+			);
4972
+		}
4973
+		return call_user_func($model_classname . "::instance");
4974
+	}
4975
+
4976
+
4977
+	/**
4978
+	 * Returns the array of EE_ModelRelations for this model.
4979
+	 *
4980
+	 * @return EE_Model_Relation_Base[]
4981
+	 */
4982
+	public function relation_settings()
4983
+	{
4984
+		return $this->_model_relations;
4985
+	}
4986
+
4987
+
4988
+	/**
4989
+	 * Gets all related models that this model BELONGS TO. Handy to know sometimes
4990
+	 * because without THOSE models, this model probably doesn't have much purpose.
4991
+	 * (Eg, without an event, datetimes have little purpose.)
4992
+	 *
4993
+	 * @return EE_Belongs_To_Relation[]
4994
+	 */
4995
+	public function belongs_to_relations()
4996
+	{
4997
+		$belongs_to_relations = [];
4998
+		foreach ($this->relation_settings() as $model_name => $relation_obj) {
4999
+			if ($relation_obj instanceof EE_Belongs_To_Relation) {
5000
+				$belongs_to_relations[ $model_name ] = $relation_obj;
5001
+			}
5002
+		}
5003
+		return $belongs_to_relations;
5004
+	}
5005
+
5006
+
5007
+	/**
5008
+	 * Returns the specified EE_Model_Relation, or throws an exception
5009
+	 *
5010
+	 * @param string $relation_name name of relation, key in $this->_relatedModels
5011
+	 * @return EE_Model_Relation_Base
5012
+	 * @throws EE_Error
5013
+	 */
5014
+	public function related_settings_for($relation_name)
5015
+	{
5016
+		$relatedModels = $this->relation_settings();
5017
+		if (! array_key_exists($relation_name, $relatedModels)) {
5018
+			throw new EE_Error(
5019
+				sprintf(
5020
+					esc_html__(
5021
+						'Cannot get %s related to %s. There is no model relation of that type. There is, however, %s...',
5022
+						'event_espresso'
5023
+					),
5024
+					$relation_name,
5025
+					$this->_get_class_name(),
5026
+					implode(', ', array_keys($relatedModels))
5027
+				)
5028
+			);
5029
+		}
5030
+		return $relatedModels[ $relation_name ];
5031
+	}
5032
+
5033
+
5034
+	/**
5035
+	 * A convenience method for getting a specific field's settings, instead of getting all field settings for all
5036
+	 * fields
5037
+	 *
5038
+	 * @param string  $fieldName
5039
+	 * @param boolean $include_db_only_fields
5040
+	 * @return EE_Model_Field_Base
5041
+	 * @throws EE_Error
5042
+	 */
5043
+	public function field_settings_for($fieldName, $include_db_only_fields = true)
5044
+	{
5045
+		$fieldSettings = $this->field_settings($include_db_only_fields);
5046
+		if (! array_key_exists($fieldName, $fieldSettings)) {
5047
+			throw new EE_Error(
5048
+				sprintf(
5049
+					esc_html__("There is no field/column '%s' on '%s'", 'event_espresso'),
5050
+					$fieldName,
5051
+					get_class($this)
5052
+				)
5053
+			);
5054
+		}
5055
+		return $fieldSettings[ $fieldName ];
5056
+	}
5057
+
5058
+
5059
+	/**
5060
+	 * Checks if this field exists on this model
5061
+	 *
5062
+	 * @param string $fieldName a key in the model's _field_settings array
5063
+	 * @return boolean
5064
+	 */
5065
+	public function has_field($fieldName)
5066
+	{
5067
+		$fieldSettings = $this->field_settings(true);
5068
+		if (isset($fieldSettings[ $fieldName ])) {
5069
+			return true;
5070
+		}
5071
+		return false;
5072
+	}
5073
+
5074
+
5075
+	/**
5076
+	 * Returns whether or not this model has a relation to the specified model
5077
+	 *
5078
+	 * @param string $relation_name possibly one of the keys in the relation_settings array
5079
+	 * @return boolean
5080
+	 */
5081
+	public function has_relation($relation_name)
5082
+	{
5083
+		$relations = $this->relation_settings();
5084
+		if (isset($relations[ $relation_name ])) {
5085
+			return true;
5086
+		}
5087
+		return false;
5088
+	}
5089
+
5090
+
5091
+	/**
5092
+	 * gets the field object of type 'primary_key' from the fieldsSettings attribute.
5093
+	 * Eg, on EE_Answer that would be ANS_ID field object
5094
+	 *
5095
+	 * @param $field_obj
5096
+	 * @return boolean
5097
+	 */
5098
+	public function is_primary_key_field($field_obj): bool
5099
+	{
5100
+		return $field_obj instanceof EE_Primary_Key_Field_Base;
5101
+	}
5102
+
5103
+
5104
+	/**
5105
+	 * gets the field object of type 'primary_key' from the fieldsSettings attribute.
5106
+	 * Eg, on EE_Answer that would be ANS_ID field object
5107
+	 *
5108
+	 * @return EE_Primary_Key_Field_Base
5109
+	 * @throws EE_Error
5110
+	 */
5111
+	public function get_primary_key_field()
5112
+	{
5113
+		if ($this->_primary_key_field === null) {
5114
+			foreach ($this->field_settings(true) as $field_obj) {
5115
+				if ($this->is_primary_key_field($field_obj)) {
5116
+					$this->_primary_key_field = $field_obj;
5117
+					break;
5118
+				}
5119
+			}
5120
+			if (! $this->_primary_key_field instanceof EE_Primary_Key_Field_Base) {
5121
+				throw new EE_Error(
5122
+					sprintf(
5123
+						esc_html__("There is no Primary Key defined on model %s", 'event_espresso'),
5124
+						get_class($this)
5125
+					)
5126
+				);
5127
+			}
5128
+		}
5129
+		return $this->_primary_key_field;
5130
+	}
5131
+
5132
+
5133
+	/**
5134
+	 * Returns whether or not not there is a primary key on this model.
5135
+	 * Internally does some caching.
5136
+	 *
5137
+	 * @return boolean
5138
+	 */
5139
+	public function has_primary_key_field()
5140
+	{
5141
+		if ($this->_has_primary_key_field === null) {
5142
+			try {
5143
+				$this->get_primary_key_field();
5144
+				$this->_has_primary_key_field = true;
5145
+			} catch (EE_Error $e) {
5146
+				$this->_has_primary_key_field = false;
5147
+			}
5148
+		}
5149
+		return $this->_has_primary_key_field;
5150
+	}
5151
+
5152
+
5153
+	/**
5154
+	 * Finds the first field of type $field_class_name.
5155
+	 *
5156
+	 * @param string $field_class_name class name of field that you want to find. Eg, EE_Datetime_Field,
5157
+	 *                                 EE_Foreign_Key_Field, etc
5158
+	 * @return EE_Model_Field_Base or null if none is found
5159
+	 */
5160
+	public function get_a_field_of_type($field_class_name)
5161
+	{
5162
+		foreach ($this->field_settings() as $field) {
5163
+			if ($field instanceof $field_class_name) {
5164
+				return $field;
5165
+			}
5166
+		}
5167
+		return null;
5168
+	}
5169
+
5170
+
5171
+	/**
5172
+	 * Gets a foreign key field pointing to model.
5173
+	 *
5174
+	 * @param string $model_name eg Event, Registration, not EEM_Event
5175
+	 * @return EE_Foreign_Key_Field_Base
5176
+	 * @throws EE_Error
5177
+	 */
5178
+	public function get_foreign_key_to($model_name)
5179
+	{
5180
+		if (! isset($this->_cache_foreign_key_to_fields[ $model_name ])) {
5181
+			foreach ($this->field_settings() as $field) {
5182
+				if (
5183
+					$field instanceof EE_Foreign_Key_Field_Base
5184
+					&& in_array($model_name, $field->get_model_names_pointed_to())
5185
+				) {
5186
+					$this->_cache_foreign_key_to_fields[ $model_name ] = $field;
5187
+					break;
5188
+				}
5189
+			}
5190
+			if (! isset($this->_cache_foreign_key_to_fields[ $model_name ])) {
5191
+				throw new EE_Error(
5192
+					sprintf(
5193
+						esc_html__(
5194
+							"There is no foreign key field pointing to model %s on model %s",
5195
+							'event_espresso'
5196
+						),
5197
+						$model_name,
5198
+						get_class($this)
5199
+					)
5200
+				);
5201
+			}
5202
+		}
5203
+		return $this->_cache_foreign_key_to_fields[ $model_name ];
5204
+	}
5205
+
5206
+
5207
+	/**
5208
+	 * Gets the table name (including $wpdb->prefix) for the table alias
5209
+	 *
5210
+	 * @param string $table_alias eg Event, Event_Meta, Registration, Transaction, but maybe
5211
+	 *                            a table alias with a model chain prefix, like 'Venue__Event_Venue___Event_Meta'.
5212
+	 *                            Either one works
5213
+	 * @return string
5214
+	 */
5215
+	public function get_table_for_alias($table_alias)
5216
+	{
5217
+		$table_alias_sans_model_relation_chain_prefix =
5218
+			EE_Model_Parser::remove_table_alias_model_relation_chain_prefix($table_alias);
5219
+		return $this->_tables[ $table_alias_sans_model_relation_chain_prefix ]->get_table_name();
5220
+	}
5221
+
5222
+
5223
+	/**
5224
+	 * Returns a flat array of all field son this model, instead of organizing them
5225
+	 * by table_alias as they are in the constructor.
5226
+	 *
5227
+	 * @param bool $include_db_only_fields flag indicating whether or not to include the db-only fields
5228
+	 * @return EE_Model_Field_Base[] where the keys are the field's name
5229
+	 */
5230
+	public function field_settings($include_db_only_fields = false)
5231
+	{
5232
+		if ($include_db_only_fields) {
5233
+			if ($this->_cached_fields === null) {
5234
+				$this->_cached_fields = [];
5235
+				foreach ($this->_fields as $fields_corresponding_to_table) {
5236
+					foreach ($fields_corresponding_to_table as $field_name => $field_obj) {
5237
+						$this->_cached_fields[ $field_name ] = $field_obj;
5238
+					}
5239
+				}
5240
+			}
5241
+			return $this->_cached_fields;
5242
+		}
5243
+		if ($this->_cached_fields_non_db_only === null) {
5244
+			$this->_cached_fields_non_db_only = [];
5245
+			foreach ($this->_fields as $fields_corresponding_to_table) {
5246
+				foreach ($fields_corresponding_to_table as $field_name => $field_obj) {
5247
+					/** @var $field_obj EE_Model_Field_Base */
5248
+					if (! $field_obj->is_db_only_field()) {
5249
+						$this->_cached_fields_non_db_only[ $field_name ] = $field_obj;
5250
+					}
5251
+				}
5252
+			}
5253
+		}
5254
+		return $this->_cached_fields_non_db_only;
5255
+	}
5256
+
5257
+
5258
+	/**
5259
+	 *        cycle though array of attendees and create objects out of each item
5260
+	 *
5261
+	 * @access        private
5262
+	 * @param array $rows        of results of $wpdb->get_results($query,ARRAY_A)
5263
+	 * @return EE_Base_Class[] array keys are primary keys (if there is a primary key on the model. if not,
5264
+	 *                           numerically indexed)
5265
+	 * @throws EE_Error
5266
+	 * @throws ReflectionException
5267
+	 */
5268
+	protected function _create_objects($rows = [])
5269
+	{
5270
+		$array_of_objects = [];
5271
+		if (empty($rows)) {
5272
+			return [];
5273
+		}
5274
+		$count_if_model_has_no_primary_key = 0;
5275
+		$has_primary_key                   = $this->has_primary_key_field();
5276
+		$primary_key_field                 = $has_primary_key
5277
+			? $this->get_primary_key_field()
5278
+			: null;
5279
+		foreach ((array) $rows as $row) {
5280
+			if (empty($row)) {
5281
+				// wp did its weird thing where it returns an array like array(0=>null), which is totally not helpful...
5282
+				return [];
5283
+			}
5284
+			// check if we've already set this object in the results array,
5285
+			// in which case there's no need to process it further (again)
5286
+			if ($has_primary_key) {
5287
+				$table_pk_value = $this->_get_column_value_with_table_alias_or_not(
5288
+					$row,
5289
+					$primary_key_field->get_qualified_column(),
5290
+					$primary_key_field->get_table_column()
5291
+				);
5292
+				if ($table_pk_value && isset($array_of_objects[ $table_pk_value ])) {
5293
+					continue;
5294
+				}
5295
+			}
5296
+			$classInstance = $this->instantiate_class_from_array_or_object($row);
5297
+			if (! $classInstance) {
5298
+				throw new EE_Error(
5299
+					sprintf(
5300
+						esc_html__('Could not create instance of class %s from row %s', 'event_espresso'),
5301
+						$this->get_this_model_name(),
5302
+						http_build_query($row)
5303
+					)
5304
+				);
5305
+			}
5306
+			// set the timezone on the instantiated objects
5307
+			$classInstance->set_timezone($this->_timezone);
5308
+			// make sure if there is any timezone setting present that we set the timezone for the object
5309
+			$key                      = $has_primary_key
5310
+				? $classInstance->ID()
5311
+				: $count_if_model_has_no_primary_key++;
5312
+			$array_of_objects[ $key ] = $classInstance;
5313
+			// also, for all the relations of type BelongsTo, see if we can cache
5314
+			// those related models
5315
+			// (we could do this for other relations too, but if there are conditions
5316
+			// that filtered out some fo the results, then we'd be caching an incomplete set
5317
+			// so it requires a little more thought than just caching them immediately...)
5318
+			foreach ($this->_model_relations as $modelName => $relation_obj) {
5319
+				if ($relation_obj instanceof EE_Belongs_To_Relation) {
5320
+					// check if this model's INFO is present. If so, cache it on the model
5321
+					$other_model           = $relation_obj->get_other_model();
5322
+					$other_model_obj_maybe = $other_model->instantiate_class_from_array_or_object($row);
5323
+					// if we managed to make a model object from the results, cache it on the main model object
5324
+					if ($other_model_obj_maybe) {
5325
+						// set timezone on these other model objects if they are present
5326
+						$other_model_obj_maybe->set_timezone($this->_timezone);
5327
+						$classInstance->cache($modelName, $other_model_obj_maybe);
5328
+					}
5329
+				}
5330
+			}
5331
+			// also, if this was a custom select query, let's see if there are any results for the custom select fields
5332
+			// and add them to the object as well.  We'll convert according to the set data_type if there's any set for
5333
+			// the field in the CustomSelects object
5334
+			if ($this->_custom_selections instanceof CustomSelects) {
5335
+				$classInstance->setCustomSelectsValues(
5336
+					$this->getValuesForCustomSelectAliasesFromResults($row)
5337
+				);
5338
+			}
5339
+		}
5340
+		return $array_of_objects;
5341
+	}
5342
+
5343
+
5344
+	/**
5345
+	 * This will parse a given row of results from the db and see if any keys in the results match an alias within the
5346
+	 * current CustomSelects object. This will be used to build an array of values indexed by those keys.
5347
+	 *
5348
+	 * @param array $db_results_row
5349
+	 * @return array
5350
+	 */
5351
+	protected function getValuesForCustomSelectAliasesFromResults(array $db_results_row)
5352
+	{
5353
+		$results = [];
5354
+		if ($this->_custom_selections instanceof CustomSelects) {
5355
+			foreach ($this->_custom_selections->columnAliases() as $alias) {
5356
+				if (isset($db_results_row[ $alias ])) {
5357
+					$results[ $alias ] = $this->convertValueToDataType(
5358
+						$db_results_row[ $alias ],
5359
+						$this->_custom_selections->getDataTypeForAlias($alias)
5360
+					);
5361
+				}
5362
+			}
5363
+		}
5364
+		return $results;
5365
+	}
5366
+
5367
+
5368
+	/**
5369
+	 * This will set the value for the given alias
5370
+	 *
5371
+	 * @param string $value
5372
+	 * @param string $datatype (one of %d, %s, %f)
5373
+	 * @return int|string|float (int for %d, string for %s, float for %f)
5374
+	 */
5375
+	protected function convertValueToDataType($value, $datatype)
5376
+	{
5377
+		switch ($datatype) {
5378
+			case '%f':
5379
+				return (float) $value;
5380
+			case '%d':
5381
+				return (int) $value;
5382
+			default:
5383
+				return (string) $value;
5384
+		}
5385
+	}
5386
+
5387
+
5388
+	/**
5389
+	 * The purpose of this method is to allow us to create a model object that is not in the db that holds default
5390
+	 * values. A typical example of where this is used is when creating a new item and the initial load of a form.  We
5391
+	 * dont' necessarily want to test for if the object is present but just assume it is BUT load the defaults from the
5392
+	 * object (as set in the model_field!).
5393
+	 *
5394
+	 * @return EE_Base_Class single EE_Base_Class object with default values for the properties.
5395
+	 * @throws EE_Error
5396
+	 * @throws ReflectionException
5397
+	 */
5398
+	public function create_default_object()
5399
+	{
5400
+		$this_model_fields_and_values = [];
5401
+		// setup the row using default values;
5402
+		foreach ($this->field_settings() as $field_name => $field_obj) {
5403
+			$this_model_fields_and_values[ $field_name ] = $field_obj->get_default_value();
5404
+		}
5405
+		$className = $this->_get_class_name();
5406
+		return EE_Registry::instance()->load_class($className, [$this_model_fields_and_values], false, false);
5407
+	}
5408
+
5409
+
5410
+	/**
5411
+	 * @param mixed $cols_n_values either an array of where each key is the name of a field, and the value is its value
5412
+	 *                             or an stdClass where each property is the name of a column,
5413
+	 * @return EE_Base_Class
5414
+	 * @throws EE_Error
5415
+	 * @throws ReflectionException
5416
+	 */
5417
+	public function instantiate_class_from_array_or_object($cols_n_values)
5418
+	{
5419
+		if (! is_array($cols_n_values) && is_object($cols_n_values)) {
5420
+			$cols_n_values = get_object_vars($cols_n_values);
5421
+		}
5422
+		$primary_key = null;
5423
+		// make sure the array only has keys that are fields/columns on this model
5424
+		$this_model_fields_n_values = $this->_deduce_fields_n_values_from_cols_n_values($cols_n_values);
5425
+		if ($this->has_primary_key_field() && isset($this_model_fields_n_values[ $this->primary_key_name() ])) {
5426
+			$primary_key = $this_model_fields_n_values[ $this->primary_key_name() ];
5427
+		}
5428
+		$className = $this->_get_class_name();
5429
+		// check we actually found results that we can use to build our model object
5430
+		// if not, return null
5431
+		if ($this->has_primary_key_field()) {
5432
+			if (empty($this_model_fields_n_values[ $this->primary_key_name() ])) {
5433
+				return null;
5434
+			}
5435
+		} elseif ($this->unique_indexes()) {
5436
+			$first_column = reset($this_model_fields_n_values);
5437
+			if (empty($first_column)) {
5438
+				return null;
5439
+			}
5440
+		}
5441
+		// if there is no primary key or the object doesn't already exist in the entity map, then create a new instance
5442
+		if ($primary_key) {
5443
+			$classInstance = $this->get_from_entity_map($primary_key);
5444
+			if (! $classInstance) {
5445
+				$classInstance = EE_Registry::instance()
5446
+											->load_class(
5447
+												$className,
5448
+												[$this_model_fields_n_values, $this->_timezone],
5449
+												true,
5450
+												false
5451
+											);
5452
+				// add this new object to the entity map
5453
+				$classInstance = $this->add_to_entity_map($classInstance);
5454
+			}
5455
+		} else {
5456
+			$classInstance = EE_Registry::instance()->load_class(
5457
+				$className,
5458
+				[$this_model_fields_n_values, $this->_timezone],
5459
+				true,
5460
+				false
5461
+			);
5462
+		}
5463
+		return $classInstance;
5464
+	}
5465
+
5466
+
5467
+	/**
5468
+	 * Gets the model object from the  entity map if it exists
5469
+	 *
5470
+	 * @param int|string $id the ID of the model object
5471
+	 * @return EE_Base_Class
5472
+	 */
5473
+	public function get_from_entity_map($id)
5474
+	{
5475
+		return $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ] ?? null;
5476
+	}
5477
+
5478
+
5479
+	/**
5480
+	 * add_to_entity_map
5481
+	 * Adds the object to the model's entity mappings
5482
+	 *        Effectively tells the models "Hey, this model object is the most up-to-date representation of the data,
5483
+	 *        and for the remainder of the request, it's even more up-to-date than what's in the database.
5484
+	 *        So, if the database doesn't agree with what's in the entity mapper, ignore the database"
5485
+	 *        If the database gets updated directly and you want the entity mapper to reflect that change,
5486
+	 *        then this method should be called immediately after the update query
5487
+	 * Note: The map is indexed by whatever the current blog id is set (via EEM_Base::$_model_query_blog_id).  This is
5488
+	 * so on multisite, the entity map is specific to the query being done for a specific site.
5489
+	 *
5490
+	 * @param EE_Base_Class $object
5491
+	 * @return EE_Base_Class
5492
+	 * @throws EE_Error
5493
+	 * @throws ReflectionException
5494
+	 */
5495
+	public function add_to_entity_map(EE_Base_Class $object)
5496
+	{
5497
+		$className = $this->_get_class_name();
5498
+		if (! $object instanceof $className) {
5499
+			throw new EE_Error(
5500
+				sprintf(
5501
+					esc_html__("You tried adding a %s to a mapping of %ss", "event_espresso"),
5502
+					is_object($object)
5503
+						? get_class($object)
5504
+						: $object,
5505
+					$className
5506
+				)
5507
+			);
5508
+		}
5509
+
5510
+		if (! $object->ID()) {
5511
+			throw new EE_Error(
5512
+				sprintf(
5513
+					esc_html__(
5514
+						"You tried storing a model object with NO ID in the %s entity mapper.",
5515
+						"event_espresso"
5516
+					),
5517
+					get_class($this)
5518
+				)
5519
+			);
5520
+		}
5521
+		// double check it's not already there
5522
+		$classInstance = $this->get_from_entity_map($object->ID());
5523
+		if ($classInstance) {
5524
+			return $classInstance;
5525
+		}
5526
+		$this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $object->ID() ] = $object;
5527
+		return $object;
5528
+	}
5529
+
5530
+
5531
+	/**
5532
+	 * if a valid identifier is provided, then that entity is unset from the entity map,
5533
+	 * if no identifier is provided, then the entire entity map is emptied
5534
+	 *
5535
+	 * @param int|string $id the ID of the model object
5536
+	 * @return boolean
5537
+	 */
5538
+	public function clear_entity_map($id = null)
5539
+	{
5540
+		if (empty($id)) {
5541
+			$this->_entity_map[ EEM_Base::$_model_query_blog_id ] = [];
5542
+			return true;
5543
+		}
5544
+		if (isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])) {
5545
+			unset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ]);
5546
+			return true;
5547
+		}
5548
+		return false;
5549
+	}
5550
+
5551
+
5552
+	/**
5553
+	 * Public wrapper for _deduce_fields_n_values_from_cols_n_values.
5554
+	 * Given an array where keys are column (or column alias) names and values,
5555
+	 * returns an array of their corresponding field names and database values
5556
+	 *
5557
+	 * @param array $cols_n_values
5558
+	 * @return array
5559
+	 * @throws EE_Error
5560
+	 * @throws ReflectionException
5561
+	 */
5562
+	public function deduce_fields_n_values_from_cols_n_values(array $cols_n_values): array
5563
+	{
5564
+		return $this->_deduce_fields_n_values_from_cols_n_values($cols_n_values);
5565
+	}
5566
+
5567
+
5568
+	/**
5569
+	 * _deduce_fields_n_values_from_cols_n_values
5570
+	 * Given an array where keys are column (or column alias) names and values,
5571
+	 * returns an array of their corresponding field names and database values
5572
+	 *
5573
+	 * @param array|stdClass $cols_n_values
5574
+	 * @return array
5575
+	 * @throws EE_Error
5576
+	 * @throws ReflectionException
5577
+	 */
5578
+	protected function _deduce_fields_n_values_from_cols_n_values($cols_n_values): array
5579
+	{
5580
+		if ($cols_n_values instanceof stdClass) {
5581
+			$cols_n_values = get_object_vars($cols_n_values);
5582
+		}
5583
+		$this_model_fields_n_values = [];
5584
+		foreach ($this->get_tables() as $table_alias => $table_obj) {
5585
+			$table_pk_value = $this->_get_column_value_with_table_alias_or_not(
5586
+				$cols_n_values,
5587
+				$table_obj->get_fully_qualified_pk_column(),
5588
+				$table_obj->get_pk_column()
5589
+			);
5590
+			// there is a primary key on this table and its not set. Use defaults for all its columns
5591
+			if ($table_pk_value === null && $table_obj->get_pk_column()) {
5592
+				foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5593
+					if (! $field_obj->is_db_only_field()) {
5594
+						// prepare field as if its coming from db
5595
+						$prepared_value                            =
5596
+							$field_obj->prepare_for_set($field_obj->get_default_value());
5597
+						$this_model_fields_n_values[ $field_name ] = $field_obj->prepare_for_use_in_db($prepared_value);
5598
+					}
5599
+				}
5600
+			} else {
5601
+				// the table's rows existed. Use their values
5602
+				foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5603
+					if (! $field_obj->is_db_only_field()) {
5604
+						$this_model_fields_n_values[ $field_name ] = $this->_get_column_value_with_table_alias_or_not(
5605
+							$cols_n_values,
5606
+							$field_obj->get_qualified_column(),
5607
+							$field_obj->get_table_column()
5608
+						);
5609
+					}
5610
+				}
5611
+			}
5612
+		}
5613
+		return $this_model_fields_n_values;
5614
+	}
5615
+
5616
+
5617
+	/**
5618
+	 * @param $cols_n_values
5619
+	 * @param $qualified_column
5620
+	 * @param $regular_column
5621
+	 * @return null
5622
+	 * @throws EE_Error
5623
+	 * @throws ReflectionException
5624
+	 */
5625
+	protected function _get_column_value_with_table_alias_or_not($cols_n_values, $qualified_column, $regular_column)
5626
+	{
5627
+		$value = null;
5628
+		// ask the field what it think it's table_name.column_name should be, and call it the "qualified column"
5629
+		// does the field on the model relate to this column retrieved from the db?
5630
+		// or is it a db-only field? (not relating to the model)
5631
+		if (isset($cols_n_values[ $qualified_column ])) {
5632
+			$value = $cols_n_values[ $qualified_column ];
5633
+		} elseif (isset($cols_n_values[ $regular_column ])) {
5634
+			$value = $cols_n_values[ $regular_column ];
5635
+		} elseif (! empty($this->foreign_key_aliases)) {
5636
+			// no PK?  ok check if there is a foreign key alias set for this table
5637
+			// then check if that alias exists in the incoming data
5638
+			// AND that the actual PK the $FK_alias represents matches the $qualified_column (full PK)
5639
+			foreach ($this->foreign_key_aliases as $FK_alias => $PK_column) {
5640
+				if ($PK_column === $qualified_column && !empty($cols_n_values[ $FK_alias ])) {
5641
+					$value = $cols_n_values[ $FK_alias ];
5642
+					[$pk_class] = explode('.', $PK_column);
5643
+					$pk_model_name = "EEM_{$pk_class}";
5644
+					/** @var EEM_Base $pk_model */
5645
+					$pk_model = EE_Registry::instance()->load_model($pk_model_name);
5646
+					if ($pk_model instanceof EEM_Base) {
5647
+						// make sure object is pulled from db and added to entity map
5648
+						$pk_model->get_one_by_ID($value);
5649
+					}
5650
+					break;
5651
+				}
5652
+			}
5653
+		}
5654
+		return $value;
5655
+	}
5656
+
5657
+
5658
+	/**
5659
+	 * refresh_entity_map_from_db
5660
+	 * Makes sure the model object in the entity map at $id assumes the values
5661
+	 * of the database (opposite of EE_base_Class::save())
5662
+	 *
5663
+	 * @param int|string $id
5664
+	 * @return EE_Base_Class|EE_Soft_Delete_Base_Class|mixed|null
5665
+	 * @throws EE_Error
5666
+	 * @throws ReflectionException
5667
+	 */
5668
+	public function refresh_entity_map_from_db($id)
5669
+	{
5670
+		$obj_in_map = $this->get_from_entity_map($id);
5671
+		if ($obj_in_map) {
5672
+			$wpdb_results = $this->_get_all_wpdb_results(
5673
+				[[$this->get_primary_key_field()->get_name() => $id], 'limit' => 1]
5674
+			);
5675
+			if ($wpdb_results && is_array($wpdb_results)) {
5676
+				$one_row = reset($wpdb_results);
5677
+				foreach ($this->_deduce_fields_n_values_from_cols_n_values($one_row) as $field_name => $db_value) {
5678
+					$obj_in_map->set_from_db($field_name, $db_value);
5679
+				}
5680
+				// clear the cache of related model objects
5681
+				foreach ($this->relation_settings() as $relation_name => $relation_obj) {
5682
+					$obj_in_map->clear_cache($relation_name, null, true);
5683
+				}
5684
+			}
5685
+			$this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ] = $obj_in_map;
5686
+			return $obj_in_map;
5687
+		}
5688
+		return $this->get_one_by_ID($id);
5689
+	}
5690
+
5691
+
5692
+	/**
5693
+	 * refresh_entity_map_with
5694
+	 * Leaves the entry in the entity map alone, but updates it to match the provided
5695
+	 * $replacing_model_obj (which we assume to be its equivalent but somehow NOT in the entity map).
5696
+	 * This is useful if you have a model object you want to make authoritative over what's in the entity map currently.
5697
+	 * Note: The old $replacing_model_obj should now be destroyed as it's now un-authoritative
5698
+	 *
5699
+	 * @param int|string    $id
5700
+	 * @param EE_Base_Class $replacing_model_obj
5701
+	 * @return EE_Base_Class
5702
+	 * @throws EE_Error
5703
+	 * @throws ReflectionException
5704
+	 */
5705
+	public function refresh_entity_map_with($id, $replacing_model_obj)
5706
+	{
5707
+		$obj_in_map = $this->get_from_entity_map($id);
5708
+		if ($obj_in_map) {
5709
+			if ($replacing_model_obj instanceof EE_Base_Class) {
5710
+				foreach ($replacing_model_obj->model_field_array() as $field_name => $value) {
5711
+					$obj_in_map->set($field_name, $value);
5712
+				}
5713
+				// make the model object in the entity map's cache match the $replacing_model_obj
5714
+				foreach ($this->relation_settings() as $relation_name => $relation_obj) {
5715
+					$obj_in_map->clear_cache($relation_name, null, true);
5716
+					foreach ($replacing_model_obj->get_all_from_cache($relation_name) as $cache_id => $cached_obj) {
5717
+						$obj_in_map->cache($relation_name, $cached_obj, $cache_id);
5718
+					}
5719
+				}
5720
+			}
5721
+			return $obj_in_map;
5722
+		}
5723
+		$this->add_to_entity_map($replacing_model_obj);
5724
+		return $replacing_model_obj;
5725
+	}
5726
+
5727
+
5728
+	/**
5729
+	 * Gets the EE class that corresponds to this model. Eg, for EEM_Answer that
5730
+	 * would be EE_Answer.To import that class, you'd just add ".class.php" to the name, like so
5731
+	 * require_once($this->_getClassName().".class.php");
5732
+	 *
5733
+	 * @return string
5734
+	 */
5735
+	private function _get_class_name()
5736
+	{
5737
+		return "EE_" . $this->get_this_model_name();
5738
+	}
5739
+
5740
+
5741
+	/**
5742
+	 * Get the name of the items this model represents, for the quantity specified. Eg,
5743
+	 * if $quantity==1, on EEM_Event, it would 'Event' (internationalized), otherwise
5744
+	 * it would be 'Events'.
5745
+	 *
5746
+	 * @param int|float|null $quantity
5747
+	 * @return string
5748
+	 */
5749
+	public function item_name($quantity = 1): string
5750
+	{
5751
+		$quantity = floor($quantity);
5752
+		return apply_filters(
5753
+			'FHEE__EEM_Base__item_name__plural_or_singular',
5754
+			$quantity > 1
5755
+				? $this->plural_item
5756
+				: $this->singular_item,
5757
+			$quantity,
5758
+			$this->plural_item,
5759
+			$this->singular_item
5760
+		);
5761
+	}
5762
+
5763
+
5764
+	/**
5765
+	 * Very handy general function to allow for plugins to extend any child of EE_TempBase.
5766
+	 * If a method is called on a child of EE_TempBase that doesn't exist, this function is called
5767
+	 * (http://www.garfieldtech.com/blog/php-magic-call) and passed the method's name and arguments. Instead of
5768
+	 * requiring a plugin to extend the EE_TempBase (which works fine is there's only 1 plugin, but when will that
5769
+	 * happen?) they can add a hook onto 'filters_hook_espresso__{className}__{methodName}' (eg,
5770
+	 * filters_hook_espresso__EE_Answer__my_great_function) and accepts 2 arguments: the object on which the function
5771
+	 * was called, and an array of the original arguments passed to the function. Whatever their callback function
5772
+	 * returns will be returned by this function. Example: in functions.php (or in a plugin):
5773
+	 * add_filter('FHEE__EE_Answer__my_callback','my_callback',10,3); function
5774
+	 * my_callback($previousReturnValue,EE_TempBase $object,$argsArray){
5775
+	 * $returnString= "you called my_callback! and passed args:".implode(",",$argsArray);
5776
+	 *        return $previousReturnValue.$returnString;
5777
+	 * }
5778
+	 * require('EEM_Answer.model.php');
5779
+	 * echo EEM_Answer::instance()->my_callback('monkeys',100);
5780
+	 * // will output "you called my_callback! and passed args:monkeys,100"
5781
+	 *
5782
+	 * @param string $methodName name of method which was called on a child of EE_TempBase, but which
5783
+	 * @param array  $args       array of original arguments passed to the function
5784
+	 * @return mixed whatever the plugin which calls add_filter decides
5785
+	 * @throws EE_Error
5786
+	 */
5787
+	public function __call($methodName, $args)
5788
+	{
5789
+		$className = get_class($this);
5790
+		$tagName   = "FHEE__{$className}__{$methodName}";
5791
+		if (! has_filter($tagName)) {
5792
+			throw new EE_Error(
5793
+				sprintf(
5794
+					esc_html__(
5795
+						'Method %1$s on model %2$s does not exist! You can create one with the following code in functions.php or in a plugin: %4$s function my_callback(%4$s \$previousReturnValue, EEM_Base \$object\ $argsArray=NULL ){%4$s     /*function body*/%4$s      return \$whatever;%4$s }%4$s add_filter( \'%3$s\', \'my_callback\', 10, 3 );',
5796
+						'event_espresso'
5797
+					),
5798
+					$methodName,
5799
+					$className,
5800
+					$tagName,
5801
+					'<br />'
5802
+				)
5803
+			);
5804
+		}
5805
+		return apply_filters($tagName, null, $this, $args);
5806
+	}
5807
+
5808
+
5809
+	/**
5810
+	 * Ensures $base_class_obj_or_id is of the EE_Base_Class child that corresponds ot this model.
5811
+	 * If not, assumes its an ID, and uses $this->get_one_by_ID() to get the EE_Base_Class.
5812
+	 *
5813
+	 * @param EE_Base_Class|string|int $base_class_obj_or_id either:
5814
+	 *                                                       the EE_Base_Class object that corresponds to this Model,
5815
+	 *                                                       the object's class name
5816
+	 *                                                       or object's ID
5817
+	 * @param boolean                  $ensure_is_in_db      if set, we will also verify this model object
5818
+	 *                                                       exists in the database. If it does not, we add it
5819
+	 * @return EE_Base_Class
5820
+	 * @throws EE_Error
5821
+	 * @throws ReflectionException
5822
+	 */
5823
+	public function ensure_is_obj($base_class_obj_or_id, $ensure_is_in_db = false)
5824
+	{
5825
+		$className = $this->_get_class_name();
5826
+		if ($base_class_obj_or_id instanceof $className) {
5827
+			$model_object = $base_class_obj_or_id;
5828
+		} else {
5829
+			$primary_key_field = $this->get_primary_key_field();
5830
+			if (
5831
+				$primary_key_field instanceof EE_Primary_Key_Int_Field
5832
+				&& (
5833
+					is_int($base_class_obj_or_id)
5834
+					|| is_string($base_class_obj_or_id)
5835
+				)
5836
+			) {
5837
+				// assume it's an ID.
5838
+				// either a proper integer or a string representing an integer (eg "101" instead of 101)
5839
+				$model_object = $this->get_one_by_ID($base_class_obj_or_id);
5840
+			} elseif (
5841
+				$primary_key_field instanceof EE_Primary_Key_String_Field
5842
+				&& is_string($base_class_obj_or_id)
5843
+			) {
5844
+				// assume its a string representation of the object
5845
+				$model_object = $this->get_one_by_ID($base_class_obj_or_id);
5846
+			} else {
5847
+				throw new EE_Error(
5848
+					sprintf(
5849
+						esc_html__(
5850
+							"'%s' is neither an object of type %s, nor an ID! Its full value is '%s'",
5851
+							'event_espresso'
5852
+						),
5853
+						$base_class_obj_or_id,
5854
+						$this->_get_class_name(),
5855
+						print_r($base_class_obj_or_id, true)
5856
+					)
5857
+				);
5858
+			}
5859
+		}
5860
+		if ($ensure_is_in_db && $model_object->ID() !== null) {
5861
+			$model_object->save();
5862
+		}
5863
+		return $model_object;
5864
+	}
5865
+
5866
+
5867
+	/**
5868
+	 * Similar to ensure_is_obj(), this method makes sure $base_class_obj_or_id
5869
+	 * is a value of the this model's primary key. If it's an EE_Base_Class child,
5870
+	 * returns it ID.
5871
+	 *
5872
+	 * @param EE_Base_Class|int|string $base_class_obj_or_id
5873
+	 * @return int|string depending on the type of this model object's ID
5874
+	 * @throws EE_Error
5875
+	 * @throws ReflectionException
5876
+	 */
5877
+	public function ensure_is_ID($base_class_obj_or_id)
5878
+	{
5879
+		$className = $this->_get_class_name();
5880
+		if ($base_class_obj_or_id instanceof $className) {
5881
+			/** @var $base_class_obj_or_id EE_Base_Class */
5882
+			$id = $base_class_obj_or_id->ID();
5883
+		} elseif (is_int($base_class_obj_or_id)) {
5884
+			// assume it's an ID
5885
+			$id = $base_class_obj_or_id;
5886
+		} elseif (is_string($base_class_obj_or_id)) {
5887
+			// assume its a string representation of the object
5888
+			$id = $base_class_obj_or_id;
5889
+		} else {
5890
+			throw new EE_Error(
5891
+				sprintf(
5892
+					esc_html__(
5893
+						"'%s' is neither an object of type %s, nor an ID! Its full value is '%s'",
5894
+						'event_espresso'
5895
+					),
5896
+					$base_class_obj_or_id,
5897
+					$this->_get_class_name(),
5898
+					print_r($base_class_obj_or_id, true)
5899
+				)
5900
+			);
5901
+		}
5902
+		return $id;
5903
+	}
5904
+
5905
+
5906
+	/**
5907
+	 * Sets whether the values passed to the model (eg, values in WHERE, values in INSERT, UPDATE, etc)
5908
+	 * have already been ran through the appropriate model field's prepare_for_use_in_db method. IE, they have
5909
+	 * been sanitized and converted into the appropriate domain.
5910
+	 * Usually the only place you'll want to change the default (which is to assume values have NOT been sanitized by
5911
+	 * the model object/model field) is when making a method call from WITHIN a model object, which has direct access
5912
+	 * to its sanitized values. Note: after changing this setting, you should set it back to its previous value (using
5913
+	 * get_assumption_concerning_values_already_prepared_by_model_object()) eg.
5914
+	 * $EVT = EEM_Event::instance(); $old_setting =
5915
+	 * $EVT->get_assumption_concerning_values_already_prepared_by_model_object();
5916
+	 * $EVT->assume_values_already_prepared_by_model_object(true);
5917
+	 * $EVT->update(array('foo'=>'bar'),array(array('foo'=>'monkey')));
5918
+	 * $EVT->assume_values_already_prepared_by_model_object($old_setting);
5919
+	 *
5920
+	 * @param int $values_already_prepared like one of the constants on EEM_Base
5921
+	 * @return void
5922
+	 */
5923
+	public function assume_values_already_prepared_by_model_object(
5924
+		$values_already_prepared = self::not_prepared_by_model_object
5925
+	) {
5926
+		$this->_values_already_prepared_by_model_object = $values_already_prepared;
5927
+	}
5928
+
5929
+
5930
+	/**
5931
+	 * Read comments for assume_values_already_prepared_by_model_object()
5932
+	 *
5933
+	 * @return int
5934
+	 */
5935
+	public function get_assumption_concerning_values_already_prepared_by_model_object()
5936
+	{
5937
+		return $this->_values_already_prepared_by_model_object;
5938
+	}
5939
+
5940
+
5941
+	/**
5942
+	 * Gets all the indexes on this model
5943
+	 *
5944
+	 * @return EE_Index[]
5945
+	 */
5946
+	public function indexes()
5947
+	{
5948
+		return $this->_indexes;
5949
+	}
5950
+
5951
+
5952
+	/**
5953
+	 * Gets all the Unique Indexes on this model
5954
+	 *
5955
+	 * @return EE_Unique_Index[]
5956
+	 */
5957
+	public function unique_indexes()
5958
+	{
5959
+		$unique_indexes = [];
5960
+		foreach ($this->_indexes as $name => $index) {
5961
+			if ($index instanceof EE_Unique_Index) {
5962
+				$unique_indexes [ $name ] = $index;
5963
+			}
5964
+		}
5965
+		return $unique_indexes;
5966
+	}
5967
+
5968
+
5969
+	/**
5970
+	 * Gets all the fields which, when combined, make the primary key.
5971
+	 * This is usually just an array with 1 element (the primary key), but in cases
5972
+	 * where there is no primary key, it's a combination of fields as defined
5973
+	 * on a primary index
5974
+	 *
5975
+	 * @return EE_Model_Field_Base[] indexed by the field's name
5976
+	 * @throws EE_Error
5977
+	 */
5978
+	public function get_combined_primary_key_fields()
5979
+	{
5980
+		foreach ($this->indexes() as $index) {
5981
+			if ($index instanceof EE_Primary_Key_Index) {
5982
+				return $index->fields();
5983
+			}
5984
+		}
5985
+		return [$this->primary_key_name() => $this->get_primary_key_field()];
5986
+	}
5987
+
5988
+
5989
+	/**
5990
+	 * Used to build a primary key string (when the model has no primary key),
5991
+	 * which can be used a unique string to identify this model object.
5992
+	 *
5993
+	 * @param array $fields_n_values keys are field names, values are their values.
5994
+	 *                               Note: if you have results from `EEM_Base::get_all_wpdb_results()`, you need to
5995
+	 *                               run it through `EEM_Base::deduce_fields_n_values_from_cols_n_values()`
5996
+	 *                               before passing it to this function (that will convert it from columns-n-values
5997
+	 *                               to field-names-n-values).
5998
+	 * @return string
5999
+	 * @throws EE_Error
6000
+	 */
6001
+	public function get_index_primary_key_string($fields_n_values)
6002
+	{
6003
+		$cols_n_values_for_primary_key_index = array_intersect_key(
6004
+			$fields_n_values,
6005
+			$this->get_combined_primary_key_fields()
6006
+		);
6007
+		return http_build_query($cols_n_values_for_primary_key_index);
6008
+	}
6009
+
6010
+
6011
+	/**
6012
+	 * Gets the field values from the primary key string
6013
+	 *
6014
+	 * @param string $index_primary_key_string
6015
+	 * @return null|array
6016
+	 * @throws EE_Error
6017
+	 * @see EEM_Base::get_combined_primary_key_fields() and EEM_Base::get_index_primary_key_string()
6018
+	 */
6019
+	public function parse_index_primary_key_string($index_primary_key_string)
6020
+	{
6021
+		$key_fields = $this->get_combined_primary_key_fields();
6022
+		// check all of them are in the $id
6023
+		$key_vals_in_combined_pk = [];
6024
+		parse_str($index_primary_key_string, $key_vals_in_combined_pk);
6025
+		foreach ($key_fields as $key_field_name => $field_obj) {
6026
+			if (! isset($key_vals_in_combined_pk[ $key_field_name ])) {
6027
+				return null;
6028
+			}
6029
+		}
6030
+		return $key_vals_in_combined_pk;
6031
+	}
6032
+
6033
+
6034
+	/**
6035
+	 * verifies that an array of key-value pairs for model fields has a key
6036
+	 * for each field comprising the primary key index
6037
+	 *
6038
+	 * @param array $key_vals
6039
+	 * @return boolean
6040
+	 * @throws EE_Error
6041
+	 */
6042
+	public function has_all_combined_primary_key_fields($key_vals)
6043
+	{
6044
+		$keys_it_should_have = array_keys($this->get_combined_primary_key_fields());
6045
+		foreach ($keys_it_should_have as $key) {
6046
+			if (! isset($key_vals[ $key ])) {
6047
+				return false;
6048
+			}
6049
+		}
6050
+		return true;
6051
+	}
6052
+
6053
+
6054
+	/**
6055
+	 * Finds all model objects in the DB that appear to be a copy of $model_object_or_attributes_array.
6056
+	 * We consider something to be a copy if all the attributes match (except the ID, of course).
6057
+	 *
6058
+	 * @param array|EE_Base_Class $model_object_or_attributes_array If its an array, it's field-value pairs
6059
+	 * @param array               $query_params                     @see
6060
+	 *                                                              https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
6061
+	 * @throws EE_Error
6062
+	 * @throws ReflectionException
6063
+	 * @return EE_Base_Class[] Array keys are object IDs (if there is a primary key on the model. if not, numerically
6064
+	 *                                                              indexed)
6065
+	 */
6066
+	public function get_all_copies($model_object_or_attributes_array, $query_params = [])
6067
+	{
6068
+		if ($model_object_or_attributes_array instanceof EE_Base_Class) {
6069
+			$attributes_array = $model_object_or_attributes_array->model_field_array();
6070
+		} elseif (is_array($model_object_or_attributes_array)) {
6071
+			$attributes_array = $model_object_or_attributes_array;
6072
+		} else {
6073
+			throw new EE_Error(
6074
+				sprintf(
6075
+					esc_html__(
6076
+						"get_all_copies should be provided with either a model object or an array of field-value-pairs, but was given %s",
6077
+						"event_espresso"
6078
+					),
6079
+					$model_object_or_attributes_array
6080
+				)
6081
+			);
6082
+		}
6083
+		// even copies obviously won't have the same ID, so remove the primary key
6084
+		// from the WHERE conditions for finding copies (if there is a primary key, of course)
6085
+		if ($this->has_primary_key_field() && isset($attributes_array[ $this->primary_key_name() ])) {
6086
+			unset($attributes_array[ $this->primary_key_name() ]);
6087
+		}
6088
+		if (isset($query_params[0])) {
6089
+			$query_params[0] = array_merge($attributes_array, $query_params);
6090
+		} else {
6091
+			$query_params[0] = $attributes_array;
6092
+		}
6093
+		return $this->get_all($query_params);
6094
+	}
6095
+
6096
+
6097
+	/**
6098
+	 * Gets the first copy we find. See get_all_copies for more details
6099
+	 *
6100
+	 * @param mixed EE_Base_Class | array        $model_object_or_attributes_array
6101
+	 * @param array $query_params
6102
+	 * @return EE_Base_Class
6103
+	 * @throws EE_Error
6104
+	 * @throws ReflectionException
6105
+	 */
6106
+	public function get_one_copy($model_object_or_attributes_array, $query_params = [])
6107
+	{
6108
+		if (! is_array($query_params)) {
6109
+			EE_Error::doing_it_wrong(
6110
+				'EEM_Base::get_one_copy',
6111
+				sprintf(
6112
+					esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
6113
+					gettype($query_params)
6114
+				),
6115
+				'4.6.0'
6116
+			);
6117
+			$query_params = [];
6118
+		}
6119
+		$query_params['limit'] = 1;
6120
+		$copies                = $this->get_all_copies($model_object_or_attributes_array, $query_params);
6121
+		if (is_array($copies)) {
6122
+			return array_shift($copies);
6123
+		}
6124
+		return null;
6125
+	}
6126
+
6127
+
6128
+	/**
6129
+	 * Updates the item with the specified id. Ignores default query parameters because
6130
+	 * we have specified the ID, and its assumed we KNOW what we're doing
6131
+	 *
6132
+	 * @param array      $fields_n_values keys are field names, values are their new values
6133
+	 * @param int|string $id              the value of the primary key to update
6134
+	 * @return int number of rows updated
6135
+	 * @throws EE_Error
6136
+	 * @throws ReflectionException
6137
+	 */
6138
+	public function update_by_ID($fields_n_values, $id)
6139
+	{
6140
+		$query_params = [
6141
+			0                          => [$this->get_primary_key_field()->get_name() => $id],
6142
+			'default_where_conditions' => EEM_Base::default_where_conditions_others_only,
6143
+		];
6144
+		return $this->update($fields_n_values, $query_params);
6145
+	}
6146
+
6147
+
6148
+	/**
6149
+	 * Changes an operator which was supplied to the models into one usable in SQL
6150
+	 *
6151
+	 * @param string $operator_supplied
6152
+	 * @return string an operator which can be used in SQL
6153
+	 * @throws EE_Error
6154
+	 */
6155
+	private function _prepare_operator_for_sql($operator_supplied)
6156
+	{
6157
+		$sql_operator = $this->_valid_operators[ $operator_supplied ] ?? null;
6158
+		if ($sql_operator) {
6159
+			return $sql_operator;
6160
+		}
6161
+		throw new EE_Error(
6162
+			sprintf(
6163
+				esc_html__(
6164
+					"The operator '%s' is not in the list of valid operators: %s",
6165
+					"event_espresso"
6166
+				),
6167
+				$operator_supplied,
6168
+				implode(",", array_keys($this->_valid_operators))
6169
+			)
6170
+		);
6171
+	}
6172
+
6173
+
6174
+	/**
6175
+	 * Gets the valid operators
6176
+	 *
6177
+	 * @return array keys are accepted strings, values are the SQL they are converted to
6178
+	 */
6179
+	public function valid_operators()
6180
+	{
6181
+		return $this->_valid_operators;
6182
+	}
6183
+
6184
+
6185
+	/**
6186
+	 * Gets the between-style operators (take 2 arguments).
6187
+	 *
6188
+	 * @return array keys are accepted strings, values are the SQL they are converted to
6189
+	 */
6190
+	public function valid_between_style_operators()
6191
+	{
6192
+		return array_intersect(
6193
+			$this->valid_operators(),
6194
+			$this->_between_style_operators
6195
+		);
6196
+	}
6197
+
6198
+
6199
+	/**
6200
+	 * Gets the "like"-style operators (take a single argument, but it may contain wildcards)
6201
+	 *
6202
+	 * @return array keys are accepted strings, values are the SQL they are converted to
6203
+	 */
6204
+	public function valid_like_style_operators()
6205
+	{
6206
+		return array_intersect(
6207
+			$this->valid_operators(),
6208
+			$this->_like_style_operators
6209
+		);
6210
+	}
6211
+
6212
+
6213
+	/**
6214
+	 * Gets the "in"-style operators
6215
+	 *
6216
+	 * @return array keys are accepted strings, values are the SQL they are converted to
6217
+	 */
6218
+	public function valid_in_style_operators()
6219
+	{
6220
+		return array_intersect(
6221
+			$this->valid_operators(),
6222
+			$this->_in_style_operators
6223
+		);
6224
+	}
6225
+
6226
+
6227
+	/**
6228
+	 * Gets the "null"-style operators (accept no arguments)
6229
+	 *
6230
+	 * @return array keys are accepted strings, values are the SQL they are converted to
6231
+	 */
6232
+	public function valid_null_style_operators()
6233
+	{
6234
+		return array_intersect(
6235
+			$this->valid_operators(),
6236
+			$this->_null_style_operators
6237
+		);
6238
+	}
6239
+
6240
+
6241
+	/**
6242
+	 * Gets an array where keys are the primary keys and values are their 'names'
6243
+	 * (as determined by the model object's name() function, which is often overridden)
6244
+	 *
6245
+	 * @param array $query_params like get_all's
6246
+	 * @return string[]
6247
+	 * @throws EE_Error
6248
+	 * @throws ReflectionException
6249
+	 */
6250
+	public function get_all_names($query_params = [])
6251
+	{
6252
+		$objs  = $this->get_all($query_params);
6253
+		$names = [];
6254
+		foreach ($objs as $obj) {
6255
+			$names[ $obj->ID() ] = $obj->name();
6256
+		}
6257
+		return $names;
6258
+	}
6259
+
6260
+
6261
+	/**
6262
+	 * Gets an array of primary keys from the model objects. If you acquired the model objects
6263
+	 * using EEM_Base::get_all() you don't need to call this (and probably shouldn't because
6264
+	 * this is duplicated effort and reduces efficiency) you would be better to use
6265
+	 * array_keys() on $model_objects.
6266
+	 *
6267
+	 * @param \EE_Base_Class[] $model_objects
6268
+	 * @param boolean          $filter_out_empty_ids if a model object has an ID of '' or 0, don't bother including it
6269
+	 *                                               in the returned array
6270
+	 * @return array
6271
+	 * @throws EE_Error
6272
+	 * @throws ReflectionException
6273
+	 */
6274
+	public function get_IDs($model_objects, $filter_out_empty_ids = false)
6275
+	{
6276
+		if (! $this->has_primary_key_field()) {
6277
+			if (WP_DEBUG) {
6278
+				EE_Error::add_error(
6279
+					esc_html__('Trying to get IDs from a model than has no primary key', 'event_espresso'),
6280
+					__FILE__,
6281
+					__FUNCTION__,
6282
+					__LINE__
6283
+				);
6284
+			}
6285
+		}
6286
+		$IDs = [];
6287
+		foreach ($model_objects as $model_object) {
6288
+			$id = $model_object->ID();
6289
+			if (! $id) {
6290
+				if ($filter_out_empty_ids) {
6291
+					continue;
6292
+				}
6293
+				if (WP_DEBUG) {
6294
+					EE_Error::add_error(
6295
+						esc_html__(
6296
+							'Called %1$s on a model object that has no ID and so probably hasn\'t been saved to the database',
6297
+							'event_espresso'
6298
+						),
6299
+						__FILE__,
6300
+						__FUNCTION__,
6301
+						__LINE__
6302
+					);
6303
+				}
6304
+			}
6305
+			$IDs[] = $id;
6306
+		}
6307
+		return $IDs;
6308
+	}
6309
+
6310
+
6311
+	/**
6312
+	 * Returns the string used in capabilities relating to this model. If there
6313
+	 * are no capabilities that relate to this model returns false
6314
+	 *
6315
+	 * @return string|false
6316
+	 */
6317
+	public function cap_slug()
6318
+	{
6319
+		return apply_filters('FHEE__EEM_Base__cap_slug', $this->_caps_slug, $this);
6320
+	}
6321
+
6322
+
6323
+	/**
6324
+	 * Returns the capability-restrictions array (@param string $context
6325
+	 *
6326
+	 * @return EE_Default_Where_Conditions[] indexed by associated capability
6327
+	 * @throws EE_Error
6328
+	 * @see EEM_Base::_cap_restrictions).
6329
+	 *      If $context is provided (which should be set to one of EEM_Base::valid_cap_contexts())
6330
+	 *      only returns the cap restrictions array in that context (ie, the array
6331
+	 *      at that key)
6332
+	 */
6333
+	public function cap_restrictions($context = EEM_Base::caps_read)
6334
+	{
6335
+		EEM_Base::verify_is_valid_cap_context($context);
6336
+		// check if we ought to run the restriction generator first
6337
+		if (
6338
+			isset($this->_cap_restriction_generators[ $context ])
6339
+			&& $this->_cap_restriction_generators[ $context ] instanceof EE_Restriction_Generator_Base
6340
+			&& ! $this->_cap_restriction_generators[ $context ]->has_generated_cap_restrictions()
6341
+		) {
6342
+			$this->_cap_restrictions[ $context ] = array_merge(
6343
+				$this->_cap_restrictions[ $context ],
6344
+				$this->_cap_restriction_generators[ $context ]->generate_restrictions()
6345
+			);
6346
+		}
6347
+		// and make sure we've finalized the construction of each restriction
6348
+		foreach ($this->_cap_restrictions[ $context ] as $where_conditions_obj) {
6349
+			if ($where_conditions_obj instanceof EE_Default_Where_Conditions) {
6350
+				$where_conditions_obj->_finalize_construct($this);
6351
+			}
6352
+		}
6353
+		return $this->_cap_restrictions[ $context ];
6354
+	}
6355
+
6356
+
6357
+	/**
6358
+	 * Indicating whether or not this model thinks its a wp core model
6359
+	 *
6360
+	 * @return boolean
6361
+	 */
6362
+	public function is_wp_core_model()
6363
+	{
6364
+		return $this->_wp_core_model;
6365
+	}
6366
+
6367
+
6368
+	/**
6369
+	 * Gets all the caps that are missing which impose a restriction on
6370
+	 * queries made in this context
6371
+	 *
6372
+	 * @param string $context one of EEM_Base::caps_ constants
6373
+	 * @return EE_Default_Where_Conditions[] indexed by capability name
6374
+	 * @throws EE_Error
6375
+	 */
6376
+	public function caps_missing($context = EEM_Base::caps_read)
6377
+	{
6378
+		$missing_caps     = [];
6379
+		$cap_restrictions = $this->cap_restrictions($context);
6380
+		foreach ($cap_restrictions as $cap => $restriction_if_no_cap) {
6381
+			if (
6382
+				! EE_Capabilities::instance()
6383
+								 ->current_user_can($cap, $this->get_this_model_name() . '_model_applying_caps')
6384
+			) {
6385
+				$missing_caps[ $cap ] = $restriction_if_no_cap;
6386
+			}
6387
+		}
6388
+		return $missing_caps;
6389
+	}
6390
+
6391
+
6392
+	/**
6393
+	 * Gets the mapping from capability contexts to action strings used in capability names
6394
+	 *
6395
+	 * @return array keys are one of EEM_Base::valid_cap_contexts(), and values are usually
6396
+	 * one of 'read', 'edit', or 'delete'
6397
+	 */
6398
+	public function cap_contexts_to_cap_action_map()
6399
+	{
6400
+		return apply_filters(
6401
+			'FHEE__EEM_Base__cap_contexts_to_cap_action_map',
6402
+			$this->_cap_contexts_to_cap_action_map,
6403
+			$this
6404
+		);
6405
+	}
6406
+
6407
+
6408
+	/**
6409
+	 * Gets the action string for the specified capability context
6410
+	 *
6411
+	 * @param string $context
6412
+	 * @return string one of EEM_Base::cap_contexts_to_cap_action_map() values
6413
+	 * @throws EE_Error
6414
+	 */
6415
+	public function cap_action_for_context($context)
6416
+	{
6417
+		$mapping = $this->cap_contexts_to_cap_action_map();
6418
+		if (isset($mapping[ $context ])) {
6419
+			return $mapping[ $context ];
6420
+		}
6421
+		if ($action = apply_filters('FHEE__EEM_Base__cap_action_for_context', null, $this, $mapping, $context)) {
6422
+			return $action;
6423
+		}
6424
+		throw new EE_Error(
6425
+			sprintf(
6426
+				esc_html__(
6427
+					'Cannot find capability restrictions for context "%1$s", allowed values are:%2$s',
6428
+					'event_espresso'
6429
+				),
6430
+				$context,
6431
+				implode(',', array_keys($this->cap_contexts_to_cap_action_map()))
6432
+			)
6433
+		);
6434
+	}
6435
+
6436
+
6437
+	/**
6438
+	 * Returns all the capability contexts which are valid when querying models
6439
+	 *
6440
+	 * @return array
6441
+	 */
6442
+	public static function valid_cap_contexts(): array
6443
+	{
6444
+		return (array) apply_filters(
6445
+			'FHEE__EEM_Base__valid_cap_contexts',
6446
+			[
6447
+				self::caps_read,
6448
+				self::caps_read_admin,
6449
+				self::caps_edit,
6450
+				self::caps_delete,
6451
+			]
6452
+		);
6453
+	}
6454
+
6455
+
6456
+	/**
6457
+	 * Returns all valid options for 'default_where_conditions'
6458
+	 *
6459
+	 * @return array
6460
+	 */
6461
+	public static function valid_default_where_conditions(): array
6462
+	{
6463
+		return [
6464
+			EEM_Base::default_where_conditions_all,
6465
+			EEM_Base::default_where_conditions_this_only,
6466
+			EEM_Base::default_where_conditions_others_only,
6467
+			EEM_Base::default_where_conditions_minimum_all,
6468
+			EEM_Base::default_where_conditions_minimum_others,
6469
+			EEM_Base::default_where_conditions_none,
6470
+		];
6471
+	}
6472
+
6473
+	// public static function default_where_conditions_full
6474
+
6475
+
6476
+	/**
6477
+	 * Verifies $context is one of EEM_Base::valid_cap_contexts(), if not it throws an exception
6478
+	 *
6479
+	 * @param string $context
6480
+	 * @return bool
6481
+	 * @throws EE_Error
6482
+	 */
6483
+	public static function verify_is_valid_cap_context($context): bool
6484
+	{
6485
+		$valid_cap_contexts = EEM_Base::valid_cap_contexts();
6486
+		if (in_array($context, $valid_cap_contexts)) {
6487
+			return true;
6488
+		}
6489
+		throw new EE_Error(
6490
+			sprintf(
6491
+				esc_html__(
6492
+					'Context "%1$s" passed into model "%2$s" is not a valid context. They are: %3$s',
6493
+					'event_espresso'
6494
+				),
6495
+				$context,
6496
+				'EEM_Base',
6497
+				implode(',', $valid_cap_contexts)
6498
+			)
6499
+		);
6500
+	}
6501
+
6502
+
6503
+	/**
6504
+	 * Clears all the models field caches. This is only useful when a sub-class
6505
+	 * might have added a field or something and these caches might be invalidated
6506
+	 */
6507
+	protected function _invalidate_field_caches()
6508
+	{
6509
+		$this->_cache_foreign_key_to_fields = [];
6510
+		$this->_cached_fields               = null;
6511
+		$this->_cached_fields_non_db_only   = null;
6512
+	}
6513
+
6514
+
6515
+	/**
6516
+	 * Gets the list of all the where query param keys that relate to logic instead of field names
6517
+	 * (eg "and", "or", "not").
6518
+	 *
6519
+	 * @return array
6520
+	 */
6521
+	public function logic_query_param_keys(): array
6522
+	{
6523
+		return $this->_logic_query_param_keys;
6524
+	}
6525
+
6526
+
6527
+	/**
6528
+	 * Determines whether or not the where query param array key is for a logic query param.
6529
+	 * Eg 'OR', 'not*', and 'and*because-i-say-so' should all return true, whereas
6530
+	 * 'ATT_fname', 'EVT_name*not-you-or-me', and 'ORG_name' should return false
6531
+	 *
6532
+	 * @param $query_param_key
6533
+	 * @return bool
6534
+	 */
6535
+	public function is_logic_query_param_key($query_param_key): bool
6536
+	{
6537
+		foreach ($this->logic_query_param_keys() as $logic_query_param_key) {
6538
+			if (
6539
+				$query_param_key === $logic_query_param_key
6540
+				|| strpos($query_param_key, $logic_query_param_key . '*') === 0
6541
+			) {
6542
+				return true;
6543
+			}
6544
+		}
6545
+		return false;
6546
+	}
6547
+
6548
+
6549
+	/**
6550
+	 * Returns true if this model has a password field on it (regardless of whether that password field has any content)
6551
+	 *
6552
+	 * @return boolean
6553
+	 * @since 4.9.74.p
6554
+	 */
6555
+	public function hasPassword(): bool
6556
+	{
6557
+		// if we don't yet know if there's a password field, find out and remember it for next time.
6558
+		if ($this->has_password_field === null) {
6559
+			$password_field           = $this->getPasswordField();
6560
+			$this->has_password_field = $password_field instanceof EE_Password_Field;
6561
+		}
6562
+		return $this->has_password_field;
6563
+	}
6564
+
6565
+
6566
+	/**
6567
+	 * Returns the password field on this model, if there is one
6568
+	 *
6569
+	 * @return EE_Password_Field|null
6570
+	 * @since 4.9.74.p
6571
+	 */
6572
+	public function getPasswordField()
6573
+	{
6574
+		// if we definetely already know there is a password field or not (because has_password_field is true or false)
6575
+		// there's no need to search for it. If we don't know yet, then find out
6576
+		if ($this->has_password_field === null && $this->password_field === null) {
6577
+			$this->password_field = $this->get_a_field_of_type('EE_Password_Field');
6578
+		}
6579
+		// don't bother setting has_password_field because that's hasPassword()'s job.
6580
+		return $this->password_field;
6581
+	}
6582
+
6583
+
6584
+	/**
6585
+	 * Returns the list of field (as EE_Model_Field_Bases) that are protected by the password
6586
+	 *
6587
+	 * @return EE_Model_Field_Base[]
6588
+	 * @throws EE_Error
6589
+	 * @since 4.9.74.p
6590
+	 */
6591
+	public function getPasswordProtectedFields()
6592
+	{
6593
+		$password_field = $this->getPasswordField();
6594
+		$fields         = [];
6595
+		if ($password_field instanceof EE_Password_Field) {
6596
+			$field_names = $password_field->protectedFields();
6597
+			foreach ($field_names as $field_name) {
6598
+				$fields[ $field_name ] = $this->field_settings_for($field_name);
6599
+			}
6600
+		}
6601
+		return $fields;
6602
+	}
6603
+
6604
+
6605
+	/**
6606
+	 * Checks if the current user can perform the requested action on this model
6607
+	 *
6608
+	 * @param string              $cap_to_check one of the array keys from _cap_contexts_to_cap_action_map
6609
+	 * @param EE_Base_Class|array $model_obj_or_fields_n_values
6610
+	 * @return bool
6611
+	 * @throws EE_Error
6612
+	 * @throws InvalidArgumentException
6613
+	 * @throws InvalidDataTypeException
6614
+	 * @throws InvalidInterfaceException
6615
+	 * @throws ReflectionException
6616
+	 * @throws UnexpectedEntityException
6617
+	 * @since 4.9.74.p
6618
+	 */
6619
+	public function currentUserCan($cap_to_check, $model_obj_or_fields_n_values)
6620
+	{
6621
+		if ($model_obj_or_fields_n_values instanceof EE_Base_Class) {
6622
+			$model_obj_or_fields_n_values = $model_obj_or_fields_n_values->model_field_array();
6623
+		}
6624
+		if (! is_array($model_obj_or_fields_n_values)) {
6625
+			throw new UnexpectedEntityException(
6626
+				$model_obj_or_fields_n_values,
6627
+				'EE_Base_Class',
6628
+				sprintf(
6629
+					esc_html__(
6630
+						'%1$s must be passed an `EE_Base_Class or an array of fields names with their values. You passed in something different.',
6631
+						'event_espresso'
6632
+					),
6633
+					__FUNCTION__
6634
+				)
6635
+			);
6636
+		}
6637
+		return $this->exists(
6638
+			$this->alter_query_params_to_restrict_by_ID(
6639
+				$this->get_index_primary_key_string($model_obj_or_fields_n_values),
6640
+				[
6641
+					'default_where_conditions' => 'none',
6642
+					'caps'                     => $cap_to_check,
6643
+				]
6644
+			)
6645
+		);
6646
+	}
6647
+
6648
+
6649
+	/**
6650
+	 * Returns the query param where conditions key to the password affecting this model.
6651
+	 * Eg on EEM_Event this would just be "password", on EEM_Datetime this would be "Event.password", etc.
6652
+	 *
6653
+	 * @return null|string
6654
+	 * @throws EE_Error
6655
+	 * @throws InvalidArgumentException
6656
+	 * @throws InvalidDataTypeException
6657
+	 * @throws InvalidInterfaceException
6658
+	 * @throws ModelConfigurationException
6659
+	 * @throws ReflectionException
6660
+	 * @since 4.9.74.p
6661
+	 */
6662
+	public function modelChainAndPassword()
6663
+	{
6664
+		if ($this->model_chain_to_password === null) {
6665
+			throw new ModelConfigurationException(
6666
+				$this,
6667
+				esc_html_x(
6668
+				// @codingStandardsIgnoreStart
6669
+					'Cannot exclude protected data because the model has not specified which model has the password.',
6670
+					// @codingStandardsIgnoreEnd
6671
+					'1: model name',
6672
+					'event_espresso'
6673
+				)
6674
+			);
6675
+		}
6676
+		if ($this->model_chain_to_password === '') {
6677
+			$model_with_password = $this;
6678
+		} else {
6679
+			if ($pos_of_period = strrpos($this->model_chain_to_password, '.')) {
6680
+				$last_model_in_chain = substr($this->model_chain_to_password, $pos_of_period + 1);
6681
+			} else {
6682
+				$last_model_in_chain = $this->model_chain_to_password;
6683
+			}
6684
+			$model_with_password = EE_Registry::instance()->load_model($last_model_in_chain);
6685
+		}
6686
+
6687
+		$password_field = $model_with_password->getPasswordField();
6688
+		if ($password_field instanceof EE_Password_Field) {
6689
+			$password_field_name = $password_field->get_name();
6690
+		} else {
6691
+			throw new ModelConfigurationException(
6692
+				$this,
6693
+				sprintf(
6694
+					esc_html_x(
6695
+						'This model claims related model "%1$s" should have a password field on it, but none was found. The model relation chain is "%2$s"',
6696
+						'1: model name, 2: special string',
6697
+						'event_espresso'
6698
+					),
6699
+					$model_with_password->get_this_model_name(),
6700
+					$this->model_chain_to_password
6701
+				)
6702
+			);
6703
+		}
6704
+		return (
6705
+			   $this->model_chain_to_password
6706
+				   ? $this->model_chain_to_password . '.'
6707
+				   : ''
6708
+			   ) . $password_field_name;
6709
+	}
6710
+
6711
+
6712
+	/**
6713
+	 * Returns true if there is a password on a related model which restricts access to some of this model's rows,
6714
+	 * or if this model itself has a password affecting access to some of its other fields.
6715
+	 *
6716
+	 * @return boolean
6717
+	 * @since 4.9.74.p
6718
+	 */
6719
+	public function restrictedByRelatedModelPassword(): bool
6720
+	{
6721
+		return $this->model_chain_to_password !== null;
6722
+	}
6723 6723
 }
Please login to merge, or discard this patch.
Spacing   +231 added lines, -231 removed lines patch added patch discarded remove patch
@@ -567,7 +567,7 @@  discard block
 block discarded – undo
567 567
     protected function __construct($timezone = '')
568 568
     {
569 569
         // check that the model has not been loaded too soon
570
-        if (! did_action('AHEE__EE_System__load_espresso_addons')) {
570
+        if ( ! did_action('AHEE__EE_System__load_espresso_addons')) {
571 571
             throw new EE_Error(
572 572
                 sprintf(
573 573
                     esc_html__(
@@ -590,7 +590,7 @@  discard block
 block discarded – undo
590 590
          *
591 591
          * @var EE_Table_Base[] $_tables
592 592
          */
593
-        $this->_tables = (array) apply_filters('FHEE__' . get_class($this) . '__construct__tables', $this->_tables);
593
+        $this->_tables = (array) apply_filters('FHEE__'.get_class($this).'__construct__tables', $this->_tables);
594 594
         foreach ($this->_tables as $table_alias => $table_obj) {
595 595
             /** @var $table_obj EE_Table_Base */
596 596
             $table_obj->_construct_finalize_with_alias($table_alias);
@@ -604,10 +604,10 @@  discard block
 block discarded – undo
604 604
          *
605 605
          * @param EE_Model_Field_Base[] $_fields
606 606
          */
607
-        $this->_fields = (array) apply_filters('FHEE__' . get_class($this) . '__construct__fields', $this->_fields);
607
+        $this->_fields = (array) apply_filters('FHEE__'.get_class($this).'__construct__fields', $this->_fields);
608 608
         $this->_invalidate_field_caches();
609 609
         foreach ($this->_fields as $table_alias => $fields_for_table) {
610
-            if (! array_key_exists($table_alias, $this->_tables)) {
610
+            if ( ! array_key_exists($table_alias, $this->_tables)) {
611 611
                 throw new EE_Error(
612 612
                     sprintf(
613 613
                         esc_html__(
@@ -644,7 +644,7 @@  discard block
 block discarded – undo
644 644
          * @param EE_Model_Relation_Base[] $_model_relations
645 645
          */
646 646
         $this->_model_relations = (array) apply_filters(
647
-            'FHEE__' . get_class($this) . '__construct__model_relations',
647
+            'FHEE__'.get_class($this).'__construct__model_relations',
648 648
             $this->_model_relations
649 649
         );
650 650
         foreach ($this->_model_relations as $model_name => $relation_obj) {
@@ -656,12 +656,12 @@  discard block
 block discarded – undo
656 656
         }
657 657
         $this->set_timezone($timezone);
658 658
         // finalize default where condition strategy, or set default
659
-        if (! $this->_default_where_conditions_strategy) {
659
+        if ( ! $this->_default_where_conditions_strategy) {
660 660
             // nothing was set during child constructor, so set default
661 661
             $this->_default_where_conditions_strategy = new EE_Default_Where_Conditions();
662 662
         }
663 663
         $this->_default_where_conditions_strategy->_finalize_construct($this);
664
-        if (! $this->_minimum_where_conditions_strategy) {
664
+        if ( ! $this->_minimum_where_conditions_strategy) {
665 665
             // nothing was set during child constructor, so set default
666 666
             $this->_minimum_where_conditions_strategy = new EE_Default_Where_Conditions();
667 667
         }
@@ -674,8 +674,8 @@  discard block
 block discarded – undo
674 674
         // initialize the standard cap restriction generators if none were specified by the child constructor
675 675
         if (is_array($this->_cap_restriction_generators)) {
676 676
             foreach ($this->cap_contexts_to_cap_action_map() as $cap_context => $action) {
677
-                if (! isset($this->_cap_restriction_generators[ $cap_context ])) {
678
-                    $this->_cap_restriction_generators[ $cap_context ] = apply_filters(
677
+                if ( ! isset($this->_cap_restriction_generators[$cap_context])) {
678
+                    $this->_cap_restriction_generators[$cap_context] = apply_filters(
679 679
                         'FHEE__EEM_Base___construct__standard_cap_restriction_generator',
680 680
                         new EE_Restriction_Generator_Protected(),
681 681
                         $cap_context,
@@ -687,10 +687,10 @@  discard block
 block discarded – undo
687 687
         // if there are cap restriction generators, use them to make the default cap restrictions
688 688
         if (is_array($this->_cap_restriction_generators)) {
689 689
             foreach ($this->_cap_restriction_generators as $context => $generator_object) {
690
-                if (! $generator_object) {
690
+                if ( ! $generator_object) {
691 691
                     continue;
692 692
                 }
693
-                if (! $generator_object instanceof EE_Restriction_Generator_Base) {
693
+                if ( ! $generator_object instanceof EE_Restriction_Generator_Base) {
694 694
                     throw new EE_Error(
695 695
                         sprintf(
696 696
                             esc_html__(
@@ -703,12 +703,12 @@  discard block
 block discarded – undo
703 703
                     );
704 704
                 }
705 705
                 $action = $this->cap_action_for_context($context);
706
-                if (! $generator_object->construction_finalized()) {
706
+                if ( ! $generator_object->construction_finalized()) {
707 707
                     $generator_object->_construct_finalize($this, $action);
708 708
                 }
709 709
             }
710 710
         }
711
-        do_action('AHEE__' . get_class($this) . '__construct__end');
711
+        do_action('AHEE__'.get_class($this).'__construct__end');
712 712
     }
713 713
 
714 714
 
@@ -720,7 +720,7 @@  discard block
 block discarded – undo
720 720
      */
721 721
     protected static function getLoader(): LoaderInterface
722 722
     {
723
-        if (! EEM_Base::$loader instanceof LoaderInterface) {
723
+        if ( ! EEM_Base::$loader instanceof LoaderInterface) {
724 724
             EEM_Base::$loader = LoaderFactory::getLoader();
725 725
         }
726 726
         return EEM_Base::$loader;
@@ -733,7 +733,7 @@  discard block
 block discarded – undo
733 733
      */
734 734
     private static function getMirror(): Mirror
735 735
     {
736
-        if (! EEM_Base::$mirror instanceof Mirror) {
736
+        if ( ! EEM_Base::$mirror instanceof Mirror) {
737 737
             EEM_Base::$mirror = EEM_Base::getLoader()->getShared(Mirror::class);
738 738
         }
739 739
         return EEM_Base::$mirror;
@@ -789,7 +789,7 @@  discard block
 block discarded – undo
789 789
     public static function instance($timezone = '')
790 790
     {
791 791
         // check if instance of Espresso_model already exists
792
-        if (! static::$_instance instanceof static) {
792
+        if ( ! static::$_instance instanceof static) {
793 793
             $arguments = EEM_Base::getModelArguments(static::class, (string) $timezone);
794 794
             $model     = new static(...$arguments);
795 795
             EEM_Base::getLoader()->share(static::class, $model, $arguments);
@@ -818,7 +818,7 @@  discard block
 block discarded – undo
818 818
      */
819 819
     public static function reset($timezone = '')
820 820
     {
821
-        if (! static::$_instance instanceof EEM_Base) {
821
+        if ( ! static::$_instance instanceof EEM_Base) {
822 822
             return null;
823 823
         }
824 824
         // Let's NOT swap out the current instance for a new one
@@ -829,7 +829,7 @@  discard block
 block discarded – undo
829 829
         foreach (EEM_Base::getMirror()->getDefaultProperties(static::class) as $property => $value) {
830 830
             // don't set instance to null like it was originally,
831 831
             // but it's static anyways, and we're ignoring static properties (for now at least)
832
-            if (! isset($static_properties[ $property ])) {
832
+            if ( ! isset($static_properties[$property])) {
833 833
                 static::$_instance->{$property} = $value;
834 834
             }
835 835
         }
@@ -878,7 +878,7 @@  discard block
 block discarded – undo
878 878
      */
879 879
     public function status_array($translated = false)
880 880
     {
881
-        if (! array_key_exists('Status', $this->_model_relations)) {
881
+        if ( ! array_key_exists('Status', $this->_model_relations)) {
882 882
             return [];
883 883
         }
884 884
         $model_name   = $this->get_this_model_name();
@@ -886,7 +886,7 @@  discard block
 block discarded – undo
886 886
         $stati        = EEM_Status::instance()->get_all([['STS_type' => $status_type]]);
887 887
         $status_array = [];
888 888
         foreach ($stati as $status) {
889
-            $status_array[ $status->ID() ] = $status->get('STS_code');
889
+            $status_array[$status->ID()] = $status->get('STS_code');
890 890
         }
891 891
         return $translated
892 892
             ? EEM_Status::instance()->localized_status($status_array, false, 'sentence')
@@ -951,7 +951,7 @@  discard block
 block discarded – undo
951 951
     {
952 952
         $wp_user_field_name = $this->wp_user_field_name();
953 953
         if ($wp_user_field_name) {
954
-            $query_params[0][ $wp_user_field_name ] = get_current_user_id();
954
+            $query_params[0][$wp_user_field_name] = get_current_user_id();
955 955
         }
956 956
         return $query_params;
957 957
     }
@@ -970,17 +970,17 @@  discard block
 block discarded – undo
970 970
     public function wp_user_field_name()
971 971
     {
972 972
         try {
973
-            if (! empty($this->_model_chain_to_wp_user)) {
973
+            if ( ! empty($this->_model_chain_to_wp_user)) {
974 974
                 $models_to_follow_to_wp_users = explode('.', $this->_model_chain_to_wp_user);
975 975
                 $last_model_name              = end($models_to_follow_to_wp_users);
976 976
                 $model_with_fk_to_wp_users    = EE_Registry::instance()->load_model($last_model_name);
977
-                $model_chain_to_wp_user       = $this->_model_chain_to_wp_user . '.';
977
+                $model_chain_to_wp_user       = $this->_model_chain_to_wp_user.'.';
978 978
             } else {
979 979
                 $model_with_fk_to_wp_users = $this;
980 980
                 $model_chain_to_wp_user    = '';
981 981
             }
982 982
             $wp_user_field = $model_with_fk_to_wp_users->get_foreign_key_to('WP_User');
983
-            return $model_chain_to_wp_user . $wp_user_field->get_name();
983
+            return $model_chain_to_wp_user.$wp_user_field->get_name();
984 984
         } catch (EE_Error $e) {
985 985
             return false;
986 986
         }
@@ -1056,11 +1056,11 @@  discard block
 block discarded – undo
1056 1056
         if ($this->_custom_selections instanceof CustomSelects) {
1057 1057
             $custom_expressions = $this->_custom_selections->columnsToSelectExpression();
1058 1058
             $select_expressions .= $select_expressions
1059
-                ? ', ' . $custom_expressions
1059
+                ? ', '.$custom_expressions
1060 1060
                 : $custom_expressions;
1061 1061
         }
1062 1062
 
1063
-        $SQL = "SELECT $select_expressions " . $this->_construct_2nd_half_of_select_query($model_query_info);
1063
+        $SQL = "SELECT $select_expressions ".$this->_construct_2nd_half_of_select_query($model_query_info);
1064 1064
         return $this->_do_wpdb_query('get_results', [$SQL, $output]);
1065 1065
     }
1066 1066
 
@@ -1077,7 +1077,7 @@  discard block
 block discarded – undo
1077 1077
      */
1078 1078
     protected function getCustomSelection(array $query_params, $columns_to_select = null)
1079 1079
     {
1080
-        if (! isset($query_params['extra_selects']) && $columns_to_select === null) {
1080
+        if ( ! isset($query_params['extra_selects']) && $columns_to_select === null) {
1081 1081
             return null;
1082 1082
         }
1083 1083
         $selects = $query_params['extra_selects'] ?? $columns_to_select;
@@ -1128,7 +1128,7 @@  discard block
 block discarded – undo
1128 1128
         if (is_array($columns_to_select)) {
1129 1129
             $select_sql_array = [];
1130 1130
             foreach ($columns_to_select as $alias => $selection_and_datatype) {
1131
-                if (! is_array($selection_and_datatype) || ! isset($selection_and_datatype[1])) {
1131
+                if ( ! is_array($selection_and_datatype) || ! isset($selection_and_datatype[1])) {
1132 1132
                     throw new EE_Error(
1133 1133
                         sprintf(
1134 1134
                             esc_html__(
@@ -1140,7 +1140,7 @@  discard block
 block discarded – undo
1140 1140
                         )
1141 1141
                     );
1142 1142
                 }
1143
-                if (! in_array($selection_and_datatype[1], $this->_valid_wpdb_data_types, true)) {
1143
+                if ( ! in_array($selection_and_datatype[1], $this->_valid_wpdb_data_types, true)) {
1144 1144
                     throw new EE_Error(
1145 1145
                         sprintf(
1146 1146
                             esc_html__(
@@ -1200,7 +1200,7 @@  discard block
 block discarded – undo
1200 1200
                 ['default_where_conditions' => EEM_Base::default_where_conditions_minimum_all]
1201 1201
             )
1202 1202
         );
1203
-        $className    = $this->_get_class_name();
1203
+        $className = $this->_get_class_name();
1204 1204
         if ($model_object instanceof $className) {
1205 1205
             // make sure valid objects get added to the entity map
1206 1206
             // so that the next call to this method doesn't trigger another trip to the db
@@ -1223,12 +1223,12 @@  discard block
 block discarded – undo
1223 1223
      */
1224 1224
     public function alter_query_params_to_restrict_by_ID($id, $query_params = [])
1225 1225
     {
1226
-        if (! isset($query_params[0])) {
1226
+        if ( ! isset($query_params[0])) {
1227 1227
             $query_params[0] = [];
1228 1228
         }
1229 1229
         $conditions_from_id = $this->parse_index_primary_key_string($id);
1230 1230
         if ($conditions_from_id === null) {
1231
-            $query_params[0][ $this->primary_key_name() ] = $id;
1231
+            $query_params[0][$this->primary_key_name()] = $id;
1232 1232
         } else {
1233 1233
             // no primary key, so the $id must be from the get_index_primary_key_string()
1234 1234
             $query_params[0] = array_replace_recursive($query_params[0], $this->parse_index_primary_key_string($id));
@@ -1248,7 +1248,7 @@  discard block
 block discarded – undo
1248 1248
      */
1249 1249
     public function get_one($query_params = [])
1250 1250
     {
1251
-        if (! is_array($query_params)) {
1251
+        if ( ! is_array($query_params)) {
1252 1252
             EE_Error::doing_it_wrong(
1253 1253
                 'EEM_Base::get_one',
1254 1254
                 sprintf(
@@ -1457,7 +1457,7 @@  discard block
 block discarded – undo
1457 1457
                 return [];
1458 1458
             }
1459 1459
         }
1460
-        if (! is_array($query_params)) {
1460
+        if ( ! is_array($query_params)) {
1461 1461
             EE_Error::doing_it_wrong(
1462 1462
                 'EEM_Base::_get_consecutive',
1463 1463
                 sprintf(
@@ -1469,7 +1469,7 @@  discard block
 block discarded – undo
1469 1469
             $query_params = [];
1470 1470
         }
1471 1471
         // let's add the where query param for consecutive look up.
1472
-        $query_params[0][ $field_to_order_by ] = [$operand, $current_field_value];
1472
+        $query_params[0][$field_to_order_by] = [$operand, $current_field_value];
1473 1473
         $query_params['limit']                 = $limit;
1474 1474
         // set direction
1475 1475
         $incoming_orderby         = isset($query_params['order_by'])
@@ -1495,7 +1495,7 @@  discard block
 block discarded – undo
1495 1495
      */
1496 1496
     public function set_timezone(?string $timezone = '')
1497 1497
     {
1498
-        if (! $timezone) {
1498
+        if ( ! $timezone) {
1499 1499
             return;
1500 1500
         }
1501 1501
         $this->_timezone = $timezone;
@@ -1552,7 +1552,7 @@  discard block
 block discarded – undo
1552 1552
     {
1553 1553
         $field_settings = $this->field_settings_for($field_name);
1554 1554
         // if not a valid EE_Datetime_Field then throw error
1555
-        if (! $field_settings instanceof EE_Datetime_Field) {
1555
+        if ( ! $field_settings instanceof EE_Datetime_Field) {
1556 1556
             throw new EE_Error(
1557 1557
                 sprintf(
1558 1558
                     esc_html__(
@@ -1639,7 +1639,7 @@  discard block
 block discarded – undo
1639 1639
         // just using this to ensure the timezone is set correctly internally
1640 1640
         $this->get_formats_for($field_name);
1641 1641
         // load EEH_DTT_Helper
1642
-        $timezone_string     = ! empty($timezone_string) ? $timezone_string : EEH_DTT_Helper::get_timezone();
1642
+        $timezone_string = ! empty($timezone_string) ? $timezone_string : EEH_DTT_Helper::get_timezone();
1643 1643
         $incomingDateTime = date_create_from_format($incoming_format, $timestring, new DateTimeZone($timezone_string));
1644 1644
         EEH_DTT_Helper::setTimezone($incomingDateTime, new DateTimeZone($this->_timezone));
1645 1645
         return DbSafeDateTime::createFromDateTime($incomingDateTime);
@@ -1709,7 +1709,7 @@  discard block
 block discarded – undo
1709 1709
      */
1710 1710
     public function update($fields_n_values, $query_params, $keep_model_objs_in_sync = true)
1711 1711
     {
1712
-        if (! is_array($query_params)) {
1712
+        if ( ! is_array($query_params)) {
1713 1713
             EE_Error::doing_it_wrong(
1714 1714
                 'EEM_Base::update',
1715 1715
                 sprintf(
@@ -1757,7 +1757,7 @@  discard block
 block discarded – undo
1757 1757
             $wpdb_result = (array) $wpdb_result;
1758 1758
             // get the model object's PK, as we'll want this if we need to insert a row into secondary tables
1759 1759
             if ($this->has_primary_key_field()) {
1760
-                $main_table_pk_value = $wpdb_result[ $this->get_primary_key_field()->get_qualified_column() ];
1760
+                $main_table_pk_value = $wpdb_result[$this->get_primary_key_field()->get_qualified_column()];
1761 1761
             } else {
1762 1762
                 // if there's no primary key, we basically can't support having a 2nd table on the model (we could but it would be lots of work)
1763 1763
                 $main_table_pk_value = null;
@@ -1773,7 +1773,7 @@  discard block
 block discarded – undo
1773 1773
                     // in this table, right? so insert a row in the current table, using any fields available
1774 1774
                     if (
1775 1775
                         ! (array_key_exists($this_table_pk_column, $wpdb_result)
1776
-                           && $wpdb_result[ $this_table_pk_column ])
1776
+                           && $wpdb_result[$this_table_pk_column])
1777 1777
                     ) {
1778 1778
                         $success = $this->_insert_into_specific_table(
1779 1779
                             $table_obj,
@@ -1781,7 +1781,7 @@  discard block
 block discarded – undo
1781 1781
                             $main_table_pk_value
1782 1782
                         );
1783 1783
                         // if we died here, report the error
1784
-                        if (! $success) {
1784
+                        if ( ! $success) {
1785 1785
                             return false;
1786 1786
                         }
1787 1787
                     }
@@ -1809,10 +1809,10 @@  discard block
 block discarded – undo
1809 1809
                 $model_objs_affected_ids     = [];
1810 1810
                 foreach ($models_affected_key_columns as $row) {
1811 1811
                     $combined_index_key                             = $this->get_index_primary_key_string($row);
1812
-                    $model_objs_affected_ids[ $combined_index_key ] = $combined_index_key;
1812
+                    $model_objs_affected_ids[$combined_index_key] = $combined_index_key;
1813 1813
                 }
1814 1814
             }
1815
-            if (! $model_objs_affected_ids) {
1815
+            if ( ! $model_objs_affected_ids) {
1816 1816
                 // wait wait wait- if nothing was affected let's stop here
1817 1817
                 return 0;
1818 1818
             }
@@ -1841,8 +1841,8 @@  discard block
 block discarded – undo
1841 1841
         $rows_affected = $this->_do_wpdb_query(
1842 1842
             'query',
1843 1843
             [
1844
-                "UPDATE " . $model_query_info->get_full_join_sql()
1845
-                . " SET " . $this->_construct_update_sql($fields_n_values)
1844
+                "UPDATE ".$model_query_info->get_full_join_sql()
1845
+                . " SET ".$this->_construct_update_sql($fields_n_values)
1846 1846
                 . $model_query_info->get_where_sql(),
1847 1847
             ]
1848 1848
         );
@@ -1856,7 +1856,7 @@  discard block
 block discarded – undo
1856 1856
          * @param int      $rows_affected
1857 1857
          */
1858 1858
         do_action('AHEE__EEM_Base__update__end', $this, $fields_n_values, $query_params, $rows_affected);
1859
-        return $rows_affected;// how many supposedly got updated
1859
+        return $rows_affected; // how many supposedly got updated
1860 1860
     }
1861 1861
 
1862 1862
 
@@ -1889,7 +1889,7 @@  discard block
 block discarded – undo
1889 1889
         $model_query_info   = $this->_create_model_query_info_carrier($query_params);
1890 1890
         $select_expressions = $field->get_qualified_column();
1891 1891
         $SQL                =
1892
-            "SELECT $select_expressions " . $this->_construct_2nd_half_of_select_query($model_query_info);
1892
+            "SELECT $select_expressions ".$this->_construct_2nd_half_of_select_query($model_query_info);
1893 1893
         return $this->_do_wpdb_query('get_col', [$SQL]);
1894 1894
     }
1895 1895
 
@@ -1908,7 +1908,7 @@  discard block
 block discarded – undo
1908 1908
     {
1909 1909
         $query_params['limit'] = 1;
1910 1910
         $col                   = $this->get_col($query_params, $field_to_select);
1911
-        if (! empty($col)) {
1911
+        if ( ! empty($col)) {
1912 1912
             return reset($col);
1913 1913
         }
1914 1914
         return null;
@@ -1939,7 +1939,7 @@  discard block
 block discarded – undo
1939 1939
             $value_sql       = $prepared_value === null
1940 1940
                 ? 'NULL'
1941 1941
                 : $wpdb->prepare($field_obj->get_wpdb_data_type(), $prepared_value);
1942
-            $cols_n_values[] = $field_obj->get_qualified_column() . "=" . $value_sql;
1942
+            $cols_n_values[] = $field_obj->get_qualified_column()."=".$value_sql;
1943 1943
         }
1944 1944
         return implode(",", $cols_n_values);
1945 1945
     }
@@ -2073,7 +2073,7 @@  discard block
 block discarded – undo
2073 2073
                                 . $model_query_info->get_full_join_sql()
2074 2074
                                 . " WHERE "
2075 2075
                                 . $deletion_where_query_part;
2076
-            $rows_deleted     = $this->_do_wpdb_query('query', [$SQL]);
2076
+            $rows_deleted = $this->_do_wpdb_query('query', [$SQL]);
2077 2077
         } else {
2078 2078
             $rows_deleted = 0;
2079 2079
         }
@@ -2083,12 +2083,12 @@  discard block
 block discarded – undo
2083 2083
         if (
2084 2084
             $this->has_primary_key_field()
2085 2085
             && $rows_deleted !== false
2086
-            && isset($columns_and_ids_for_deleting[ $this->get_primary_key_field()->get_qualified_column() ])
2086
+            && isset($columns_and_ids_for_deleting[$this->get_primary_key_field()->get_qualified_column()])
2087 2087
         ) {
2088
-            $ids_for_removal = $columns_and_ids_for_deleting[ $this->get_primary_key_field()->get_qualified_column() ];
2088
+            $ids_for_removal = $columns_and_ids_for_deleting[$this->get_primary_key_field()->get_qualified_column()];
2089 2089
             foreach ($ids_for_removal as $id) {
2090
-                if (isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])) {
2091
-                    unset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ]);
2090
+                if (isset($this->_entity_map[EEM_Base::$_model_query_blog_id][$id])) {
2091
+                    unset($this->_entity_map[EEM_Base::$_model_query_blog_id][$id]);
2092 2092
                 }
2093 2093
             }
2094 2094
 
@@ -2127,7 +2127,7 @@  discard block
 block discarded – undo
2127 2127
          * @param int      $rows_deleted
2128 2128
          */
2129 2129
         do_action('AHEE__EEM_Base__delete__end', $this, $query_params, $rows_deleted, $columns_and_ids_for_deleting);
2130
-        return $rows_deleted;// how many supposedly got deleted
2130
+        return $rows_deleted; // how many supposedly got deleted
2131 2131
     }
2132 2132
 
2133 2133
 
@@ -2226,15 +2226,15 @@  discard block
 block discarded – undo
2226 2226
                 if (
2227 2227
                     $allow_blocking
2228 2228
                     && $this->delete_is_blocked_by_related_models(
2229
-                        $item_to_delete[ $primary_table->get_fully_qualified_pk_column() ]
2229
+                        $item_to_delete[$primary_table->get_fully_qualified_pk_column()]
2230 2230
                     )
2231 2231
                 ) {
2232 2232
                     continue;
2233 2233
                 }
2234 2234
                 // primary table deletes
2235
-                if (isset($item_to_delete[ $primary_table->get_fully_qualified_pk_column() ])) {
2236
-                    $ids_to_delete_indexed_by_column[ $primary_table->get_fully_qualified_pk_column() ][] =
2237
-                        $item_to_delete[ $primary_table->get_fully_qualified_pk_column() ];
2235
+                if (isset($item_to_delete[$primary_table->get_fully_qualified_pk_column()])) {
2236
+                    $ids_to_delete_indexed_by_column[$primary_table->get_fully_qualified_pk_column()][] =
2237
+                        $item_to_delete[$primary_table->get_fully_qualified_pk_column()];
2238 2238
                 }
2239 2239
             }
2240 2240
         } elseif (count($this->get_combined_primary_key_fields()) > 1) {
@@ -2243,8 +2243,8 @@  discard block
 block discarded – undo
2243 2243
                 $ids_to_delete_indexed_by_column_for_row = [];
2244 2244
                 foreach ($fields as $cpk_field) {
2245 2245
                     if ($cpk_field instanceof EE_Model_Field_Base) {
2246
-                        $ids_to_delete_indexed_by_column_for_row[ $cpk_field->get_qualified_column() ] =
2247
-                            $item_to_delete[ $cpk_field->get_qualified_column() ];
2246
+                        $ids_to_delete_indexed_by_column_for_row[$cpk_field->get_qualified_column()] =
2247
+                            $item_to_delete[$cpk_field->get_qualified_column()];
2248 2248
                     }
2249 2249
                 }
2250 2250
                 $ids_to_delete_indexed_by_column[] = $ids_to_delete_indexed_by_column_for_row;
@@ -2282,7 +2282,7 @@  discard block
 block discarded – undo
2282 2282
         } elseif ($this->has_primary_key_field()) {
2283 2283
             $query = [];
2284 2284
             foreach ($ids_to_delete_indexed_by_column as $column => $ids) {
2285
-                $query[] = $column . ' IN' . $this->_construct_in_value($ids, $this->_primary_key_field);
2285
+                $query[] = $column.' IN'.$this->_construct_in_value($ids, $this->_primary_key_field);
2286 2286
             }
2287 2287
             $query_part = ! empty($query)
2288 2288
                 ? implode(' AND ', $query)
@@ -2292,7 +2292,7 @@  discard block
 block discarded – undo
2292 2292
             foreach ($ids_to_delete_indexed_by_column as $ids_to_delete_indexed_by_column_for_each_row) {
2293 2293
                 $values_for_each_combined_primary_key_for_a_row = [];
2294 2294
                 foreach ($ids_to_delete_indexed_by_column_for_each_row as $column => $id) {
2295
-                    $values_for_each_combined_primary_key_for_a_row[] = $column . '=' . $id;
2295
+                    $values_for_each_combined_primary_key_for_a_row[] = $column.'='.$id;
2296 2296
                 }
2297 2297
                 $ways_to_identify_a_row[] = '('
2298 2298
                                             . implode(' AND ', $values_for_each_combined_primary_key_for_a_row)
@@ -2368,10 +2368,10 @@  discard block
 block discarded – undo
2368 2368
             }
2369 2369
         }
2370 2370
         $column_to_count = $distinct
2371
-            ? "DISTINCT " . $column_to_count
2371
+            ? "DISTINCT ".$column_to_count
2372 2372
             : $column_to_count;
2373 2373
         $SQL             =
2374
-            "SELECT COUNT(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2374
+            "SELECT COUNT(".$column_to_count.")".$this->_construct_2nd_half_of_select_query($model_query_info);
2375 2375
         return (int) $this->_do_wpdb_query('get_var', [$SQL]);
2376 2376
     }
2377 2377
 
@@ -2396,7 +2396,7 @@  discard block
 block discarded – undo
2396 2396
         }
2397 2397
         $column_to_count = $field_obj->get_qualified_column();
2398 2398
         $SQL             =
2399
-            "SELECT SUM(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2399
+            "SELECT SUM(".$column_to_count.")".$this->_construct_2nd_half_of_select_query($model_query_info);
2400 2400
         $return_value    = $this->_do_wpdb_query('get_var', [$SQL]);
2401 2401
         $data_type       = $field_obj->get_wpdb_data_type();
2402 2402
         if ($data_type === '%d' || $data_type === '%s') {
@@ -2432,7 +2432,7 @@  discard block
 block discarded – undo
2432 2432
         }
2433 2433
         /** @type WPDB $wpdb */
2434 2434
         global $wpdb;
2435
-        if (! method_exists($wpdb, $wpdb_method)) {
2435
+        if ( ! method_exists($wpdb, $wpdb_method)) {
2436 2436
             throw new DomainException(
2437 2437
                 sprintf(
2438 2438
                     esc_html__(
@@ -2451,7 +2451,7 @@  discard block
 block discarded – undo
2451 2451
         $this->show_db_query_if_previously_requested($wpdb->last_query);
2452 2452
         if (WP_DEBUG) {
2453 2453
             $wpdb->show_errors($old_show_errors_value);
2454
-            if (! empty($wpdb->last_error)) {
2454
+            if ( ! empty($wpdb->last_error)) {
2455 2455
                 throw new EE_Error(sprintf(esc_html__('WPDB Error: "%s"', 'event_espresso'), $wpdb->last_error));
2456 2456
             }
2457 2457
             if ($result === false) {
@@ -2521,7 +2521,7 @@  discard block
 block discarded – undo
2521 2521
                     // ummmm... you in trouble
2522 2522
                     return $result;
2523 2523
             }
2524
-            if (! empty($error_message)) {
2524
+            if ( ! empty($error_message)) {
2525 2525
                 EE_Log::instance()->log(__FILE__, __FUNCTION__, $error_message, 'error');
2526 2526
                 trigger_error($error_message);
2527 2527
             }
@@ -2602,11 +2602,11 @@  discard block
 block discarded – undo
2602 2602
      */
2603 2603
     private function _construct_2nd_half_of_select_query(EE_Model_Query_Info_Carrier $model_query_info)
2604 2604
     {
2605
-        return " FROM " . $model_query_info->get_full_join_sql() .
2606
-               $model_query_info->get_where_sql() .
2607
-               $model_query_info->get_group_by_sql() .
2608
-               $model_query_info->get_having_sql() .
2609
-               $model_query_info->get_order_by_sql() .
2605
+        return " FROM ".$model_query_info->get_full_join_sql().
2606
+               $model_query_info->get_where_sql().
2607
+               $model_query_info->get_group_by_sql().
2608
+               $model_query_info->get_having_sql().
2609
+               $model_query_info->get_order_by_sql().
2610 2610
                $model_query_info->get_limit_sql();
2611 2611
     }
2612 2612
 
@@ -2631,7 +2631,7 @@  discard block
 block discarded – undo
2631 2631
             $left = is_admin() ? '12rem' : '2rem';
2632 2632
             echo "
2633 2633
             <div class='ee-status-outline ee-status-bg--attention' style='margin: 2rem 2rem 2rem $left;'>
2634
-                " . esc_html($sql_query) . "
2634
+                ".esc_html($sql_query)."
2635 2635
             </div>";
2636 2636
             $this->_show_next_x_db_queries--;
2637 2637
         }
@@ -2807,12 +2807,12 @@  discard block
 block discarded – undo
2807 2807
         $related_model = $this->get_related_model_obj($model_name);
2808 2808
         // we're just going to use the query params on the related model's normal get_all query,
2809 2809
         // except add a condition to say to match the current mod
2810
-        if (! isset($query_params['default_where_conditions'])) {
2810
+        if ( ! isset($query_params['default_where_conditions'])) {
2811 2811
             $query_params['default_where_conditions'] = EEM_Base::default_where_conditions_none;
2812 2812
         }
2813 2813
         $this_model_name                                                 = $this->get_this_model_name();
2814 2814
         $this_pk_field_name                                              = $this->get_primary_key_field()->get_name();
2815
-        $query_params[0][ $this_model_name . "." . $this_pk_field_name ] = $id_or_obj;
2815
+        $query_params[0][$this_model_name.".".$this_pk_field_name] = $id_or_obj;
2816 2816
         return $related_model->count($query_params, $field_to_count, $distinct);
2817 2817
     }
2818 2818
 
@@ -2833,7 +2833,7 @@  discard block
 block discarded – undo
2833 2833
     public function sum_related($id_or_obj, $model_name, $query_params, $field_to_sum = null)
2834 2834
     {
2835 2835
         $related_model = $this->get_related_model_obj($model_name);
2836
-        if (! is_array($query_params)) {
2836
+        if ( ! is_array($query_params)) {
2837 2837
             EE_Error::doing_it_wrong(
2838 2838
                 'EEM_Base::sum_related',
2839 2839
                 sprintf(
@@ -2846,12 +2846,12 @@  discard block
 block discarded – undo
2846 2846
         }
2847 2847
         // we're just going to use the query params on the related model's normal get_all query,
2848 2848
         // except add a condition to say to match the current mod
2849
-        if (! isset($query_params['default_where_conditions'])) {
2849
+        if ( ! isset($query_params['default_where_conditions'])) {
2850 2850
             $query_params['default_where_conditions'] = EEM_Base::default_where_conditions_none;
2851 2851
         }
2852 2852
         $this_model_name                                                 = $this->get_this_model_name();
2853 2853
         $this_pk_field_name                                              = $this->get_primary_key_field()->get_name();
2854
-        $query_params[0][ $this_model_name . "." . $this_pk_field_name ] = $id_or_obj;
2854
+        $query_params[0][$this_model_name.".".$this_pk_field_name] = $id_or_obj;
2855 2855
         return $related_model->sum($query_params, $field_to_sum);
2856 2856
     }
2857 2857
 
@@ -2903,7 +2903,7 @@  discard block
 block discarded – undo
2903 2903
                 $field_with_model_name = $field;
2904 2904
             }
2905 2905
         }
2906
-        if (! isset($field_with_model_name) || ! $field_with_model_name) {
2906
+        if ( ! isset($field_with_model_name) || ! $field_with_model_name) {
2907 2907
             throw new EE_Error(
2908 2908
                 sprintf(
2909 2909
                     esc_html__("There is no EE_Any_Foreign_Model_Name field on model %s", "event_espresso"),
@@ -3042,14 +3042,14 @@  discard block
 block discarded – undo
3042 3042
                 || $this->get_primary_key_field()
3043 3043
                    instanceof
3044 3044
                    EE_Primary_Key_String_Field)
3045
-            && isset($fields_n_values[ $this->primary_key_name() ])
3045
+            && isset($fields_n_values[$this->primary_key_name()])
3046 3046
         ) {
3047
-            $query_params[0]['OR'][ $this->primary_key_name() ] = $fields_n_values[ $this->primary_key_name() ];
3047
+            $query_params[0]['OR'][$this->primary_key_name()] = $fields_n_values[$this->primary_key_name()];
3048 3048
         }
3049 3049
         foreach ($this->unique_indexes() as $unique_index_name => $unique_index) {
3050 3050
             $uniqueness_where_params                              =
3051 3051
                 array_intersect_key($fields_n_values, $unique_index->fields());
3052
-            $query_params[0]['OR'][ 'AND*' . $unique_index_name ] = $uniqueness_where_params;
3052
+            $query_params[0]['OR']['AND*'.$unique_index_name] = $uniqueness_where_params;
3053 3053
         }
3054 3054
         // if there is nothing to base this search on, then we shouldn't find anything
3055 3055
         if (empty($query_params)) {
@@ -3126,16 +3126,16 @@  discard block
 block discarded – undo
3126 3126
             $prepared_value = $this->_prepare_value_or_use_default($field_obj, $fields_n_values);
3127 3127
             // if the value we want to assign it to is NULL, just don't mention it for the insertion
3128 3128
             if ($prepared_value !== null) {
3129
-                $insertion_col_n_values[ $field_obj->get_table_column() ] = $prepared_value;
3129
+                $insertion_col_n_values[$field_obj->get_table_column()] = $prepared_value;
3130 3130
                 $format_for_insertion[]                                   = $field_obj->get_wpdb_data_type();
3131 3131
             }
3132 3132
         }
3133 3133
         if ($table instanceof EE_Secondary_Table && $new_id) {
3134 3134
             // its not the main table, so we should have already saved the main table's PK which we just inserted
3135 3135
             // so add the fk to the main table as a column
3136
-            $insertion_col_n_values[ $table->get_fk_on_table() ] = $new_id;
3136
+            $insertion_col_n_values[$table->get_fk_on_table()] = $new_id;
3137 3137
             $format_for_insertion[]                              =
3138
-                '%d';// yes right now we're only allowing these foreign keys to be INTs
3138
+                '%d'; // yes right now we're only allowing these foreign keys to be INTs
3139 3139
         }
3140 3140
 
3141 3141
         // insert the new entry
@@ -3153,7 +3153,7 @@  discard block
 block discarded – undo
3153 3153
             }
3154 3154
             // it's not an auto-increment primary key, so
3155 3155
             // it must have been supplied
3156
-            return $fields_n_values[ $this->get_primary_key_field()->get_name() ];
3156
+            return $fields_n_values[$this->get_primary_key_field()->get_name()];
3157 3157
         }
3158 3158
         // we can't return a  primary key because there is none. instead return
3159 3159
         // a unique string indicating this model
@@ -3175,10 +3175,10 @@  discard block
 block discarded – undo
3175 3175
     {
3176 3176
         $field_name = $field_obj->get_name();
3177 3177
         // if this field doesn't allow nullable, don't allow it
3178
-        if (! $field_obj->is_nullable() && ! isset($fields_n_values[ $field_name ])) {
3179
-            $fields_n_values[ $field_name ] = $field_obj->get_default_value();
3178
+        if ( ! $field_obj->is_nullable() && ! isset($fields_n_values[$field_name])) {
3179
+            $fields_n_values[$field_name] = $field_obj->get_default_value();
3180 3180
         }
3181
-        $unprepared_value = $fields_n_values[ $field_name ] ?? null;
3181
+        $unprepared_value = $fields_n_values[$field_name] ?? null;
3182 3182
         return $this->_prepare_value_for_use_in_db($unprepared_value, $field_obj);
3183 3183
     }
3184 3184
 
@@ -3281,7 +3281,7 @@  discard block
 block discarded – undo
3281 3281
      */
3282 3282
     public function get_table_obj_by_alias($table_alias = '')
3283 3283
     {
3284
-        return $this->_tables[ $table_alias ] ?? null;
3284
+        return $this->_tables[$table_alias] ?? null;
3285 3285
     }
3286 3286
 
3287 3287
 
@@ -3295,7 +3295,7 @@  discard block
 block discarded – undo
3295 3295
         $other_tables = [];
3296 3296
         foreach ($this->_tables as $table_alias => $table) {
3297 3297
             if ($table instanceof EE_Secondary_Table) {
3298
-                $other_tables[ $table_alias ] = $table;
3298
+                $other_tables[$table_alias] = $table;
3299 3299
             }
3300 3300
         }
3301 3301
         return $other_tables;
@@ -3310,7 +3310,7 @@  discard block
 block discarded – undo
3310 3310
      */
3311 3311
     public function _get_fields_for_table($table_alias)
3312 3312
     {
3313
-        return $this->_fields[ $table_alias ];
3313
+        return $this->_fields[$table_alias];
3314 3314
     }
3315 3315
 
3316 3316
 
@@ -3339,7 +3339,7 @@  discard block
 block discarded – undo
3339 3339
                     $query_info_carrier,
3340 3340
                     'group_by'
3341 3341
                 );
3342
-            } elseif (! empty($query_params['group_by'])) {
3342
+            } elseif ( ! empty($query_params['group_by'])) {
3343 3343
                 $this->_extract_related_model_info_from_query_param(
3344 3344
                     $query_params['group_by'],
3345 3345
                     $query_info_carrier,
@@ -3361,7 +3361,7 @@  discard block
 block discarded – undo
3361 3361
                     $query_info_carrier,
3362 3362
                     'order_by'
3363 3363
                 );
3364
-            } elseif (! empty($query_params['order_by'])) {
3364
+            } elseif ( ! empty($query_params['order_by'])) {
3365 3365
                 $this->_extract_related_model_info_from_query_param(
3366 3366
                     $query_params['order_by'],
3367 3367
                     $query_info_carrier,
@@ -3396,7 +3396,7 @@  discard block
 block discarded – undo
3396 3396
         EE_Model_Query_Info_Carrier $model_query_info_carrier,
3397 3397
         $query_param_type
3398 3398
     ) {
3399
-        if (! empty($sub_query_params)) {
3399
+        if ( ! empty($sub_query_params)) {
3400 3400
             $sub_query_params = (array) $sub_query_params;
3401 3401
             foreach ($sub_query_params as $param => $possibly_array_of_params) {
3402 3402
                 // $param could be simply 'EVT_ID', or it could be 'Registrations.REG_ID', or even 'Registrations.Transactions.Payments.PAY_amount'
@@ -3412,7 +3412,7 @@  discard block
 block discarded – undo
3412 3412
                 $query_param_sans_stars =
3413 3413
                     $this->_remove_stars_and_anything_after_from_condition_query_param_key($param);
3414 3414
                 if (in_array($query_param_sans_stars, $this->_logic_query_param_keys, true)) {
3415
-                    if (! is_array($possibly_array_of_params)) {
3415
+                    if ( ! is_array($possibly_array_of_params)) {
3416 3416
                         throw new EE_Error(
3417 3417
                             sprintf(
3418 3418
                                 esc_html__(
@@ -3438,7 +3438,7 @@  discard block
 block discarded – undo
3438 3438
                     // then $possible_array_of_params looks something like array('<','DTT_sold',true)
3439 3439
                     // indicating that $possible_array_of_params[1] is actually a field name,
3440 3440
                     // from which we should extract query parameters!
3441
-                    if (! isset($possibly_array_of_params[0], $possibly_array_of_params[1])) {
3441
+                    if ( ! isset($possibly_array_of_params[0], $possibly_array_of_params[1])) {
3442 3442
                         throw new EE_Error(
3443 3443
                             sprintf(
3444 3444
                                 esc_html__(
@@ -3478,8 +3478,8 @@  discard block
 block discarded – undo
3478 3478
         EE_Model_Query_Info_Carrier $model_query_info_carrier,
3479 3479
         $query_param_type
3480 3480
     ) {
3481
-        if (! empty($sub_query_params)) {
3482
-            if (! is_array($sub_query_params)) {
3481
+        if ( ! empty($sub_query_params)) {
3482
+            if ( ! is_array($sub_query_params)) {
3483 3483
                 throw new EE_Error(
3484 3484
                     sprintf(
3485 3485
                         esc_html__("Query parameter %s should be an array, but it isn't.", "event_espresso"),
@@ -3517,7 +3517,7 @@  discard block
 block discarded – undo
3517 3517
      */
3518 3518
     public function _create_model_query_info_carrier($query_params)
3519 3519
     {
3520
-        if (! is_array($query_params)) {
3520
+        if ( ! is_array($query_params)) {
3521 3521
             EE_Error::doing_it_wrong(
3522 3522
                 'EEM_Base::_create_model_query_info_carrier',
3523 3523
                 sprintf(
@@ -3548,7 +3548,7 @@  discard block
 block discarded – undo
3548 3548
             // only include if related to a cpt where no password has been set
3549 3549
             $query_params[0]['OR*nopassword'] = [
3550 3550
                 $where_param_key_for_password       => '',
3551
-                $where_param_key_for_password . '*' => ['IS_NULL'],
3551
+                $where_param_key_for_password.'*' => ['IS_NULL'],
3552 3552
             ];
3553 3553
         }
3554 3554
         $query_object = $this->_extract_related_models_from_query($query_params);
@@ -3602,7 +3602,7 @@  discard block
 block discarded – undo
3602 3602
         // set limit
3603 3603
         if (array_key_exists('limit', $query_params)) {
3604 3604
             if (is_array($query_params['limit'])) {
3605
-                if (! isset($query_params['limit'][0], $query_params['limit'][1])) {
3605
+                if ( ! isset($query_params['limit'][0], $query_params['limit'][1])) {
3606 3606
                     $e = sprintf(
3607 3607
                         esc_html__(
3608 3608
                             "Invalid DB query. You passed '%s' for the LIMIT, but only the following are valid: an integer, string representing an integer, a string like 'int,int', or an array like array(int,int)",
@@ -3610,12 +3610,12 @@  discard block
 block discarded – undo
3610 3610
                         ),
3611 3611
                         http_build_query($query_params['limit'])
3612 3612
                     );
3613
-                    throw new EE_Error($e . "|" . $e);
3613
+                    throw new EE_Error($e."|".$e);
3614 3614
                 }
3615 3615
                 // they passed us an array for the limit. Assume it's like array(50,25), meaning offset by 50, and get 25
3616
-                $query_object->set_limit_sql(" LIMIT " . $query_params['limit'][0] . "," . $query_params['limit'][1]);
3617
-            } elseif (! empty($query_params['limit'])) {
3618
-                $query_object->set_limit_sql(" LIMIT " . $query_params['limit']);
3616
+                $query_object->set_limit_sql(" LIMIT ".$query_params['limit'][0].",".$query_params['limit'][1]);
3617
+            } elseif ( ! empty($query_params['limit'])) {
3618
+                $query_object->set_limit_sql(" LIMIT ".$query_params['limit']);
3619 3619
             }
3620 3620
         }
3621 3621
         // set order by
@@ -3647,10 +3647,10 @@  discard block
 block discarded – undo
3647 3647
                 $order_array = [];
3648 3648
                 foreach ($query_params['order_by'] as $field_name_to_order_by => $order) {
3649 3649
                     $order         = $this->_extract_order($order);
3650
-                    $order_array[] = $this->_deduce_column_name_from_query_param($field_name_to_order_by) . SP . $order;
3650
+                    $order_array[] = $this->_deduce_column_name_from_query_param($field_name_to_order_by).SP.$order;
3651 3651
                 }
3652
-                $query_object->set_order_by_sql(" ORDER BY " . implode(",", $order_array));
3653
-            } elseif (! empty($query_params['order_by'])) {
3652
+                $query_object->set_order_by_sql(" ORDER BY ".implode(",", $order_array));
3653
+            } elseif ( ! empty($query_params['order_by'])) {
3654 3654
                 $this->_extract_related_model_info_from_query_param(
3655 3655
                     $query_params['order_by'],
3656 3656
                     $query_object,
@@ -3661,7 +3661,7 @@  discard block
 block discarded – undo
3661 3661
                     ? $this->_extract_order($query_params['order'])
3662 3662
                     : 'DESC';
3663 3663
                 $query_object->set_order_by_sql(
3664
-                    " ORDER BY " . $this->_deduce_column_name_from_query_param($query_params['order_by']) . SP . $order
3664
+                    " ORDER BY ".$this->_deduce_column_name_from_query_param($query_params['order_by']).SP.$order
3665 3665
                 );
3666 3666
             }
3667 3667
         }
@@ -3673,7 +3673,7 @@  discard block
 block discarded – undo
3673 3673
         ) {
3674 3674
             $pk_field = $this->get_primary_key_field();
3675 3675
             $order    = $this->_extract_order($query_params['order']);
3676
-            $query_object->set_order_by_sql(" ORDER BY " . $pk_field->get_qualified_column() . SP . $order);
3676
+            $query_object->set_order_by_sql(" ORDER BY ".$pk_field->get_qualified_column().SP.$order);
3677 3677
         }
3678 3678
         // set group by
3679 3679
         if (array_key_exists('group_by', $query_params)) {
@@ -3683,10 +3683,10 @@  discard block
 block discarded – undo
3683 3683
                 foreach ($query_params['group_by'] as $field_name_to_group_by) {
3684 3684
                     $group_by_array[] = $this->_deduce_column_name_from_query_param($field_name_to_group_by);
3685 3685
                 }
3686
-                $query_object->set_group_by_sql(" GROUP BY " . implode(", ", $group_by_array));
3687
-            } elseif (! empty($query_params['group_by'])) {
3686
+                $query_object->set_group_by_sql(" GROUP BY ".implode(", ", $group_by_array));
3687
+            } elseif ( ! empty($query_params['group_by'])) {
3688 3688
                 $query_object->set_group_by_sql(
3689
-                    " GROUP BY " . $this->_deduce_column_name_from_query_param($query_params['group_by'])
3689
+                    " GROUP BY ".$this->_deduce_column_name_from_query_param($query_params['group_by'])
3690 3690
                 );
3691 3691
             }
3692 3692
         }
@@ -3696,7 +3696,7 @@  discard block
 block discarded – undo
3696 3696
         }
3697 3697
         // now, just verify they didn't pass anything wack
3698 3698
         foreach ($query_params as $query_key => $query_value) {
3699
-            if (! in_array($query_key, $this->_allowed_query_params, true)) {
3699
+            if ( ! in_array($query_key, $this->_allowed_query_params, true)) {
3700 3700
                 throw new EE_Error(
3701 3701
                     sprintf(
3702 3702
                         esc_html__(
@@ -3801,7 +3801,7 @@  discard block
 block discarded – undo
3801 3801
         $where_query_params = []
3802 3802
     ) {
3803 3803
         $allowed_used_default_where_conditions_values = EEM_Base::valid_default_where_conditions();
3804
-        if (! in_array($use_default_where_conditions, $allowed_used_default_where_conditions_values)) {
3804
+        if ( ! in_array($use_default_where_conditions, $allowed_used_default_where_conditions_values)) {
3805 3805
             throw new EE_Error(
3806 3806
                 sprintf(
3807 3807
                     esc_html__(
@@ -3831,7 +3831,7 @@  discard block
 block discarded – undo
3831 3831
                 // we don't want to add full or even minimum default where conditions from this model, so just continue
3832 3832
                 continue;
3833 3833
             }
3834
-            $overrides              = $this->_override_defaults_or_make_null_friendly(
3834
+            $overrides = $this->_override_defaults_or_make_null_friendly(
3835 3835
                 $related_model_universal_where_params,
3836 3836
                 $where_query_params,
3837 3837
                 $related_model,
@@ -3940,19 +3940,19 @@  discard block
 block discarded – undo
3940 3940
     ) {
3941 3941
         $null_friendly_where_conditions = [];
3942 3942
         $none_overridden                = true;
3943
-        $or_condition_key_for_defaults  = 'OR*' . get_class($model);
3943
+        $or_condition_key_for_defaults  = 'OR*'.get_class($model);
3944 3944
         foreach ($default_where_conditions as $key => $val) {
3945
-            if (isset($provided_where_conditions[ $key ])) {
3945
+            if (isset($provided_where_conditions[$key])) {
3946 3946
                 $none_overridden = false;
3947 3947
             } else {
3948
-                $null_friendly_where_conditions[ $or_condition_key_for_defaults ]['AND'][ $key ] = $val;
3948
+                $null_friendly_where_conditions[$or_condition_key_for_defaults]['AND'][$key] = $val;
3949 3949
             }
3950 3950
         }
3951 3951
         if ($none_overridden && $default_where_conditions) {
3952 3952
             if ($model->has_primary_key_field()) {
3953
-                $null_friendly_where_conditions[ $or_condition_key_for_defaults ][ $model_relation_path
3953
+                $null_friendly_where_conditions[$or_condition_key_for_defaults][$model_relation_path
3954 3954
                                                                                    . "."
3955
-                                                                                   . $model->primary_key_name() ] =
3955
+                                                                                   . $model->primary_key_name()] =
3956 3956
                     ['IS NULL'];
3957 3957
             }/*else{
3958 3958
                 //@todo NO PK, use other defaults
@@ -4063,7 +4063,7 @@  discard block
 block discarded – undo
4063 4063
             foreach ($tables as $table_obj) {
4064 4064
                 $qualified_pk_column = $table_alias_with_model_relation_chain_prefix
4065 4065
                                        . $table_obj->get_fully_qualified_pk_column();
4066
-                if (! in_array($qualified_pk_column, $selects)) {
4066
+                if ( ! in_array($qualified_pk_column, $selects)) {
4067 4067
                     $selects[] = "$qualified_pk_column AS '$qualified_pk_column'";
4068 4068
                 }
4069 4069
             }
@@ -4215,9 +4215,9 @@  discard block
 block discarded – undo
4215 4215
         $query_parameter_type
4216 4216
     ) {
4217 4217
         foreach ($this->_model_relations as $valid_related_model_name => $relation_obj) {
4218
-            if (strpos($possible_join_string, $valid_related_model_name . ".") === 0) {
4218
+            if (strpos($possible_join_string, $valid_related_model_name.".") === 0) {
4219 4219
                 $this->_add_join_to_model($valid_related_model_name, $query_info_carrier, $original_query_param);
4220
-                $possible_join_string = substr($possible_join_string, strlen($valid_related_model_name . "."));
4220
+                $possible_join_string = substr($possible_join_string, strlen($valid_related_model_name."."));
4221 4221
                 if ($possible_join_string === '') {
4222 4222
                     // nothing left to $query_param
4223 4223
                     // we should actually end in a field name, not a model like this!
@@ -4350,7 +4350,7 @@  discard block
 block discarded – undo
4350 4350
     {
4351 4351
         $SQL = $this->_construct_condition_clause_recursive($where_params, ' AND ');
4352 4352
         if ($SQL) {
4353
-            return " WHERE " . $SQL;
4353
+            return " WHERE ".$SQL;
4354 4354
         }
4355 4355
         return '';
4356 4356
     }
@@ -4368,7 +4368,7 @@  discard block
 block discarded – undo
4368 4368
     {
4369 4369
         $SQL = $this->_construct_condition_clause_recursive($having_params, ' AND ');
4370 4370
         if ($SQL) {
4371
-            return " HAVING " . $SQL;
4371
+            return " HAVING ".$SQL;
4372 4372
         }
4373 4373
         return '';
4374 4374
     }
@@ -4422,7 +4422,7 @@  discard block
 block discarded – undo
4422 4422
             } else {
4423 4423
                 $field_obj = $this->_deduce_field_from_query_param($query_param);
4424 4424
                 // if it's not a normal field, maybe it's a custom selection?
4425
-                if (! $field_obj) {
4425
+                if ( ! $field_obj) {
4426 4426
                     if ($this->_custom_selections instanceof CustomSelects) {
4427 4427
                         $field_obj = $this->_custom_selections->getDataTypeForAlias($query_param);
4428 4428
                     } else {
@@ -4438,7 +4438,7 @@  discard block
 block discarded – undo
4438 4438
                     }
4439 4439
                 }
4440 4440
                 $op_and_value_sql = $this->_construct_op_and_value($op_and_value_or_sub_condition, $field_obj);
4441
-                $where_clauses[]  = $this->_deduce_column_name_from_query_param($query_param) . SP . $op_and_value_sql;
4441
+                $where_clauses[]  = $this->_deduce_column_name_from_query_param($query_param).SP.$op_and_value_sql;
4442 4442
             }
4443 4443
         }
4444 4444
         return $where_clauses
@@ -4462,7 +4462,7 @@  discard block
 block discarded – undo
4462 4462
                 $field->get_model_name(),
4463 4463
                 $query_param
4464 4464
             );
4465
-            return $table_alias_prefix . $field->get_qualified_column();
4465
+            return $table_alias_prefix.$field->get_qualified_column();
4466 4466
         }
4467 4467
         if (
4468 4468
             $this->_custom_selections instanceof CustomSelects
@@ -4523,7 +4523,7 @@  discard block
 block discarded – undo
4523 4523
             $operator = isset($op_and_value[0])
4524 4524
                 ? $this->_prepare_operator_for_sql($op_and_value[0])
4525 4525
                 : null;
4526
-            if (! $operator) {
4526
+            if ( ! $operator) {
4527 4527
                 $php_array_like_string = [];
4528 4528
                 foreach ($op_and_value as $key => $value) {
4529 4529
                     $php_array_like_string[] = "$key=>$value";
@@ -4543,14 +4543,14 @@  discard block
 block discarded – undo
4543 4543
 
4544 4544
         // check to see if the value is actually another field
4545 4545
         if (is_array($op_and_value) && isset($op_and_value[2]) && $op_and_value[2]) {
4546
-            return $operator . SP . $this->_deduce_column_name_from_query_param($value);
4546
+            return $operator.SP.$this->_deduce_column_name_from_query_param($value);
4547 4547
         }
4548 4548
         if (in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4549 4549
             // in this case, the value should be an array, or at least a comma-separated list
4550 4550
             // it will need to handle a little differently
4551 4551
             $cleaned_value = $this->_construct_in_value($value, $field_obj);
4552 4552
             // note: $cleaned_value has already been run through $wpdb->prepare()
4553
-            return $operator . SP . $cleaned_value;
4553
+            return $operator.SP.$cleaned_value;
4554 4554
         }
4555 4555
         if (in_array($operator, $this->valid_between_style_operators()) && is_array($value)) {
4556 4556
             // the value should be an array with count of two.
@@ -4566,7 +4566,7 @@  discard block
 block discarded – undo
4566 4566
                 );
4567 4567
             }
4568 4568
             $cleaned_value = $this->_construct_between_value($value, $field_obj);
4569
-            return $operator . SP . $cleaned_value;
4569
+            return $operator.SP.$cleaned_value;
4570 4570
         }
4571 4571
         if (in_array($operator, $this->valid_null_style_operators())) {
4572 4572
             if ($value !== null) {
@@ -4586,10 +4586,10 @@  discard block
 block discarded – undo
4586 4586
         if (in_array($operator, $this->valid_like_style_operators()) && ! is_array($value)) {
4587 4587
             // if the operator is 'LIKE', we want to allow percent signs (%) and not
4588 4588
             // remove other junk. So just treat it as a string.
4589
-            return $operator . SP . $this->_wpdb_prepare_using_field($value, '%s');
4589
+            return $operator.SP.$this->_wpdb_prepare_using_field($value, '%s');
4590 4590
         }
4591
-        if (! in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4592
-            return $operator . SP . $this->_wpdb_prepare_using_field($value, $field_obj);
4591
+        if ( ! in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4592
+            return $operator.SP.$this->_wpdb_prepare_using_field($value, $field_obj);
4593 4593
         }
4594 4594
         if (in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4595 4595
             throw new EE_Error(
@@ -4603,7 +4603,7 @@  discard block
 block discarded – undo
4603 4603
                 )
4604 4604
             );
4605 4605
         }
4606
-        if (! in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4606
+        if ( ! in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4607 4607
             throw new EE_Error(
4608 4608
                 sprintf(
4609 4609
                     esc_html__(
@@ -4642,7 +4642,7 @@  discard block
 block discarded – undo
4642 4642
         foreach ($values as $value) {
4643 4643
             $cleaned_values[] = $this->_wpdb_prepare_using_field($value, $field_obj);
4644 4644
         }
4645
-        return $cleaned_values[0] . " AND " . $cleaned_values[1];
4645
+        return $cleaned_values[0]." AND ".$cleaned_values[1];
4646 4646
     }
4647 4647
 
4648 4648
 
@@ -4680,7 +4680,7 @@  discard block
 block discarded – undo
4680 4680
             $main_table  = $this->_get_main_table();
4681 4681
             $prepped[]   = "SELECT {$first_field->get_table_column()} FROM {$main_table->get_table_name()} WHERE FALSE";
4682 4682
         }
4683
-        return '(' . implode(',', $prepped) . ')';
4683
+        return '('.implode(',', $prepped).')';
4684 4684
     }
4685 4685
 
4686 4686
 
@@ -4700,7 +4700,7 @@  discard block
 block discarded – undo
4700 4700
                 $this->_prepare_value_for_use_in_db($value, $field_obj)
4701 4701
             );
4702 4702
         } //$field_obj should really just be a data type
4703
-        if (! in_array($field_obj, $this->_valid_wpdb_data_types)) {
4703
+        if ( ! in_array($field_obj, $this->_valid_wpdb_data_types)) {
4704 4704
             throw new EE_Error(
4705 4705
                 sprintf(
4706 4706
                     esc_html__("%s is not a valid wpdb datatype. Valid ones are %s", "event_espresso"),
@@ -4737,14 +4737,14 @@  discard block
 block discarded – undo
4737 4737
             );
4738 4738
         }
4739 4739
         $number_of_parts       = count($query_param_parts);
4740
-        $last_query_param_part = $query_param_parts[ count($query_param_parts) - 1 ];
4740
+        $last_query_param_part = $query_param_parts[count($query_param_parts) - 1];
4741 4741
         if ($number_of_parts === 1) {
4742 4742
             $field_name = $last_query_param_part;
4743 4743
             $model_obj  = $this;
4744 4744
         } else {// $number_of_parts >= 2
4745 4745
             // the last part is the column name, and there are only 2parts. therefore...
4746 4746
             $field_name = $last_query_param_part;
4747
-            $model_obj  = $this->get_related_model_obj($query_param_parts[ $number_of_parts - 2 ]);
4747
+            $model_obj  = $this->get_related_model_obj($query_param_parts[$number_of_parts - 2]);
4748 4748
         }
4749 4749
         try {
4750 4750
             return $model_obj->field_settings_for($field_name);
@@ -4765,7 +4765,7 @@  discard block
 block discarded – undo
4765 4765
     public function _get_qualified_column_for_field($field_name)
4766 4766
     {
4767 4767
         $all_fields = $this->field_settings();
4768
-        $field      = $all_fields[ $field_name ] ?? false;
4768
+        $field      = $all_fields[$field_name] ?? false;
4769 4769
         if ($field) {
4770 4770
             return $field->get_qualified_column();
4771 4771
         }
@@ -4835,12 +4835,12 @@  discard block
 block discarded – undo
4835 4835
      */
4836 4836
     public function get_qualified_columns_for_all_fields($model_relation_chain = '', $return_string = true)
4837 4837
     {
4838
-        $table_prefix      = str_replace('.', '__', $model_relation_chain) . (empty($model_relation_chain)
4838
+        $table_prefix      = str_replace('.', '__', $model_relation_chain).(empty($model_relation_chain)
4839 4839
                 ? ''
4840 4840
                 : '__');
4841 4841
         $qualified_columns = [];
4842 4842
         foreach ($this->field_settings() as $field_name => $field) {
4843
-            $qualified_columns[] = $table_prefix . $field->get_qualified_column();
4843
+            $qualified_columns[] = $table_prefix.$field->get_qualified_column();
4844 4844
         }
4845 4845
         return $return_string
4846 4846
             ? implode(', ', $qualified_columns)
@@ -4867,11 +4867,11 @@  discard block
 block discarded – undo
4867 4867
             if ($table_obj instanceof EE_Primary_Table) {
4868 4868
                 $SQL .= $table_alias === $table_obj->get_table_alias()
4869 4869
                     ? $table_obj->get_select_join_limit($limit)
4870
-                    : SP . $table_obj->get_table_name() . " AS " . $table_obj->get_table_alias() . SP;
4870
+                    : SP.$table_obj->get_table_name()." AS ".$table_obj->get_table_alias().SP;
4871 4871
             } elseif ($table_obj instanceof EE_Secondary_Table) {
4872 4872
                 $SQL .= $table_alias === $table_obj->get_table_alias()
4873 4873
                     ? $table_obj->get_select_join_limit_join($limit)
4874
-                    : SP . $table_obj->get_join_sql($table_alias) . SP;
4874
+                    : SP.$table_obj->get_join_sql($table_alias).SP;
4875 4875
             }
4876 4876
         }
4877 4877
         return $SQL;
@@ -4942,7 +4942,7 @@  discard block
 block discarded – undo
4942 4942
         foreach ($this->field_settings() as $field_obj) {
4943 4943
             // $data_types[$field_obj->get_table_column()] = $field_obj->get_wpdb_data_type();
4944 4944
             /** @var $field_obj EE_Model_Field_Base */
4945
-            $data_types[ $field_obj->get_qualified_column() ] = $field_obj->get_wpdb_data_type();
4945
+            $data_types[$field_obj->get_qualified_column()] = $field_obj->get_wpdb_data_type();
4946 4946
         }
4947 4947
         return $data_types;
4948 4948
     }
@@ -4957,8 +4957,8 @@  discard block
 block discarded – undo
4957 4957
      */
4958 4958
     public function get_related_model_obj($model_name)
4959 4959
     {
4960
-        $model_classname = "EEM_" . $model_name;
4961
-        if (! class_exists($model_classname)) {
4960
+        $model_classname = "EEM_".$model_name;
4961
+        if ( ! class_exists($model_classname)) {
4962 4962
             throw new EE_Error(
4963 4963
                 sprintf(
4964 4964
                     esc_html__(
@@ -4970,7 +4970,7 @@  discard block
 block discarded – undo
4970 4970
                 )
4971 4971
             );
4972 4972
         }
4973
-        return call_user_func($model_classname . "::instance");
4973
+        return call_user_func($model_classname."::instance");
4974 4974
     }
4975 4975
 
4976 4976
 
@@ -4997,7 +4997,7 @@  discard block
 block discarded – undo
4997 4997
         $belongs_to_relations = [];
4998 4998
         foreach ($this->relation_settings() as $model_name => $relation_obj) {
4999 4999
             if ($relation_obj instanceof EE_Belongs_To_Relation) {
5000
-                $belongs_to_relations[ $model_name ] = $relation_obj;
5000
+                $belongs_to_relations[$model_name] = $relation_obj;
5001 5001
             }
5002 5002
         }
5003 5003
         return $belongs_to_relations;
@@ -5014,7 +5014,7 @@  discard block
 block discarded – undo
5014 5014
     public function related_settings_for($relation_name)
5015 5015
     {
5016 5016
         $relatedModels = $this->relation_settings();
5017
-        if (! array_key_exists($relation_name, $relatedModels)) {
5017
+        if ( ! array_key_exists($relation_name, $relatedModels)) {
5018 5018
             throw new EE_Error(
5019 5019
                 sprintf(
5020 5020
                     esc_html__(
@@ -5027,7 +5027,7 @@  discard block
 block discarded – undo
5027 5027
                 )
5028 5028
             );
5029 5029
         }
5030
-        return $relatedModels[ $relation_name ];
5030
+        return $relatedModels[$relation_name];
5031 5031
     }
5032 5032
 
5033 5033
 
@@ -5043,7 +5043,7 @@  discard block
 block discarded – undo
5043 5043
     public function field_settings_for($fieldName, $include_db_only_fields = true)
5044 5044
     {
5045 5045
         $fieldSettings = $this->field_settings($include_db_only_fields);
5046
-        if (! array_key_exists($fieldName, $fieldSettings)) {
5046
+        if ( ! array_key_exists($fieldName, $fieldSettings)) {
5047 5047
             throw new EE_Error(
5048 5048
                 sprintf(
5049 5049
                     esc_html__("There is no field/column '%s' on '%s'", 'event_espresso'),
@@ -5052,7 +5052,7 @@  discard block
 block discarded – undo
5052 5052
                 )
5053 5053
             );
5054 5054
         }
5055
-        return $fieldSettings[ $fieldName ];
5055
+        return $fieldSettings[$fieldName];
5056 5056
     }
5057 5057
 
5058 5058
 
@@ -5065,7 +5065,7 @@  discard block
 block discarded – undo
5065 5065
     public function has_field($fieldName)
5066 5066
     {
5067 5067
         $fieldSettings = $this->field_settings(true);
5068
-        if (isset($fieldSettings[ $fieldName ])) {
5068
+        if (isset($fieldSettings[$fieldName])) {
5069 5069
             return true;
5070 5070
         }
5071 5071
         return false;
@@ -5081,7 +5081,7 @@  discard block
 block discarded – undo
5081 5081
     public function has_relation($relation_name)
5082 5082
     {
5083 5083
         $relations = $this->relation_settings();
5084
-        if (isset($relations[ $relation_name ])) {
5084
+        if (isset($relations[$relation_name])) {
5085 5085
             return true;
5086 5086
         }
5087 5087
         return false;
@@ -5117,7 +5117,7 @@  discard block
 block discarded – undo
5117 5117
                     break;
5118 5118
                 }
5119 5119
             }
5120
-            if (! $this->_primary_key_field instanceof EE_Primary_Key_Field_Base) {
5120
+            if ( ! $this->_primary_key_field instanceof EE_Primary_Key_Field_Base) {
5121 5121
                 throw new EE_Error(
5122 5122
                     sprintf(
5123 5123
                         esc_html__("There is no Primary Key defined on model %s", 'event_espresso'),
@@ -5177,17 +5177,17 @@  discard block
 block discarded – undo
5177 5177
      */
5178 5178
     public function get_foreign_key_to($model_name)
5179 5179
     {
5180
-        if (! isset($this->_cache_foreign_key_to_fields[ $model_name ])) {
5180
+        if ( ! isset($this->_cache_foreign_key_to_fields[$model_name])) {
5181 5181
             foreach ($this->field_settings() as $field) {
5182 5182
                 if (
5183 5183
                     $field instanceof EE_Foreign_Key_Field_Base
5184 5184
                     && in_array($model_name, $field->get_model_names_pointed_to())
5185 5185
                 ) {
5186
-                    $this->_cache_foreign_key_to_fields[ $model_name ] = $field;
5186
+                    $this->_cache_foreign_key_to_fields[$model_name] = $field;
5187 5187
                     break;
5188 5188
                 }
5189 5189
             }
5190
-            if (! isset($this->_cache_foreign_key_to_fields[ $model_name ])) {
5190
+            if ( ! isset($this->_cache_foreign_key_to_fields[$model_name])) {
5191 5191
                 throw new EE_Error(
5192 5192
                     sprintf(
5193 5193
                         esc_html__(
@@ -5200,7 +5200,7 @@  discard block
 block discarded – undo
5200 5200
                 );
5201 5201
             }
5202 5202
         }
5203
-        return $this->_cache_foreign_key_to_fields[ $model_name ];
5203
+        return $this->_cache_foreign_key_to_fields[$model_name];
5204 5204
     }
5205 5205
 
5206 5206
 
@@ -5216,7 +5216,7 @@  discard block
 block discarded – undo
5216 5216
     {
5217 5217
         $table_alias_sans_model_relation_chain_prefix =
5218 5218
             EE_Model_Parser::remove_table_alias_model_relation_chain_prefix($table_alias);
5219
-        return $this->_tables[ $table_alias_sans_model_relation_chain_prefix ]->get_table_name();
5219
+        return $this->_tables[$table_alias_sans_model_relation_chain_prefix]->get_table_name();
5220 5220
     }
5221 5221
 
5222 5222
 
@@ -5234,7 +5234,7 @@  discard block
 block discarded – undo
5234 5234
                 $this->_cached_fields = [];
5235 5235
                 foreach ($this->_fields as $fields_corresponding_to_table) {
5236 5236
                     foreach ($fields_corresponding_to_table as $field_name => $field_obj) {
5237
-                        $this->_cached_fields[ $field_name ] = $field_obj;
5237
+                        $this->_cached_fields[$field_name] = $field_obj;
5238 5238
                     }
5239 5239
                 }
5240 5240
             }
@@ -5245,8 +5245,8 @@  discard block
 block discarded – undo
5245 5245
             foreach ($this->_fields as $fields_corresponding_to_table) {
5246 5246
                 foreach ($fields_corresponding_to_table as $field_name => $field_obj) {
5247 5247
                     /** @var $field_obj EE_Model_Field_Base */
5248
-                    if (! $field_obj->is_db_only_field()) {
5249
-                        $this->_cached_fields_non_db_only[ $field_name ] = $field_obj;
5248
+                    if ( ! $field_obj->is_db_only_field()) {
5249
+                        $this->_cached_fields_non_db_only[$field_name] = $field_obj;
5250 5250
                     }
5251 5251
                 }
5252 5252
             }
@@ -5289,12 +5289,12 @@  discard block
 block discarded – undo
5289 5289
                     $primary_key_field->get_qualified_column(),
5290 5290
                     $primary_key_field->get_table_column()
5291 5291
                 );
5292
-                if ($table_pk_value && isset($array_of_objects[ $table_pk_value ])) {
5292
+                if ($table_pk_value && isset($array_of_objects[$table_pk_value])) {
5293 5293
                     continue;
5294 5294
                 }
5295 5295
             }
5296 5296
             $classInstance = $this->instantiate_class_from_array_or_object($row);
5297
-            if (! $classInstance) {
5297
+            if ( ! $classInstance) {
5298 5298
                 throw new EE_Error(
5299 5299
                     sprintf(
5300 5300
                         esc_html__('Could not create instance of class %s from row %s', 'event_espresso'),
@@ -5309,7 +5309,7 @@  discard block
 block discarded – undo
5309 5309
             $key                      = $has_primary_key
5310 5310
                 ? $classInstance->ID()
5311 5311
                 : $count_if_model_has_no_primary_key++;
5312
-            $array_of_objects[ $key ] = $classInstance;
5312
+            $array_of_objects[$key] = $classInstance;
5313 5313
             // also, for all the relations of type BelongsTo, see if we can cache
5314 5314
             // those related models
5315 5315
             // (we could do this for other relations too, but if there are conditions
@@ -5353,9 +5353,9 @@  discard block
 block discarded – undo
5353 5353
         $results = [];
5354 5354
         if ($this->_custom_selections instanceof CustomSelects) {
5355 5355
             foreach ($this->_custom_selections->columnAliases() as $alias) {
5356
-                if (isset($db_results_row[ $alias ])) {
5357
-                    $results[ $alias ] = $this->convertValueToDataType(
5358
-                        $db_results_row[ $alias ],
5356
+                if (isset($db_results_row[$alias])) {
5357
+                    $results[$alias] = $this->convertValueToDataType(
5358
+                        $db_results_row[$alias],
5359 5359
                         $this->_custom_selections->getDataTypeForAlias($alias)
5360 5360
                     );
5361 5361
                 }
@@ -5400,7 +5400,7 @@  discard block
 block discarded – undo
5400 5400
         $this_model_fields_and_values = [];
5401 5401
         // setup the row using default values;
5402 5402
         foreach ($this->field_settings() as $field_name => $field_obj) {
5403
-            $this_model_fields_and_values[ $field_name ] = $field_obj->get_default_value();
5403
+            $this_model_fields_and_values[$field_name] = $field_obj->get_default_value();
5404 5404
         }
5405 5405
         $className = $this->_get_class_name();
5406 5406
         return EE_Registry::instance()->load_class($className, [$this_model_fields_and_values], false, false);
@@ -5416,20 +5416,20 @@  discard block
 block discarded – undo
5416 5416
      */
5417 5417
     public function instantiate_class_from_array_or_object($cols_n_values)
5418 5418
     {
5419
-        if (! is_array($cols_n_values) && is_object($cols_n_values)) {
5419
+        if ( ! is_array($cols_n_values) && is_object($cols_n_values)) {
5420 5420
             $cols_n_values = get_object_vars($cols_n_values);
5421 5421
         }
5422 5422
         $primary_key = null;
5423 5423
         // make sure the array only has keys that are fields/columns on this model
5424 5424
         $this_model_fields_n_values = $this->_deduce_fields_n_values_from_cols_n_values($cols_n_values);
5425
-        if ($this->has_primary_key_field() && isset($this_model_fields_n_values[ $this->primary_key_name() ])) {
5426
-            $primary_key = $this_model_fields_n_values[ $this->primary_key_name() ];
5425
+        if ($this->has_primary_key_field() && isset($this_model_fields_n_values[$this->primary_key_name()])) {
5426
+            $primary_key = $this_model_fields_n_values[$this->primary_key_name()];
5427 5427
         }
5428 5428
         $className = $this->_get_class_name();
5429 5429
         // check we actually found results that we can use to build our model object
5430 5430
         // if not, return null
5431 5431
         if ($this->has_primary_key_field()) {
5432
-            if (empty($this_model_fields_n_values[ $this->primary_key_name() ])) {
5432
+            if (empty($this_model_fields_n_values[$this->primary_key_name()])) {
5433 5433
                 return null;
5434 5434
             }
5435 5435
         } elseif ($this->unique_indexes()) {
@@ -5441,7 +5441,7 @@  discard block
 block discarded – undo
5441 5441
         // if there is no primary key or the object doesn't already exist in the entity map, then create a new instance
5442 5442
         if ($primary_key) {
5443 5443
             $classInstance = $this->get_from_entity_map($primary_key);
5444
-            if (! $classInstance) {
5444
+            if ( ! $classInstance) {
5445 5445
                 $classInstance = EE_Registry::instance()
5446 5446
                                             ->load_class(
5447 5447
                                                 $className,
@@ -5472,7 +5472,7 @@  discard block
 block discarded – undo
5472 5472
      */
5473 5473
     public function get_from_entity_map($id)
5474 5474
     {
5475
-        return $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ] ?? null;
5475
+        return $this->_entity_map[EEM_Base::$_model_query_blog_id][$id] ?? null;
5476 5476
     }
5477 5477
 
5478 5478
 
@@ -5495,7 +5495,7 @@  discard block
 block discarded – undo
5495 5495
     public function add_to_entity_map(EE_Base_Class $object)
5496 5496
     {
5497 5497
         $className = $this->_get_class_name();
5498
-        if (! $object instanceof $className) {
5498
+        if ( ! $object instanceof $className) {
5499 5499
             throw new EE_Error(
5500 5500
                 sprintf(
5501 5501
                     esc_html__("You tried adding a %s to a mapping of %ss", "event_espresso"),
@@ -5507,7 +5507,7 @@  discard block
 block discarded – undo
5507 5507
             );
5508 5508
         }
5509 5509
 
5510
-        if (! $object->ID()) {
5510
+        if ( ! $object->ID()) {
5511 5511
             throw new EE_Error(
5512 5512
                 sprintf(
5513 5513
                     esc_html__(
@@ -5523,7 +5523,7 @@  discard block
 block discarded – undo
5523 5523
         if ($classInstance) {
5524 5524
             return $classInstance;
5525 5525
         }
5526
-        $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $object->ID() ] = $object;
5526
+        $this->_entity_map[EEM_Base::$_model_query_blog_id][$object->ID()] = $object;
5527 5527
         return $object;
5528 5528
     }
5529 5529
 
@@ -5538,11 +5538,11 @@  discard block
 block discarded – undo
5538 5538
     public function clear_entity_map($id = null)
5539 5539
     {
5540 5540
         if (empty($id)) {
5541
-            $this->_entity_map[ EEM_Base::$_model_query_blog_id ] = [];
5541
+            $this->_entity_map[EEM_Base::$_model_query_blog_id] = [];
5542 5542
             return true;
5543 5543
         }
5544
-        if (isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])) {
5545
-            unset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ]);
5544
+        if (isset($this->_entity_map[EEM_Base::$_model_query_blog_id][$id])) {
5545
+            unset($this->_entity_map[EEM_Base::$_model_query_blog_id][$id]);
5546 5546
             return true;
5547 5547
         }
5548 5548
         return false;
@@ -5590,18 +5590,18 @@  discard block
 block discarded – undo
5590 5590
             // there is a primary key on this table and its not set. Use defaults for all its columns
5591 5591
             if ($table_pk_value === null && $table_obj->get_pk_column()) {
5592 5592
                 foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5593
-                    if (! $field_obj->is_db_only_field()) {
5593
+                    if ( ! $field_obj->is_db_only_field()) {
5594 5594
                         // prepare field as if its coming from db
5595 5595
                         $prepared_value                            =
5596 5596
                             $field_obj->prepare_for_set($field_obj->get_default_value());
5597
-                        $this_model_fields_n_values[ $field_name ] = $field_obj->prepare_for_use_in_db($prepared_value);
5597
+                        $this_model_fields_n_values[$field_name] = $field_obj->prepare_for_use_in_db($prepared_value);
5598 5598
                     }
5599 5599
                 }
5600 5600
             } else {
5601 5601
                 // the table's rows existed. Use their values
5602 5602
                 foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5603
-                    if (! $field_obj->is_db_only_field()) {
5604
-                        $this_model_fields_n_values[ $field_name ] = $this->_get_column_value_with_table_alias_or_not(
5603
+                    if ( ! $field_obj->is_db_only_field()) {
5604
+                        $this_model_fields_n_values[$field_name] = $this->_get_column_value_with_table_alias_or_not(
5605 5605
                             $cols_n_values,
5606 5606
                             $field_obj->get_qualified_column(),
5607 5607
                             $field_obj->get_table_column()
@@ -5628,17 +5628,17 @@  discard block
 block discarded – undo
5628 5628
         // ask the field what it think it's table_name.column_name should be, and call it the "qualified column"
5629 5629
         // does the field on the model relate to this column retrieved from the db?
5630 5630
         // or is it a db-only field? (not relating to the model)
5631
-        if (isset($cols_n_values[ $qualified_column ])) {
5632
-            $value = $cols_n_values[ $qualified_column ];
5633
-        } elseif (isset($cols_n_values[ $regular_column ])) {
5634
-            $value = $cols_n_values[ $regular_column ];
5635
-        } elseif (! empty($this->foreign_key_aliases)) {
5631
+        if (isset($cols_n_values[$qualified_column])) {
5632
+            $value = $cols_n_values[$qualified_column];
5633
+        } elseif (isset($cols_n_values[$regular_column])) {
5634
+            $value = $cols_n_values[$regular_column];
5635
+        } elseif ( ! empty($this->foreign_key_aliases)) {
5636 5636
             // no PK?  ok check if there is a foreign key alias set for this table
5637 5637
             // then check if that alias exists in the incoming data
5638 5638
             // AND that the actual PK the $FK_alias represents matches the $qualified_column (full PK)
5639 5639
             foreach ($this->foreign_key_aliases as $FK_alias => $PK_column) {
5640
-                if ($PK_column === $qualified_column && !empty($cols_n_values[ $FK_alias ])) {
5641
-                    $value = $cols_n_values[ $FK_alias ];
5640
+                if ($PK_column === $qualified_column && ! empty($cols_n_values[$FK_alias])) {
5641
+                    $value = $cols_n_values[$FK_alias];
5642 5642
                     [$pk_class] = explode('.', $PK_column);
5643 5643
                     $pk_model_name = "EEM_{$pk_class}";
5644 5644
                     /** @var EEM_Base $pk_model */
@@ -5682,7 +5682,7 @@  discard block
 block discarded – undo
5682 5682
                     $obj_in_map->clear_cache($relation_name, null, true);
5683 5683
                 }
5684 5684
             }
5685
-            $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ] = $obj_in_map;
5685
+            $this->_entity_map[EEM_Base::$_model_query_blog_id][$id] = $obj_in_map;
5686 5686
             return $obj_in_map;
5687 5687
         }
5688 5688
         return $this->get_one_by_ID($id);
@@ -5734,7 +5734,7 @@  discard block
 block discarded – undo
5734 5734
      */
5735 5735
     private function _get_class_name()
5736 5736
     {
5737
-        return "EE_" . $this->get_this_model_name();
5737
+        return "EE_".$this->get_this_model_name();
5738 5738
     }
5739 5739
 
5740 5740
 
@@ -5788,7 +5788,7 @@  discard block
 block discarded – undo
5788 5788
     {
5789 5789
         $className = get_class($this);
5790 5790
         $tagName   = "FHEE__{$className}__{$methodName}";
5791
-        if (! has_filter($tagName)) {
5791
+        if ( ! has_filter($tagName)) {
5792 5792
             throw new EE_Error(
5793 5793
                 sprintf(
5794 5794
                     esc_html__(
@@ -5959,7 +5959,7 @@  discard block
 block discarded – undo
5959 5959
         $unique_indexes = [];
5960 5960
         foreach ($this->_indexes as $name => $index) {
5961 5961
             if ($index instanceof EE_Unique_Index) {
5962
-                $unique_indexes [ $name ] = $index;
5962
+                $unique_indexes [$name] = $index;
5963 5963
             }
5964 5964
         }
5965 5965
         return $unique_indexes;
@@ -6023,7 +6023,7 @@  discard block
 block discarded – undo
6023 6023
         $key_vals_in_combined_pk = [];
6024 6024
         parse_str($index_primary_key_string, $key_vals_in_combined_pk);
6025 6025
         foreach ($key_fields as $key_field_name => $field_obj) {
6026
-            if (! isset($key_vals_in_combined_pk[ $key_field_name ])) {
6026
+            if ( ! isset($key_vals_in_combined_pk[$key_field_name])) {
6027 6027
                 return null;
6028 6028
             }
6029 6029
         }
@@ -6043,7 +6043,7 @@  discard block
 block discarded – undo
6043 6043
     {
6044 6044
         $keys_it_should_have = array_keys($this->get_combined_primary_key_fields());
6045 6045
         foreach ($keys_it_should_have as $key) {
6046
-            if (! isset($key_vals[ $key ])) {
6046
+            if ( ! isset($key_vals[$key])) {
6047 6047
                 return false;
6048 6048
             }
6049 6049
         }
@@ -6082,8 +6082,8 @@  discard block
 block discarded – undo
6082 6082
         }
6083 6083
         // even copies obviously won't have the same ID, so remove the primary key
6084 6084
         // from the WHERE conditions for finding copies (if there is a primary key, of course)
6085
-        if ($this->has_primary_key_field() && isset($attributes_array[ $this->primary_key_name() ])) {
6086
-            unset($attributes_array[ $this->primary_key_name() ]);
6085
+        if ($this->has_primary_key_field() && isset($attributes_array[$this->primary_key_name()])) {
6086
+            unset($attributes_array[$this->primary_key_name()]);
6087 6087
         }
6088 6088
         if (isset($query_params[0])) {
6089 6089
             $query_params[0] = array_merge($attributes_array, $query_params);
@@ -6105,7 +6105,7 @@  discard block
 block discarded – undo
6105 6105
      */
6106 6106
     public function get_one_copy($model_object_or_attributes_array, $query_params = [])
6107 6107
     {
6108
-        if (! is_array($query_params)) {
6108
+        if ( ! is_array($query_params)) {
6109 6109
             EE_Error::doing_it_wrong(
6110 6110
                 'EEM_Base::get_one_copy',
6111 6111
                 sprintf(
@@ -6154,7 +6154,7 @@  discard block
 block discarded – undo
6154 6154
      */
6155 6155
     private function _prepare_operator_for_sql($operator_supplied)
6156 6156
     {
6157
-        $sql_operator = $this->_valid_operators[ $operator_supplied ] ?? null;
6157
+        $sql_operator = $this->_valid_operators[$operator_supplied] ?? null;
6158 6158
         if ($sql_operator) {
6159 6159
             return $sql_operator;
6160 6160
         }
@@ -6252,7 +6252,7 @@  discard block
 block discarded – undo
6252 6252
         $objs  = $this->get_all($query_params);
6253 6253
         $names = [];
6254 6254
         foreach ($objs as $obj) {
6255
-            $names[ $obj->ID() ] = $obj->name();
6255
+            $names[$obj->ID()] = $obj->name();
6256 6256
         }
6257 6257
         return $names;
6258 6258
     }
@@ -6273,7 +6273,7 @@  discard block
 block discarded – undo
6273 6273
      */
6274 6274
     public function get_IDs($model_objects, $filter_out_empty_ids = false)
6275 6275
     {
6276
-        if (! $this->has_primary_key_field()) {
6276
+        if ( ! $this->has_primary_key_field()) {
6277 6277
             if (WP_DEBUG) {
6278 6278
                 EE_Error::add_error(
6279 6279
                     esc_html__('Trying to get IDs from a model than has no primary key', 'event_espresso'),
@@ -6286,7 +6286,7 @@  discard block
 block discarded – undo
6286 6286
         $IDs = [];
6287 6287
         foreach ($model_objects as $model_object) {
6288 6288
             $id = $model_object->ID();
6289
-            if (! $id) {
6289
+            if ( ! $id) {
6290 6290
                 if ($filter_out_empty_ids) {
6291 6291
                     continue;
6292 6292
                 }
@@ -6335,22 +6335,22 @@  discard block
 block discarded – undo
6335 6335
         EEM_Base::verify_is_valid_cap_context($context);
6336 6336
         // check if we ought to run the restriction generator first
6337 6337
         if (
6338
-            isset($this->_cap_restriction_generators[ $context ])
6339
-            && $this->_cap_restriction_generators[ $context ] instanceof EE_Restriction_Generator_Base
6340
-            && ! $this->_cap_restriction_generators[ $context ]->has_generated_cap_restrictions()
6338
+            isset($this->_cap_restriction_generators[$context])
6339
+            && $this->_cap_restriction_generators[$context] instanceof EE_Restriction_Generator_Base
6340
+            && ! $this->_cap_restriction_generators[$context]->has_generated_cap_restrictions()
6341 6341
         ) {
6342
-            $this->_cap_restrictions[ $context ] = array_merge(
6343
-                $this->_cap_restrictions[ $context ],
6344
-                $this->_cap_restriction_generators[ $context ]->generate_restrictions()
6342
+            $this->_cap_restrictions[$context] = array_merge(
6343
+                $this->_cap_restrictions[$context],
6344
+                $this->_cap_restriction_generators[$context]->generate_restrictions()
6345 6345
             );
6346 6346
         }
6347 6347
         // and make sure we've finalized the construction of each restriction
6348
-        foreach ($this->_cap_restrictions[ $context ] as $where_conditions_obj) {
6348
+        foreach ($this->_cap_restrictions[$context] as $where_conditions_obj) {
6349 6349
             if ($where_conditions_obj instanceof EE_Default_Where_Conditions) {
6350 6350
                 $where_conditions_obj->_finalize_construct($this);
6351 6351
             }
6352 6352
         }
6353
-        return $this->_cap_restrictions[ $context ];
6353
+        return $this->_cap_restrictions[$context];
6354 6354
     }
6355 6355
 
6356 6356
 
@@ -6380,9 +6380,9 @@  discard block
 block discarded – undo
6380 6380
         foreach ($cap_restrictions as $cap => $restriction_if_no_cap) {
6381 6381
             if (
6382 6382
                 ! EE_Capabilities::instance()
6383
-                                 ->current_user_can($cap, $this->get_this_model_name() . '_model_applying_caps')
6383
+                                 ->current_user_can($cap, $this->get_this_model_name().'_model_applying_caps')
6384 6384
             ) {
6385
-                $missing_caps[ $cap ] = $restriction_if_no_cap;
6385
+                $missing_caps[$cap] = $restriction_if_no_cap;
6386 6386
             }
6387 6387
         }
6388 6388
         return $missing_caps;
@@ -6415,8 +6415,8 @@  discard block
 block discarded – undo
6415 6415
     public function cap_action_for_context($context)
6416 6416
     {
6417 6417
         $mapping = $this->cap_contexts_to_cap_action_map();
6418
-        if (isset($mapping[ $context ])) {
6419
-            return $mapping[ $context ];
6418
+        if (isset($mapping[$context])) {
6419
+            return $mapping[$context];
6420 6420
         }
6421 6421
         if ($action = apply_filters('FHEE__EEM_Base__cap_action_for_context', null, $this, $mapping, $context)) {
6422 6422
             return $action;
@@ -6537,7 +6537,7 @@  discard block
 block discarded – undo
6537 6537
         foreach ($this->logic_query_param_keys() as $logic_query_param_key) {
6538 6538
             if (
6539 6539
                 $query_param_key === $logic_query_param_key
6540
-                || strpos($query_param_key, $logic_query_param_key . '*') === 0
6540
+                || strpos($query_param_key, $logic_query_param_key.'*') === 0
6541 6541
             ) {
6542 6542
                 return true;
6543 6543
             }
@@ -6595,7 +6595,7 @@  discard block
 block discarded – undo
6595 6595
         if ($password_field instanceof EE_Password_Field) {
6596 6596
             $field_names = $password_field->protectedFields();
6597 6597
             foreach ($field_names as $field_name) {
6598
-                $fields[ $field_name ] = $this->field_settings_for($field_name);
6598
+                $fields[$field_name] = $this->field_settings_for($field_name);
6599 6599
             }
6600 6600
         }
6601 6601
         return $fields;
@@ -6621,7 +6621,7 @@  discard block
 block discarded – undo
6621 6621
         if ($model_obj_or_fields_n_values instanceof EE_Base_Class) {
6622 6622
             $model_obj_or_fields_n_values = $model_obj_or_fields_n_values->model_field_array();
6623 6623
         }
6624
-        if (! is_array($model_obj_or_fields_n_values)) {
6624
+        if ( ! is_array($model_obj_or_fields_n_values)) {
6625 6625
             throw new UnexpectedEntityException(
6626 6626
                 $model_obj_or_fields_n_values,
6627 6627
                 'EE_Base_Class',
@@ -6703,9 +6703,9 @@  discard block
 block discarded – undo
6703 6703
         }
6704 6704
         return (
6705 6705
                $this->model_chain_to_password
6706
-                   ? $this->model_chain_to_password . '.'
6706
+                   ? $this->model_chain_to_password.'.'
6707 6707
                    : ''
6708
-               ) . $password_field_name;
6708
+               ).$password_field_name;
6709 6709
     }
6710 6710
 
6711 6711
 
Please login to merge, or discard this patch.
core/admin/EE_Admin_Page_CPT.core.php 1 patch
Indentation   +1427 added lines, -1427 removed lines patch added patch discarded remove patch
@@ -31,452 +31,452 @@  discard block
 block discarded – undo
31 31
  */
32 32
 abstract class EE_Admin_Page_CPT extends EE_Admin_Page
33 33
 {
34
-    /**
35
-     * @var EE_CPT_Base|null
36
-     */
37
-    protected $_cpt_model_obj;
38
-
39
-    protected ?WP_Post_Type $_cpt_object = null;
40
-
41
-
42
-    /**
43
-     * This property allows cpt classes to define multiple routes as cpt routes.
44
-     * //in this array we define what the custom post type for this route is.
45
-     * array(
46
-     * 'route_name' => 'custom_post_type_slug'
47
-     * )
48
-     *
49
-     * @var array
50
-     */
51
-    protected array $_cpt_routes = [];
52
-
53
-
54
-    /**
55
-     * This simply defines what the corresponding routes WP will be redirected to after completing a post save/update.
56
-     * in this format:
57
-     * array(
58
-     * 'post_type_slug' => 'edit_route'
59
-     * )
60
-     *
61
-     * @var array
62
-     */
63
-    protected array $_cpt_edit_routes = [];
64
-
65
-
66
-    /**
67
-     * If child classes set the name of their main model via the $_cpt_obj_models property, EE_Admin_Page_CPT will
68
-     * attempt to retrieve the related object model for the edit pages and assign it to _cpt_page_object. the
69
-     * _cpt_model_names property should be in the following format: array(
70
-     * 'route_defined_by_action_param' => 'Model_Name')
71
-     *
72
-     * @var array $_cpt_model_names
73
-     */
74
-    protected array $_cpt_model_names = [];
75
-
76
-
77
-    /**
78
-     * This will hold an array of autosave containers that will be used to obtain input values and hook into the WP
79
-     * autosave so we can save our inputs on the save_post hook!  Children classes should add to this array by using
80
-     * the _register_autosave_containers() method so that we don't override any other containers already registered.
81
-     * Registration of containers should be done before load_page_dependencies() is run.
82
-     *
83
-     * @var array
84
-     */
85
-    protected array $_autosave_containers = [];
86
-
87
-    protected array $_autosave_fields     = [];
88
-
89
-    /**
90
-     * Array mapping from admin actions to their equivalent wp core pages for custom post types. So when a user visits
91
-     * a page for an action, it will appear as if they were visiting the wp core page for that custom post type
92
-     *
93
-     * @var array
94
-     */
95
-    protected array $_pagenow_map= [];
96
-
97
-
98
-    /**
99
-     * This is the route that will be used for the edit post route.
100
-     *
101
-     * @var string
102
-     */
103
-    protected string $cpt_editpost_route = 'edit';
104
-
105
-
106
-    /**
107
-     * This is hooked into the WordPress do_action('save_post') hook and runs after the custom post type has been
108
-     * saved.  Child classes are required to declare this method.  Typically you would use this to save any additional
109
-     * data. Keep in mind also that "save_post" runs on EVERY post update to the database. ALSO very important.  When a
110
-     * post transitions from scheduled to published, the save_post action is fired but you will NOT have any _POST data
111
-     * containing any extra info you may have from other meta saves.  So MAKE sure that you handle this accordingly.
112
-     *
113
-     * @abstract
114
-     * @param string  $post_id The ID of the cpt that was saved (so you can link relationally)
115
-     * @param WP_Post $post    The post object of the cpt that was saved.
116
-     * @return void
117
-     */
118
-    abstract protected function _insert_update_cpt_item($post_id, $post);
119
-
120
-
121
-    /**
122
-     * This is hooked into the WordPress do_action('trashed_post') hook and runs after a cpt has been trashed.
123
-     *
124
-     * @abstract
125
-     * @param string $post_id The ID of the cpt that was trashed
126
-     * @return void
127
-     */
128
-    abstract public function trash_cpt_item($post_id);
129
-
130
-
131
-    /**
132
-     * This is hooked into the WordPress do_action('untrashed_post') hook and runs after a cpt has been untrashed
133
-     *
134
-     * @param string $post_id theID of the cpt that was untrashed
135
-     * @return void
136
-     */
137
-    abstract public function restore_cpt_item($post_id);
138
-
139
-
140
-    /**
141
-     * This is hooked into the WordPress do_action('delete_cpt_item') hook and runs after a cpt has been fully deleted
142
-     * from the db
143
-     *
144
-     * @param string $post_id the ID of the cpt that was deleted
145
-     * @return void
146
-     */
147
-    abstract public function delete_cpt_item($post_id);
148
-
149
-
150
-    /**
151
-     * @return LoaderInterface
152
-     * @throws InvalidArgumentException
153
-     * @throws InvalidDataTypeException
154
-     * @throws InvalidInterfaceException
155
-     */
156
-    protected function getLoader(): LoaderInterface
157
-    {
158
-        if (! $this->loader instanceof LoaderInterface) {
159
-            $this->loader = LoaderFactory::getLoader();
160
-        }
161
-        return $this->loader;
162
-    }
163
-
164
-
165
-    /**
166
-     * Just utilizing the method EE_Admin exposes for doing things before page setup.
167
-     *
168
-     * @return void
169
-     */
170
-    protected function _before_page_setup()
171
-    {
172
-        $this->raw_req_action = $this->request->getRequestParam('action');
173
-        $this->raw_req_page   = $this->request->getRequestParam('page');
174
-        $this->_cpt_routes    = array_merge(
175
-            [
176
-                'create_new' => $this->page_slug,
177
-                'edit'       => $this->page_slug,
178
-                'trash'      => $this->page_slug,
179
-            ],
180
-            $this->_cpt_routes
181
-        );
182
-        $cpt_route_action     = $this->_cpt_routes[ $this->raw_req_action ] ?? null;
183
-        // let's see if the current route has a value for cpt_object_slug. if it does, we use that instead of the page
184
-        $page              = $this->raw_req_page ?: $this->page_slug;
185
-        $page              = $cpt_route_action ?: $page;
186
-        $this->_cpt_object = get_post_type_object($page);
187
-        // tweak pagenow for page loading.
188
-        if (empty($this->_pagenow_map)) {
189
-            $this->_pagenow_map = [
190
-                'create_new' => 'post-new.php',
191
-                'edit'       => 'post.php',
192
-                'trash'      => 'post.php',
193
-            ];
194
-        }
195
-        add_action('current_screen', [$this, 'modify_pagenow']);
196
-        // TODO the below will need to be reworked to account for the cpt routes that are NOT based off of page but action param.
197
-        // get current page from autosave
198
-        $current_page        = $this->request->getRequestParam('ee_autosave_data[ee-cpt-hidden-inputs][current_page]');
199
-        $this->_current_page = $this->request->getRequestParam('current_page', $current_page);
200
-    }
201
-
202
-
203
-    /**
204
-     * Simply ensure that we simulate the correct post route for cpt screens
205
-     *
206
-     * @param WP_Screen|null $current_screen
207
-     * @return void
208
-     */
209
-    public function modify_pagenow(?WP_Screen $current_screen)
210
-    {
211
-        // possibly reset pagenow.
212
-        if (
213
-            $this->page_slug === $this->raw_req_page
214
-            && isset($this->_pagenow_map[ $this->raw_req_action ])
215
-        ) {
216
-            global $pagenow, $hook_suffix;
217
-            $pagenow     = $this->_pagenow_map[ $this->raw_req_action ];
218
-            $hook_suffix = $pagenow;
219
-        }
220
-    }
221
-
222
-
223
-    /**
224
-     * This method is used to register additional autosave containers to the _autosave_containers property.
225
-     *
226
-     * @param array $ids  an array of ids for containers that hold form inputs we want autosave to pickup.  Typically
227
-     *                    you would send along the id of a metabox container.
228
-     * @return void
229
-     * @todo We should automate this at some point by creating a wrapper for add_post_metabox and in our wrapper we
230
-     *                    automatically register the id for the post metabox as a container.
231
-     */
232
-    protected function _register_autosave_containers($ids)
233
-    {
234
-        $this->_autosave_containers = array_merge($this->_autosave_fields, (array) $ids);
235
-    }
236
-
237
-
238
-    /**
239
-     * Something nifty.  We're going to loop through all the registered metaboxes and if the CALLBACK is an instance of
240
-     * EE_Admin_Page OR EE_Admin_Hooks, then we'll add the id to our _autosave_containers array.
241
-     */
242
-    protected function _set_autosave_containers()
243
-    {
244
-        global $wp_meta_boxes;
245
-        $containers = [];
246
-        if (empty($wp_meta_boxes)) {
247
-            return;
248
-        }
249
-        $current_metaboxes = $wp_meta_boxes[ $this->page_slug ] ?? [];
250
-        foreach ($current_metaboxes as $box_context) {
251
-            foreach ($box_context as $box_details) {
252
-                foreach ($box_details as $box) {
253
-                    if (
254
-                        is_array($box) && is_array($box['callback'])
255
-                        && (
256
-                            $box['callback'][0] instanceof EE_Admin_Page
257
-                            || $box['callback'][0] instanceof EE_Admin_Hooks
258
-                        )
259
-                    ) {
260
-                        $containers[] = $box['id'];
261
-                    }
262
-                }
263
-            }
264
-        }
265
-        $this->_autosave_containers = array_merge($this->_autosave_containers, $containers);
266
-        // add hidden inputs container
267
-        $this->_autosave_containers[] = 'ee-cpt-hidden-inputs';
268
-    }
269
-
270
-
271
-    protected function _load_autosave_scripts_styles()
272
-    {
273
-        /*wp_register_script('cpt-autosave', EE_ADMIN_URL . 'assets/ee-cpt-autosave.js', array('ee-serialize-full-array', 'event_editor_js'), EVENT_ESPRESSO_VERSION, TRUE );
34
+	/**
35
+	 * @var EE_CPT_Base|null
36
+	 */
37
+	protected $_cpt_model_obj;
38
+
39
+	protected ?WP_Post_Type $_cpt_object = null;
40
+
41
+
42
+	/**
43
+	 * This property allows cpt classes to define multiple routes as cpt routes.
44
+	 * //in this array we define what the custom post type for this route is.
45
+	 * array(
46
+	 * 'route_name' => 'custom_post_type_slug'
47
+	 * )
48
+	 *
49
+	 * @var array
50
+	 */
51
+	protected array $_cpt_routes = [];
52
+
53
+
54
+	/**
55
+	 * This simply defines what the corresponding routes WP will be redirected to after completing a post save/update.
56
+	 * in this format:
57
+	 * array(
58
+	 * 'post_type_slug' => 'edit_route'
59
+	 * )
60
+	 *
61
+	 * @var array
62
+	 */
63
+	protected array $_cpt_edit_routes = [];
64
+
65
+
66
+	/**
67
+	 * If child classes set the name of their main model via the $_cpt_obj_models property, EE_Admin_Page_CPT will
68
+	 * attempt to retrieve the related object model for the edit pages and assign it to _cpt_page_object. the
69
+	 * _cpt_model_names property should be in the following format: array(
70
+	 * 'route_defined_by_action_param' => 'Model_Name')
71
+	 *
72
+	 * @var array $_cpt_model_names
73
+	 */
74
+	protected array $_cpt_model_names = [];
75
+
76
+
77
+	/**
78
+	 * This will hold an array of autosave containers that will be used to obtain input values and hook into the WP
79
+	 * autosave so we can save our inputs on the save_post hook!  Children classes should add to this array by using
80
+	 * the _register_autosave_containers() method so that we don't override any other containers already registered.
81
+	 * Registration of containers should be done before load_page_dependencies() is run.
82
+	 *
83
+	 * @var array
84
+	 */
85
+	protected array $_autosave_containers = [];
86
+
87
+	protected array $_autosave_fields     = [];
88
+
89
+	/**
90
+	 * Array mapping from admin actions to their equivalent wp core pages for custom post types. So when a user visits
91
+	 * a page for an action, it will appear as if they were visiting the wp core page for that custom post type
92
+	 *
93
+	 * @var array
94
+	 */
95
+	protected array $_pagenow_map= [];
96
+
97
+
98
+	/**
99
+	 * This is the route that will be used for the edit post route.
100
+	 *
101
+	 * @var string
102
+	 */
103
+	protected string $cpt_editpost_route = 'edit';
104
+
105
+
106
+	/**
107
+	 * This is hooked into the WordPress do_action('save_post') hook and runs after the custom post type has been
108
+	 * saved.  Child classes are required to declare this method.  Typically you would use this to save any additional
109
+	 * data. Keep in mind also that "save_post" runs on EVERY post update to the database. ALSO very important.  When a
110
+	 * post transitions from scheduled to published, the save_post action is fired but you will NOT have any _POST data
111
+	 * containing any extra info you may have from other meta saves.  So MAKE sure that you handle this accordingly.
112
+	 *
113
+	 * @abstract
114
+	 * @param string  $post_id The ID of the cpt that was saved (so you can link relationally)
115
+	 * @param WP_Post $post    The post object of the cpt that was saved.
116
+	 * @return void
117
+	 */
118
+	abstract protected function _insert_update_cpt_item($post_id, $post);
119
+
120
+
121
+	/**
122
+	 * This is hooked into the WordPress do_action('trashed_post') hook and runs after a cpt has been trashed.
123
+	 *
124
+	 * @abstract
125
+	 * @param string $post_id The ID of the cpt that was trashed
126
+	 * @return void
127
+	 */
128
+	abstract public function trash_cpt_item($post_id);
129
+
130
+
131
+	/**
132
+	 * This is hooked into the WordPress do_action('untrashed_post') hook and runs after a cpt has been untrashed
133
+	 *
134
+	 * @param string $post_id theID of the cpt that was untrashed
135
+	 * @return void
136
+	 */
137
+	abstract public function restore_cpt_item($post_id);
138
+
139
+
140
+	/**
141
+	 * This is hooked into the WordPress do_action('delete_cpt_item') hook and runs after a cpt has been fully deleted
142
+	 * from the db
143
+	 *
144
+	 * @param string $post_id the ID of the cpt that was deleted
145
+	 * @return void
146
+	 */
147
+	abstract public function delete_cpt_item($post_id);
148
+
149
+
150
+	/**
151
+	 * @return LoaderInterface
152
+	 * @throws InvalidArgumentException
153
+	 * @throws InvalidDataTypeException
154
+	 * @throws InvalidInterfaceException
155
+	 */
156
+	protected function getLoader(): LoaderInterface
157
+	{
158
+		if (! $this->loader instanceof LoaderInterface) {
159
+			$this->loader = LoaderFactory::getLoader();
160
+		}
161
+		return $this->loader;
162
+	}
163
+
164
+
165
+	/**
166
+	 * Just utilizing the method EE_Admin exposes for doing things before page setup.
167
+	 *
168
+	 * @return void
169
+	 */
170
+	protected function _before_page_setup()
171
+	{
172
+		$this->raw_req_action = $this->request->getRequestParam('action');
173
+		$this->raw_req_page   = $this->request->getRequestParam('page');
174
+		$this->_cpt_routes    = array_merge(
175
+			[
176
+				'create_new' => $this->page_slug,
177
+				'edit'       => $this->page_slug,
178
+				'trash'      => $this->page_slug,
179
+			],
180
+			$this->_cpt_routes
181
+		);
182
+		$cpt_route_action     = $this->_cpt_routes[ $this->raw_req_action ] ?? null;
183
+		// let's see if the current route has a value for cpt_object_slug. if it does, we use that instead of the page
184
+		$page              = $this->raw_req_page ?: $this->page_slug;
185
+		$page              = $cpt_route_action ?: $page;
186
+		$this->_cpt_object = get_post_type_object($page);
187
+		// tweak pagenow for page loading.
188
+		if (empty($this->_pagenow_map)) {
189
+			$this->_pagenow_map = [
190
+				'create_new' => 'post-new.php',
191
+				'edit'       => 'post.php',
192
+				'trash'      => 'post.php',
193
+			];
194
+		}
195
+		add_action('current_screen', [$this, 'modify_pagenow']);
196
+		// TODO the below will need to be reworked to account for the cpt routes that are NOT based off of page but action param.
197
+		// get current page from autosave
198
+		$current_page        = $this->request->getRequestParam('ee_autosave_data[ee-cpt-hidden-inputs][current_page]');
199
+		$this->_current_page = $this->request->getRequestParam('current_page', $current_page);
200
+	}
201
+
202
+
203
+	/**
204
+	 * Simply ensure that we simulate the correct post route for cpt screens
205
+	 *
206
+	 * @param WP_Screen|null $current_screen
207
+	 * @return void
208
+	 */
209
+	public function modify_pagenow(?WP_Screen $current_screen)
210
+	{
211
+		// possibly reset pagenow.
212
+		if (
213
+			$this->page_slug === $this->raw_req_page
214
+			&& isset($this->_pagenow_map[ $this->raw_req_action ])
215
+		) {
216
+			global $pagenow, $hook_suffix;
217
+			$pagenow     = $this->_pagenow_map[ $this->raw_req_action ];
218
+			$hook_suffix = $pagenow;
219
+		}
220
+	}
221
+
222
+
223
+	/**
224
+	 * This method is used to register additional autosave containers to the _autosave_containers property.
225
+	 *
226
+	 * @param array $ids  an array of ids for containers that hold form inputs we want autosave to pickup.  Typically
227
+	 *                    you would send along the id of a metabox container.
228
+	 * @return void
229
+	 * @todo We should automate this at some point by creating a wrapper for add_post_metabox and in our wrapper we
230
+	 *                    automatically register the id for the post metabox as a container.
231
+	 */
232
+	protected function _register_autosave_containers($ids)
233
+	{
234
+		$this->_autosave_containers = array_merge($this->_autosave_fields, (array) $ids);
235
+	}
236
+
237
+
238
+	/**
239
+	 * Something nifty.  We're going to loop through all the registered metaboxes and if the CALLBACK is an instance of
240
+	 * EE_Admin_Page OR EE_Admin_Hooks, then we'll add the id to our _autosave_containers array.
241
+	 */
242
+	protected function _set_autosave_containers()
243
+	{
244
+		global $wp_meta_boxes;
245
+		$containers = [];
246
+		if (empty($wp_meta_boxes)) {
247
+			return;
248
+		}
249
+		$current_metaboxes = $wp_meta_boxes[ $this->page_slug ] ?? [];
250
+		foreach ($current_metaboxes as $box_context) {
251
+			foreach ($box_context as $box_details) {
252
+				foreach ($box_details as $box) {
253
+					if (
254
+						is_array($box) && is_array($box['callback'])
255
+						&& (
256
+							$box['callback'][0] instanceof EE_Admin_Page
257
+							|| $box['callback'][0] instanceof EE_Admin_Hooks
258
+						)
259
+					) {
260
+						$containers[] = $box['id'];
261
+					}
262
+				}
263
+			}
264
+		}
265
+		$this->_autosave_containers = array_merge($this->_autosave_containers, $containers);
266
+		// add hidden inputs container
267
+		$this->_autosave_containers[] = 'ee-cpt-hidden-inputs';
268
+	}
269
+
270
+
271
+	protected function _load_autosave_scripts_styles()
272
+	{
273
+		/*wp_register_script('cpt-autosave', EE_ADMIN_URL . 'assets/ee-cpt-autosave.js', array('ee-serialize-full-array', 'event_editor_js'), EVENT_ESPRESSO_VERSION, TRUE );
274 274
         wp_enqueue_script('cpt-autosave');/**/ // todo re-enable when we start doing autosave again in 4.2
275 275
 
276
-        // filter _autosave_containers
277
-        $containers = apply_filters(
278
-            'FHEE__EE_Admin_Page_CPT___load_autosave_scripts_styles__containers',
279
-            $this->_autosave_containers,
280
-            $this
281
-        );
282
-        $containers = apply_filters(
283
-            'FHEE__EE_Admin_Page_CPT__' . get_class($this) . '___load_autosave_scripts_styles__containers',
284
-            $containers,
285
-            $this
286
-        );
287
-
288
-        wp_localize_script(
289
-            'event_editor_js',
290
-            'EE_AUTOSAVE_IDS',
291
-            $containers
292
-        ); // todo once we enable autosaves, this needs to be switched to localize with "cpt-autosave"
293
-
294
-        $unsaved_data_msg = [
295
-            'eventmsg'     => sprintf(
296
-                wp_strip_all_tags(
297
-                    __(
298
-                        "The changes you made to this %s will be lost if you navigate away from this page.",
299
-                        'event_espresso'
300
-                    )
301
-                ),
302
-                $this->_cpt_object->labels->singular_name
303
-            ),
304
-            'inputChanged' => 0,
305
-        ];
306
-        wp_localize_script('event_editor_js', 'UNSAVED_DATA_MSG', $unsaved_data_msg);
307
-    }
308
-
309
-
310
-    /**
311
-     * overloading the EE_Admin_Page parent load_page_dependencies so we can get the cpt stuff added in appropriately
312
-     *
313
-     * @return void
314
-     * @throws EE_Error
315
-     * @throws ReflectionException
316
-     * @throws Throwable
317
-     */
318
-    protected function _load_page_dependencies()
319
-    {
320
-        // we only add stuff if this is a cpt_route!
321
-        if (! $this->_cpt_route) {
322
-            parent::_load_page_dependencies();
323
-            return;
324
-        }
325
-        // now let's do some automatic filters into the wp_system
326
-        // and we'll check to make sure the CHILD class
327
-        // automatically has the required methods in place.
328
-        // the following filters are for setting all the redirects
329
-        // on DEFAULT WP custom post type actions
330
-        // let's add a hidden input to the post-edit form
331
-        // so we know when we have to trigger our custom redirects!
332
-        // Otherwise the redirects will happen on ALL post saves which wouldn't be good of course!
333
-        add_action('edit_form_after_title', [$this, 'cpt_post_form_hidden_input']);
334
-        // inject our Admin page nav tabs...
335
-        // let's make sure the nav tabs are set if they aren't already
336
-        // if ( empty( $this->_nav_tabs ) ) $this->_set_nav_tabs();
337
-        add_action('edit_form_top', [$this, 'inject_nav_tabs']);
338
-        // modify the post_updated messages array
339
-        add_action('post_updated_messages', [$this, 'post_update_messages']);
340
-        // This basically allows us to change the title of the "publish" metabox area
341
-        // on CPT pages by setting a 'publishbox' value in the $_labels property array in the child class.
342
-        $screen = $this->_cpt_routes[ $this->_req_action ];
343
-        if (! empty($this->_labels['publishbox'])) {
344
-            $this->addMetaBox(
345
-                'submitdiv',
346
-                $this->getPublishBoxTitle(),
347
-                'post_submit_meta_box',
348
-                $screen,
349
-                'side',
350
-                'core'
351
-            );
352
-        }
353
-        // let's add page_templates metabox if this cpt added support for it.
354
-        if ($this->_supports_page_templates($this->_cpt_object->name)) {
355
-            $this->addMetaBox(
356
-                'page_templates',
357
-                esc_html__('Page Template', 'event_espresso'),
358
-                [$this, 'page_template_meta_box'],
359
-                $screen,
360
-                'side'
361
-            );
362
-        }
363
-        // add preview button
364
-        // add_filter('get_sample_permalink_html', [PreviewButton::class, 'addButton'], 5, 2);
365
-        PreviewButton::addEventEditorPermalinkButton(5);
366
-        // add shortlink button to cpt edit screens.
367
-        //  We can do this as a universal thing BECAUSE, cpts use the same format for shortlinks as posts!
368
-        // add_filter('get_sample_permalink_html', [EventShortlinkButton::class, 'addButton'], 10, 2);
369
-        EventShortlinkButton::addEventEditorPermalinkButton();
370
-        // this is a filter that allows the addition of extra html after the permalink field on the wp post edit-form
371
-        // add_filter('get_sample_permalink_html', [TicketSelectorShortcodeButton::class, 'addButton'], 12, 2);
372
-        TicketSelectorShortcodeButton::addEventEditorPermalinkButton(12);
373
-        // insert our own post_stati dropdown
374
-        add_action('post_submitbox_misc_actions', [$this, 'custom_post_stati_dropdown']);
375
-        // This allows adding additional information to the publish post submitbox on the wp post edit form
376
-        if (method_exists($this, 'extra_misc_actions_publish_box')) {
377
-            add_action('post_submitbox_misc_actions', [$this, 'extra_misc_actions_publish_box']);
378
-        }
379
-        // This allows for adding additional stuff after the title field on the wp post edit form.
380
-        // This is also before the wp_editor for post description field.
381
-        if (method_exists($this, 'edit_form_after_title')) {
382
-            add_action('edit_form_after_title', [$this, 'edit_form_after_title']);
383
-        }
384
-        /**
385
-         * Filtering WP's esc_url to capture urls pointing to core wp routes so they point to our route.
386
-         */
387
-        add_filter('clean_url', [$this, 'switch_core_wp_urls_with_ours']);
388
-        parent::_load_page_dependencies();
389
-        // notice we are ALSO going to load the pagenow hook set for this route
390
-        // (see _before_page_setup for the reset of the pagenow global ).
391
-        // This is for any plugins that are doing things properly
392
-        // and hooking into the load page hook for core wp cpt routes.
393
-        global $pagenow;
394
-        add_action('load-' . $pagenow, [$this, 'modify_current_screen'], 20);
395
-        do_action('load-' . $pagenow);
396
-        add_action('admin_enqueue_scripts', [$this, 'setup_autosave_hooks'], 30);
397
-        // we route REALLY early.
398
-        try {
399
-            $this->_route_admin_request();
400
-        } catch (EE_Error $e) {
401
-            $e->get_error();
402
-        }
403
-    }
404
-
405
-
406
-    /**
407
-     * Since we don't want users going to default core wp routes, this will check any wp urls run through the
408
-     * esc_url() method and if we see a url matching a pattern for our routes, we'll modify it to point to OUR
409
-     * route instead.
410
-     *
411
-     * @param string $good_protocol_url The escaped url.
412
-     * @return string possibly a new url for our route.
413
-     */
414
-    public function switch_core_wp_urls_with_ours(string $good_protocol_url): string
415
-    {
416
-        $routes_to_match = [
417
-            0 => [
418
-                'edit.php?post_type=espresso_attendees',
419
-                'admin.php?page=espresso_registrations&action=contact_list',
420
-            ],
421
-            1 => [
422
-                'edit.php?post_type=' . $this->_cpt_object->name,
423
-                'admin.php?page=' . $this->_cpt_object->name,
424
-            ],
425
-        ];
426
-        foreach ($routes_to_match as $route_matches) {
427
-            if (strpos($good_protocol_url, $route_matches[0]) !== false) {
428
-                return str_replace($route_matches[0], $route_matches[1], $good_protocol_url);
429
-            }
430
-        }
431
-        return $good_protocol_url;
432
-    }
433
-
434
-
435
-    /**
436
-     * Determine whether the current cpt supports page templates or not.
437
-     *
438
-     * @param string $cpt_name The cpt slug we're checking on.
439
-     * @return bool True supported, false not.
440
-     * @throws InvalidArgumentException
441
-     * @throws InvalidDataTypeException
442
-     * @throws InvalidInterfaceException
443
-     * @since %VER%
444
-     */
445
-    private function _supports_page_templates(string $cpt_name): bool
446
-    {
447
-        /** @var EventEspresso\core\domain\entities\custom_post_types\CustomPostTypeDefinitions $custom_post_types */
448
-        $custom_post_types = $this->loader->getShared(
449
-            'EventEspresso\core\domain\entities\custom_post_types\CustomPostTypeDefinitions'
450
-        );
451
-        $cpt_args          = $custom_post_types->getDefinitions();
452
-        $cpt_args          = isset($cpt_args[ $cpt_name ]) ? $cpt_args[ $cpt_name ]['args'] : [];
453
-        $cpt_has_support   = ! empty($cpt_args['page_templates']);
454
-
455
-        $post_templates = wp_get_theme()->get_post_templates();
456
-        // if there are $post_templates for this cpt, then we return false for this method because
457
-        // that means we aren't going to load our page template manager and leave that up to the native
458
-        // cpt template manager.
459
-        return ! isset($post_templates[ $cpt_name ]) ? $cpt_has_support : false;
460
-    }
461
-
462
-
463
-    /**
464
-     * Callback for the page_templates metabox selector.
465
-     *
466
-     * @return void
467
-     * @since %VER%
468
-     */
469
-    public function page_template_meta_box()
470
-    {
471
-        global $post;
472
-        $template = '';
473
-
474
-        $page_template_count = count(get_page_templates());
475
-        if ($page_template_count) {
476
-            $page_template = get_post_meta($post->ID, '_wp_page_template', true);
477
-            $template      = ! empty($page_template) ? $page_template : '';
478
-        }
479
-        ?>
276
+		// filter _autosave_containers
277
+		$containers = apply_filters(
278
+			'FHEE__EE_Admin_Page_CPT___load_autosave_scripts_styles__containers',
279
+			$this->_autosave_containers,
280
+			$this
281
+		);
282
+		$containers = apply_filters(
283
+			'FHEE__EE_Admin_Page_CPT__' . get_class($this) . '___load_autosave_scripts_styles__containers',
284
+			$containers,
285
+			$this
286
+		);
287
+
288
+		wp_localize_script(
289
+			'event_editor_js',
290
+			'EE_AUTOSAVE_IDS',
291
+			$containers
292
+		); // todo once we enable autosaves, this needs to be switched to localize with "cpt-autosave"
293
+
294
+		$unsaved_data_msg = [
295
+			'eventmsg'     => sprintf(
296
+				wp_strip_all_tags(
297
+					__(
298
+						"The changes you made to this %s will be lost if you navigate away from this page.",
299
+						'event_espresso'
300
+					)
301
+				),
302
+				$this->_cpt_object->labels->singular_name
303
+			),
304
+			'inputChanged' => 0,
305
+		];
306
+		wp_localize_script('event_editor_js', 'UNSAVED_DATA_MSG', $unsaved_data_msg);
307
+	}
308
+
309
+
310
+	/**
311
+	 * overloading the EE_Admin_Page parent load_page_dependencies so we can get the cpt stuff added in appropriately
312
+	 *
313
+	 * @return void
314
+	 * @throws EE_Error
315
+	 * @throws ReflectionException
316
+	 * @throws Throwable
317
+	 */
318
+	protected function _load_page_dependencies()
319
+	{
320
+		// we only add stuff if this is a cpt_route!
321
+		if (! $this->_cpt_route) {
322
+			parent::_load_page_dependencies();
323
+			return;
324
+		}
325
+		// now let's do some automatic filters into the wp_system
326
+		// and we'll check to make sure the CHILD class
327
+		// automatically has the required methods in place.
328
+		// the following filters are for setting all the redirects
329
+		// on DEFAULT WP custom post type actions
330
+		// let's add a hidden input to the post-edit form
331
+		// so we know when we have to trigger our custom redirects!
332
+		// Otherwise the redirects will happen on ALL post saves which wouldn't be good of course!
333
+		add_action('edit_form_after_title', [$this, 'cpt_post_form_hidden_input']);
334
+		// inject our Admin page nav tabs...
335
+		// let's make sure the nav tabs are set if they aren't already
336
+		// if ( empty( $this->_nav_tabs ) ) $this->_set_nav_tabs();
337
+		add_action('edit_form_top', [$this, 'inject_nav_tabs']);
338
+		// modify the post_updated messages array
339
+		add_action('post_updated_messages', [$this, 'post_update_messages']);
340
+		// This basically allows us to change the title of the "publish" metabox area
341
+		// on CPT pages by setting a 'publishbox' value in the $_labels property array in the child class.
342
+		$screen = $this->_cpt_routes[ $this->_req_action ];
343
+		if (! empty($this->_labels['publishbox'])) {
344
+			$this->addMetaBox(
345
+				'submitdiv',
346
+				$this->getPublishBoxTitle(),
347
+				'post_submit_meta_box',
348
+				$screen,
349
+				'side',
350
+				'core'
351
+			);
352
+		}
353
+		// let's add page_templates metabox if this cpt added support for it.
354
+		if ($this->_supports_page_templates($this->_cpt_object->name)) {
355
+			$this->addMetaBox(
356
+				'page_templates',
357
+				esc_html__('Page Template', 'event_espresso'),
358
+				[$this, 'page_template_meta_box'],
359
+				$screen,
360
+				'side'
361
+			);
362
+		}
363
+		// add preview button
364
+		// add_filter('get_sample_permalink_html', [PreviewButton::class, 'addButton'], 5, 2);
365
+		PreviewButton::addEventEditorPermalinkButton(5);
366
+		// add shortlink button to cpt edit screens.
367
+		//  We can do this as a universal thing BECAUSE, cpts use the same format for shortlinks as posts!
368
+		// add_filter('get_sample_permalink_html', [EventShortlinkButton::class, 'addButton'], 10, 2);
369
+		EventShortlinkButton::addEventEditorPermalinkButton();
370
+		// this is a filter that allows the addition of extra html after the permalink field on the wp post edit-form
371
+		// add_filter('get_sample_permalink_html', [TicketSelectorShortcodeButton::class, 'addButton'], 12, 2);
372
+		TicketSelectorShortcodeButton::addEventEditorPermalinkButton(12);
373
+		// insert our own post_stati dropdown
374
+		add_action('post_submitbox_misc_actions', [$this, 'custom_post_stati_dropdown']);
375
+		// This allows adding additional information to the publish post submitbox on the wp post edit form
376
+		if (method_exists($this, 'extra_misc_actions_publish_box')) {
377
+			add_action('post_submitbox_misc_actions', [$this, 'extra_misc_actions_publish_box']);
378
+		}
379
+		// This allows for adding additional stuff after the title field on the wp post edit form.
380
+		// This is also before the wp_editor for post description field.
381
+		if (method_exists($this, 'edit_form_after_title')) {
382
+			add_action('edit_form_after_title', [$this, 'edit_form_after_title']);
383
+		}
384
+		/**
385
+		 * Filtering WP's esc_url to capture urls pointing to core wp routes so they point to our route.
386
+		 */
387
+		add_filter('clean_url', [$this, 'switch_core_wp_urls_with_ours']);
388
+		parent::_load_page_dependencies();
389
+		// notice we are ALSO going to load the pagenow hook set for this route
390
+		// (see _before_page_setup for the reset of the pagenow global ).
391
+		// This is for any plugins that are doing things properly
392
+		// and hooking into the load page hook for core wp cpt routes.
393
+		global $pagenow;
394
+		add_action('load-' . $pagenow, [$this, 'modify_current_screen'], 20);
395
+		do_action('load-' . $pagenow);
396
+		add_action('admin_enqueue_scripts', [$this, 'setup_autosave_hooks'], 30);
397
+		// we route REALLY early.
398
+		try {
399
+			$this->_route_admin_request();
400
+		} catch (EE_Error $e) {
401
+			$e->get_error();
402
+		}
403
+	}
404
+
405
+
406
+	/**
407
+	 * Since we don't want users going to default core wp routes, this will check any wp urls run through the
408
+	 * esc_url() method and if we see a url matching a pattern for our routes, we'll modify it to point to OUR
409
+	 * route instead.
410
+	 *
411
+	 * @param string $good_protocol_url The escaped url.
412
+	 * @return string possibly a new url for our route.
413
+	 */
414
+	public function switch_core_wp_urls_with_ours(string $good_protocol_url): string
415
+	{
416
+		$routes_to_match = [
417
+			0 => [
418
+				'edit.php?post_type=espresso_attendees',
419
+				'admin.php?page=espresso_registrations&action=contact_list',
420
+			],
421
+			1 => [
422
+				'edit.php?post_type=' . $this->_cpt_object->name,
423
+				'admin.php?page=' . $this->_cpt_object->name,
424
+			],
425
+		];
426
+		foreach ($routes_to_match as $route_matches) {
427
+			if (strpos($good_protocol_url, $route_matches[0]) !== false) {
428
+				return str_replace($route_matches[0], $route_matches[1], $good_protocol_url);
429
+			}
430
+		}
431
+		return $good_protocol_url;
432
+	}
433
+
434
+
435
+	/**
436
+	 * Determine whether the current cpt supports page templates or not.
437
+	 *
438
+	 * @param string $cpt_name The cpt slug we're checking on.
439
+	 * @return bool True supported, false not.
440
+	 * @throws InvalidArgumentException
441
+	 * @throws InvalidDataTypeException
442
+	 * @throws InvalidInterfaceException
443
+	 * @since %VER%
444
+	 */
445
+	private function _supports_page_templates(string $cpt_name): bool
446
+	{
447
+		/** @var EventEspresso\core\domain\entities\custom_post_types\CustomPostTypeDefinitions $custom_post_types */
448
+		$custom_post_types = $this->loader->getShared(
449
+			'EventEspresso\core\domain\entities\custom_post_types\CustomPostTypeDefinitions'
450
+		);
451
+		$cpt_args          = $custom_post_types->getDefinitions();
452
+		$cpt_args          = isset($cpt_args[ $cpt_name ]) ? $cpt_args[ $cpt_name ]['args'] : [];
453
+		$cpt_has_support   = ! empty($cpt_args['page_templates']);
454
+
455
+		$post_templates = wp_get_theme()->get_post_templates();
456
+		// if there are $post_templates for this cpt, then we return false for this method because
457
+		// that means we aren't going to load our page template manager and leave that up to the native
458
+		// cpt template manager.
459
+		return ! isset($post_templates[ $cpt_name ]) ? $cpt_has_support : false;
460
+	}
461
+
462
+
463
+	/**
464
+	 * Callback for the page_templates metabox selector.
465
+	 *
466
+	 * @return void
467
+	 * @since %VER%
468
+	 */
469
+	public function page_template_meta_box()
470
+	{
471
+		global $post;
472
+		$template = '';
473
+
474
+		$page_template_count = count(get_page_templates());
475
+		if ($page_template_count) {
476
+			$page_template = get_post_meta($post->ID, '_wp_page_template', true);
477
+			$template      = ! empty($page_template) ? $page_template : '';
478
+		}
479
+		?>
480 480
         <p><strong><?php esc_html_e('Template', 'event_espresso') ?></strong></p>
481 481
         <label class="screen-reader-text" for="page_template">
482 482
             <?php esc_html_e('Page Template', 'event_espresso') ?>
@@ -486,457 +486,457 @@  discard block
 block discarded – undo
486 486
             <?php page_template_dropdown($template); ?>
487 487
         </select>
488 488
         <?php
489
-    }
490
-
491
-
492
-    /**
493
-     * if this post is a draft or scheduled post then we provide a preview button for user to click
494
-     * Method is called from parent and is hooked into the wp 'get_sample_permalink_html' filter.
495
-     *
496
-     * @param string $return    the current html
497
-     * @param int    $id        the post id for the page
498
-     * @return string            The new html string for the permalink area
499
-     * @deprecated 5.0.0.p
500
-     * @see PreviewButton::addButton()
501
-     */
502
-    public function preview_button_html(string $return, int $id): string
503
-    {
504
-        return PreviewButton::addButton($return, $id);
505
-    }
506
-
507
-
508
-    /**
509
-     * add our custom post status dropdown on the wp post page for this cpt
510
-     *
511
-     * @return void
512
-     */
513
-    public function custom_post_stati_dropdown()
514
-    {
515
-        $statuses         = $this->_cpt_model_obj->get_custom_post_statuses();
516
-        $cur_status_label = array_key_exists($this->_cpt_model_obj->status(), $statuses)
517
-            ? $statuses[ $this->_cpt_model_obj->status() ]
518
-            : '';
519
-        $template_args    = [
520
-            'cur_status'            => $this->_cpt_model_obj->status(),
521
-            'statuses'              => $statuses,
522
-            'cur_status_label'      => $cur_status_label,
523
-            'localized_status_save' => sprintf(esc_html__('Save %s', 'event_espresso'), $cur_status_label),
524
-        ];
525
-        // we'll add a trash post status (WP doesn't add one for some reason)
526
-        if ($this->_cpt_model_obj->status() === 'trash') {
527
-            $template_args['cur_status_label'] = esc_html__('Trashed', 'event_espresso');
528
-            $statuses['trash']                 = esc_html__('Trashed', 'event_espresso');
529
-            $template_args['statuses']         = $statuses;
530
-        }
531
-
532
-        $template = EE_ADMIN_TEMPLATE . 'status_dropdown.template.php';
533
-        EEH_Template::display_template($template, $template_args);
534
-    }
535
-
536
-
537
-    public function setup_autosave_hooks()
538
-    {
539
-        $this->_set_autosave_containers();
540
-        $this->_load_autosave_scripts_styles();
541
-    }
542
-
543
-
544
-    /**
545
-     * This is run on all WordPress autosaves AFTER the autosave is complete and sends along a post object (available
546
-     * in request data) containing: post_ID of the saved post autosavenonce for the saved post We'll do the check
547
-     * for the nonce in here, but then this method looks for two things:
548
-     * 1. Execute a method (if exists) matching 'ee_autosave_' and appended with the given route. OR
549
-     * 2. do_actions() for global or class specific actions that have been registered (for plugins/addons not in an
550
-     * EE_Admin_Page class. PLEASE NOTE: Data will be returned using the _return_json() object and so the
551
-     * $_template_args property should be used to hold the $data array.  We're expecting the following things set in
552
-     * template args.
553
-     *    1. $template_args['error'] = IF there is an error you can add the message in here.
554
-     *    2. $template_args['data']['items'] = an array of items that are setup in key index pairs of 'where_values_go'
555
-     *    => 'values_to_add'.  In other words, for the datetime metabox we'll have something like
556
-     *    $this->_template_args['data']['items'] = array(
557
-     *        'event-datetime-ids' => '1,2,3';
558
-     *    );
559
-     *    Keep in mind the following things:
560
-     *    - "where" index is for the input with the id as that string.
561
-     *    - "what" index is what will be used for the value of that input.
562
-     *
563
-     * @return void
564
-     * @throws EE_Error
565
-     */
566
-    public function do_extra_autosave_stuff()
567
-    {
568
-        // next let's check for the autosave nonce (we'll use _verify_nonce )
569
-        $nonce = $this->request->getRequestParam('autosavenonce');
570
-        $this->_verify_nonce($nonce, 'autosave');
571
-        // make sure we define doing autosave (cause WP isn't triggering this we want to make sure we define it)
572
-        if (! defined('DOING_AUTOSAVE')) {
573
-            define('DOING_AUTOSAVE', true);
574
-        }
575
-        // if we made it here then the nonce checked out.  Let's run our methods and actions
576
-        $autosave = "_ee_autosave_$this->_current_view";
577
-        if (method_exists($this, $autosave)) {
578
-            $this->$autosave();
579
-        } else {
580
-            $this->_template_args['success'] = true;
581
-        }
582
-        do_action('AHEE__EE_Admin_Page_CPT__do_extra_autosave_stuff__global_after', $this);
583
-        do_action('AHEE__EE_Admin_Page_CPT__do_extra_autosave_stuff__after_' . get_class($this), $this);
584
-        // now let's return json
585
-        $this->_return_json();
586
-    }
587
-
588
-
589
-    /**
590
-     * This takes care of setting up default routes and pages that utilize the core WP admin pages.
591
-     * Child classes can override the defaults (in cases for adding metaboxes etc.)
592
-     * but take care that you include the defaults here otherwise your core WP admin pages for the cpt won't work!
593
-     *
594
-     * @return void
595
-     * @throws EE_Error
596
-     * @throws ReflectionException
597
-     */
598
-    protected function _extend_page_config_for_cpt()
599
-    {
600
-        // before doing anything we need to make sure this runs ONLY when the loaded page matches the set page_slug
601
-        if ($this->raw_req_page !== $this->page_slug) {
602
-            return;
603
-        }
604
-        // set page routes and page config but ONLY if we're not viewing a custom setup cpt route as defined in _cpt_routes
605
-        if (! empty($this->_cpt_object)) {
606
-            $this->_page_routes = array_merge(
607
-                [
608
-                    'create_new' => [$this, '_create_new_cpt_item'],
609
-                    'edit'       => [$this, '_edit_cpt_item'],
610
-                ],
611
-                $this->_page_routes
612
-            );
613
-            $this->_page_config = array_merge(
614
-                [
615
-                    'create_new' => [
616
-                        'nav'           => [
617
-                            'label' => $this->_cpt_object->labels->add_new_item,
618
-                            'order' => 5,
619
-                        ],
620
-                        'require_nonce' => false,
621
-                    ],
622
-                    'edit'       => [
623
-                        'nav'           => [
624
-                            'label'      => $this->_cpt_object->labels->edit_item,
625
-                            'order'      => 5,
626
-                            'persistent' => false,
627
-                            'url'        => '',
628
-                        ],
629
-                        'require_nonce' => false,
630
-                    ],
631
-                ],
632
-                $this->_page_config
633
-            );
634
-        }
635
-        // load the next section only if this is a matching cpt route as set in the cpt routes array.
636
-        if (! isset($this->_cpt_routes[ $this->_req_action ])) {
637
-            return;
638
-        }
639
-        $this->_cpt_route = true;
640
-        // $this->_cpt_route = isset($this->_cpt_routes[ $this->_req_action ]);
641
-        // add_action('FHEE__EE_Admin_Page___load_page_dependencies__after_load', array( $this, 'modify_current_screen') );
642
-        if (empty($this->_cpt_object)) {
643
-            $msg = sprintf(
644
-                esc_html__(
645
-                    'This page has been set as being related to a registered custom post type, however, the custom post type object could not be retrieved. There are two possible reasons for this:  1. The "%s" does not match a registered post type. or 2. The custom post type is not registered for the "%s" action as indexed in the "$_cpt_routes" property on this class (%s).',
646
-                    'event_espresso'
647
-                ),
648
-                $this->page_slug,
649
-                $this->_req_action,
650
-                get_class($this)
651
-            );
652
-            throw new EE_Error($msg);
653
-        }
654
-        $this->_set_model_object($this->request->getRequestParam('post', 0, DataType::INT));
655
-    }
656
-
657
-
658
-    /**
659
-     * Sets the _cpt_model_object property using what has been set for the _cpt_model_name and a given id.
660
-     *
661
-     * @param int    $id       The id to retrieve the model object for. If empty we set a default object.
662
-     * @param bool   $ignore_route_check
663
-     * @param string $req_type whether the current route is for inserting, updating, or deleting the CPT
664
-     * @throws EE_Error
665
-     * @throws InvalidArgumentException
666
-     * @throws InvalidDataTypeException
667
-     * @throws InvalidInterfaceException
668
-     * @throws ReflectionException
669
-     */
670
-    protected function _set_model_object(int $id = 0, bool $ignore_route_check = false, string $req_type = '')
671
-    {
672
-        $model = null;
673
-        if (
674
-            empty($this->_cpt_model_names)
675
-            || (
676
-                ! $ignore_route_check
677
-                && ! isset($this->_cpt_routes[ $this->_req_action ])
678
-            )
679
-            || (
680
-                $this->_cpt_model_obj instanceof EE_CPT_Base
681
-                && $this->_cpt_model_obj->ID() === $id
682
-            )
683
-        ) {
684
-            // get out cuz we either don't have a model name OR the object has already been set and it has the same id as what has been sent.
685
-            return;
686
-        }
687
-        // if ignore_route_check is true, then get the model name via CustomPostTypeDefinitions
688
-        if ($ignore_route_check) {
689
-            $post_type = get_post_type($id);
690
-            /** @var EventEspresso\core\domain\entities\custom_post_types\CustomPostTypeDefinitions $custom_post_types */
691
-            $custom_post_types = $this->loader->getShared(
692
-                'EventEspresso\core\domain\entities\custom_post_types\CustomPostTypeDefinitions'
693
-            );
694
-            $model_names       = $custom_post_types->getCustomPostTypeModelNames($post_type);
695
-            if (isset($model_names[ $post_type ])) {
696
-                $model = EE_Registry::instance()->load_model($model_names[ $post_type ]);
697
-            }
698
-        } else {
699
-            $model = EE_Registry::instance()->load_model($this->_cpt_model_names[ $this->_req_action ]);
700
-        }
701
-        if ($model instanceof EEM_Base) {
702
-            $this->_cpt_model_obj = ! empty($id) ? $model->get_one_by_ID($id) : $model->create_default_object();
703
-        }
704
-        do_action(
705
-            'AHEE__EE_Admin_Page_CPT__set_model_object__after_set_object',
706
-            $this->_cpt_model_obj,
707
-            $req_type
708
-        );
709
-    }
710
-
711
-
712
-    /**
713
-     * admin_init_global
714
-     * This runs all the code that we want executed within the WP admin_init hook.
715
-     * This method executes for ALL EE Admin pages.
716
-     *
717
-     * @return void
718
-     */
719
-    public function admin_init_global()
720
-    {
721
-        $post_ID = $this->request->getRequestParam('post', 0, DataType::INT);
722
-        // its possible this is a new save so let's catch that instead
723
-        $post_ID        = $this->request->getRequestParam('post_ID', $post_ID, DataType::INT);
724
-        $post           = get_post($post_ID);
725
-        $post_type      = $post instanceof WP_Post ? $post->post_type : false;
726
-        $current_route  = $this->request->getRequestParam('current_route', 'shouldneverwork');
727
-        $route_to_check = $post_type && isset($this->_cpt_routes[ $current_route ])
728
-            ? $this->_cpt_routes[ $current_route ]
729
-            : '';
730
-        add_filter('get_delete_post_link', [$this, 'modify_delete_post_link'], 10, 2);
731
-        add_filter('get_edit_post_link', [$this, 'modify_edit_post_link'], 10, 2);
732
-        if ($post_type === $route_to_check) {
733
-            add_filter('redirect_post_location', [$this, 'cpt_post_location_redirect'], 10, 2);
734
-        }
735
-        // now let's filter redirect if we're on a revision page and the revision is for an event CPT.
736
-        $revision = $this->request->getRequestParam('revision');
737
-        if (! empty($revision)) {
738
-            $action = $this->request->getRequestParam('action');
739
-            // doing a restore?
740
-            if (! empty($action) && $action === 'restore') {
741
-                // get post for revision
742
-                $rev_post   = get_post($revision);
743
-                $rev_parent = get_post($rev_post->post_parent);
744
-                // only do our redirect filter AND our restore revision action if the post_type for the parent is one of our cpts.
745
-                if ($rev_parent && $rev_parent->post_type === $this->page_slug) {
746
-                    add_filter('wp_redirect', [$this, 'revision_redirect']);
747
-                    // restores of revisions
748
-                    add_action('wp_restore_post_revision', [$this, 'restore_revision'], 10, 2);
749
-                }
750
-            }
751
-        }
752
-        // NOTE we ONLY want to run these hooks if we're on the right class for the given post type.  Otherwise we could see some really freaky things happen!
753
-        if ($post_type && $post_type === $route_to_check) {
754
-            // $post_id, $post
755
-            add_action('save_post', [$this, 'insert_update'], 10, 3);
756
-            // $post_id
757
-            add_action('trashed_post', [$this, 'before_trash_cpt_item']);
758
-            add_action('trashed_post', [$this, 'dont_permanently_delete_ee_cpts']);
759
-            add_action('untrashed_post', [$this, 'before_restore_cpt_item']);
760
-            add_action('after_delete_post', [$this, 'before_delete_cpt_item']);
761
-        }
762
-    }
763
-
764
-
765
-    /**
766
-     * Callback for the WordPress trashed_post hook.
767
-     * Execute some basic checks before calling the trash_cpt_item declared in the child class.
768
-     *
769
-     * @param int $post_id
770
-     * @throws EE_Error
771
-     * @throws ReflectionException
772
-     */
773
-    public function before_trash_cpt_item(int $post_id)
774
-    {
775
-        $this->_set_model_object($post_id, true, 'trash');
776
-        // if our cpt object isn't existent then get out immediately.
777
-        if (! $this->_cpt_model_obj instanceof EE_CPT_Base || $this->_cpt_model_obj->ID() !== $post_id) {
778
-            return;
779
-        }
780
-        $this->trash_cpt_item($post_id);
781
-    }
782
-
783
-
784
-    /**
785
-     * Callback for the WordPress untrashed_post hook.
786
-     * Execute some basic checks before calling the restore_cpt_method in the child class.
787
-     *
788
-     * @param $post_id
789
-     * @throws EE_Error
790
-     * @throws ReflectionException
791
-     */
792
-    public function before_restore_cpt_item($post_id)
793
-    {
794
-        $this->_set_model_object($post_id, true, 'restore');
795
-        // if our cpt object isn't existent then get out immediately.
796
-        if (! $this->_cpt_model_obj instanceof EE_CPT_Base || $this->_cpt_model_obj->ID() !== $post_id) {
797
-            return;
798
-        }
799
-        $this->restore_cpt_item($post_id);
800
-    }
801
-
802
-
803
-    /**
804
-     * Callback for the WordPress after_delete_post hook.
805
-     * Execute some basic checks before calling the delete_cpt_item method in the child class.
806
-     *
807
-     * @param $post_id
808
-     * @throws EE_Error
809
-     * @throws ReflectionException
810
-     */
811
-    public function before_delete_cpt_item($post_id)
812
-    {
813
-        $this->_set_model_object($post_id, true, 'delete');
814
-        // if our cpt object isn't existent then get out immediately.
815
-        if (! $this->_cpt_model_obj instanceof EE_CPT_Base || $this->_cpt_model_obj->ID() !== $post_id) {
816
-            return;
817
-        }
818
-        $this->delete_cpt_item($post_id);
819
-    }
820
-
821
-
822
-    /**
823
-     * This simply verifies if the cpt_model_object is instantiated for the given page and throws an error message
824
-     * accordingly.
825
-     *
826
-     * @return void
827
-     * @throws EE_Error
828
-     * @throws ReflectionException
829
-     */
830
-    public function verify_cpt_object()
831
-    {
832
-        $label = ! empty($this->_cpt_object) ? $this->_cpt_object->labels->singular_name : $this->page_label;
833
-        // verify event object
834
-        if (! $this->_cpt_model_obj instanceof EE_CPT_Base) {
835
-            throw new EE_Error(
836
-                sprintf(
837
-                    esc_html__(
838
-                        'Something has gone wrong with the page load because we are unable to set up the object for the %1$s.  This usually happens when the given id for the page route is NOT for the correct custom post type for this page',
839
-                        'event_espresso'
840
-                    ),
841
-                    $label
842
-                )
843
-            );
844
-        }
845
-        // if auto-draft then throw an error
846
-        if ($this->_cpt_model_obj->get('status') === 'auto-draft') {
847
-            EE_Error::overwrite_errors();
848
-            EE_Error::add_error(
849
-                sprintf(
850
-                    esc_html__(
851
-                        'This %1$s was saved without a title, description, or excerpt which means that none of the extra details you added were saved properly.  All autodrafts will show up in the "draft" view of your event list table.  You can delete them from there. Please click the "Add %1$s" button to refresh and restart.',
852
-                        'event_espresso'
853
-                    ),
854
-                    $label
855
-                ),
856
-                __FILE__,
857
-                __FUNCTION__,
858
-                __LINE__
859
-            );
860
-        }
861
-    }
862
-
863
-
864
-    /**
865
-     * admin_footer_scripts_global
866
-     * Anything triggered by the 'admin_print_footer_scripts' WP hook should be put in here. This particular method
867
-     * will apply on ALL EE_Admin pages.
868
-     *
869
-     * @return void
870
-     */
871
-    public function admin_footer_scripts_global()
872
-    {
873
-        $this->_add_admin_page_ajax_loading_img();
874
-        $this->_add_admin_page_overlay();
875
-    }
876
-
877
-
878
-    /**
879
-     * add in any global scripts for cpt routes
880
-     *
881
-     * @return void
882
-     */
883
-    public function load_global_scripts_styles()
884
-    {
885
-        parent::load_global_scripts_styles();
886
-        if ($this->_cpt_model_obj instanceof EE_CPT_Base) {
887
-            // setup custom post status object for localize script but only if we've got a cpt object
888
-            $statuses = $this->_cpt_model_obj->get_custom_post_statuses();
889
-            if (! empty($statuses)) {
890
-                // get ALL statuses!
891
-                $statuses = $this->_cpt_model_obj->get_all_post_statuses();
892
-                // setup object
893
-                $ee_cpt_statuses = [];
894
-                foreach ($statuses as $status => $label) {
895
-                    $ee_cpt_statuses[ $status ] = [
896
-                        'label'      => $label,
897
-                        'save_label' => sprintf(
898
-                            wp_strip_all_tags(__('Save as %s', 'event_espresso')),
899
-                            $label
900
-                        ),
901
-                    ];
902
-                }
903
-                wp_localize_script('ee_admin_js', 'eeCPTstatuses', $ee_cpt_statuses);
904
-            }
905
-        }
906
-    }
907
-
908
-
909
-    /**
910
-     * This is a wrapper for the insert/update routes for cpt items so we can add things that are common to ALL
911
-     * insert/updates
912
-     *
913
-     * @param int     $post_id ID of post being updated
914
-     * @param WP_Post $post    Post object from WP
915
-     * @param bool    $update  Whether this is an update or a new save.
916
-     * @return void
917
-     * @throws EE_Error
918
-     * @throws ReflectionException
919
-     */
920
-    public function insert_update(int $post_id, WP_Post $post, bool $update)
921
-    {
922
-        // make sure that if this is a revision OR trash action that we don't do any updates!
923
-        $action = $this->request->getRequestParam('action');
924
-        if ($action === 'restore' || $action === 'trash') {
925
-            return;
926
-        }
927
-        $this->_set_model_object($post_id, true, 'insert_update');
928
-        // if our cpt object is not instantiated and its NOT the same post_id as what is triggering this callback, then exit.
929
-        if (
930
-            $update
931
-            && (
932
-                ! $this->_cpt_model_obj instanceof EE_CPT_Base
933
-                || $this->_cpt_model_obj->ID() !== $post_id
934
-            )
935
-        ) {
936
-            return;
937
-        }
938
-        // check for autosave and update our req_data property accordingly.
939
-        /*if ( defined('DOING_AUTOSAVE') && DOING_AUTOSAVE && isset( $this->_req_data['ee_autosave_data'] ) ) {
489
+	}
490
+
491
+
492
+	/**
493
+	 * if this post is a draft or scheduled post then we provide a preview button for user to click
494
+	 * Method is called from parent and is hooked into the wp 'get_sample_permalink_html' filter.
495
+	 *
496
+	 * @param string $return    the current html
497
+	 * @param int    $id        the post id for the page
498
+	 * @return string            The new html string for the permalink area
499
+	 * @deprecated 5.0.0.p
500
+	 * @see PreviewButton::addButton()
501
+	 */
502
+	public function preview_button_html(string $return, int $id): string
503
+	{
504
+		return PreviewButton::addButton($return, $id);
505
+	}
506
+
507
+
508
+	/**
509
+	 * add our custom post status dropdown on the wp post page for this cpt
510
+	 *
511
+	 * @return void
512
+	 */
513
+	public function custom_post_stati_dropdown()
514
+	{
515
+		$statuses         = $this->_cpt_model_obj->get_custom_post_statuses();
516
+		$cur_status_label = array_key_exists($this->_cpt_model_obj->status(), $statuses)
517
+			? $statuses[ $this->_cpt_model_obj->status() ]
518
+			: '';
519
+		$template_args    = [
520
+			'cur_status'            => $this->_cpt_model_obj->status(),
521
+			'statuses'              => $statuses,
522
+			'cur_status_label'      => $cur_status_label,
523
+			'localized_status_save' => sprintf(esc_html__('Save %s', 'event_espresso'), $cur_status_label),
524
+		];
525
+		// we'll add a trash post status (WP doesn't add one for some reason)
526
+		if ($this->_cpt_model_obj->status() === 'trash') {
527
+			$template_args['cur_status_label'] = esc_html__('Trashed', 'event_espresso');
528
+			$statuses['trash']                 = esc_html__('Trashed', 'event_espresso');
529
+			$template_args['statuses']         = $statuses;
530
+		}
531
+
532
+		$template = EE_ADMIN_TEMPLATE . 'status_dropdown.template.php';
533
+		EEH_Template::display_template($template, $template_args);
534
+	}
535
+
536
+
537
+	public function setup_autosave_hooks()
538
+	{
539
+		$this->_set_autosave_containers();
540
+		$this->_load_autosave_scripts_styles();
541
+	}
542
+
543
+
544
+	/**
545
+	 * This is run on all WordPress autosaves AFTER the autosave is complete and sends along a post object (available
546
+	 * in request data) containing: post_ID of the saved post autosavenonce for the saved post We'll do the check
547
+	 * for the nonce in here, but then this method looks for two things:
548
+	 * 1. Execute a method (if exists) matching 'ee_autosave_' and appended with the given route. OR
549
+	 * 2. do_actions() for global or class specific actions that have been registered (for plugins/addons not in an
550
+	 * EE_Admin_Page class. PLEASE NOTE: Data will be returned using the _return_json() object and so the
551
+	 * $_template_args property should be used to hold the $data array.  We're expecting the following things set in
552
+	 * template args.
553
+	 *    1. $template_args['error'] = IF there is an error you can add the message in here.
554
+	 *    2. $template_args['data']['items'] = an array of items that are setup in key index pairs of 'where_values_go'
555
+	 *    => 'values_to_add'.  In other words, for the datetime metabox we'll have something like
556
+	 *    $this->_template_args['data']['items'] = array(
557
+	 *        'event-datetime-ids' => '1,2,3';
558
+	 *    );
559
+	 *    Keep in mind the following things:
560
+	 *    - "where" index is for the input with the id as that string.
561
+	 *    - "what" index is what will be used for the value of that input.
562
+	 *
563
+	 * @return void
564
+	 * @throws EE_Error
565
+	 */
566
+	public function do_extra_autosave_stuff()
567
+	{
568
+		// next let's check for the autosave nonce (we'll use _verify_nonce )
569
+		$nonce = $this->request->getRequestParam('autosavenonce');
570
+		$this->_verify_nonce($nonce, 'autosave');
571
+		// make sure we define doing autosave (cause WP isn't triggering this we want to make sure we define it)
572
+		if (! defined('DOING_AUTOSAVE')) {
573
+			define('DOING_AUTOSAVE', true);
574
+		}
575
+		// if we made it here then the nonce checked out.  Let's run our methods and actions
576
+		$autosave = "_ee_autosave_$this->_current_view";
577
+		if (method_exists($this, $autosave)) {
578
+			$this->$autosave();
579
+		} else {
580
+			$this->_template_args['success'] = true;
581
+		}
582
+		do_action('AHEE__EE_Admin_Page_CPT__do_extra_autosave_stuff__global_after', $this);
583
+		do_action('AHEE__EE_Admin_Page_CPT__do_extra_autosave_stuff__after_' . get_class($this), $this);
584
+		// now let's return json
585
+		$this->_return_json();
586
+	}
587
+
588
+
589
+	/**
590
+	 * This takes care of setting up default routes and pages that utilize the core WP admin pages.
591
+	 * Child classes can override the defaults (in cases for adding metaboxes etc.)
592
+	 * but take care that you include the defaults here otherwise your core WP admin pages for the cpt won't work!
593
+	 *
594
+	 * @return void
595
+	 * @throws EE_Error
596
+	 * @throws ReflectionException
597
+	 */
598
+	protected function _extend_page_config_for_cpt()
599
+	{
600
+		// before doing anything we need to make sure this runs ONLY when the loaded page matches the set page_slug
601
+		if ($this->raw_req_page !== $this->page_slug) {
602
+			return;
603
+		}
604
+		// set page routes and page config but ONLY if we're not viewing a custom setup cpt route as defined in _cpt_routes
605
+		if (! empty($this->_cpt_object)) {
606
+			$this->_page_routes = array_merge(
607
+				[
608
+					'create_new' => [$this, '_create_new_cpt_item'],
609
+					'edit'       => [$this, '_edit_cpt_item'],
610
+				],
611
+				$this->_page_routes
612
+			);
613
+			$this->_page_config = array_merge(
614
+				[
615
+					'create_new' => [
616
+						'nav'           => [
617
+							'label' => $this->_cpt_object->labels->add_new_item,
618
+							'order' => 5,
619
+						],
620
+						'require_nonce' => false,
621
+					],
622
+					'edit'       => [
623
+						'nav'           => [
624
+							'label'      => $this->_cpt_object->labels->edit_item,
625
+							'order'      => 5,
626
+							'persistent' => false,
627
+							'url'        => '',
628
+						],
629
+						'require_nonce' => false,
630
+					],
631
+				],
632
+				$this->_page_config
633
+			);
634
+		}
635
+		// load the next section only if this is a matching cpt route as set in the cpt routes array.
636
+		if (! isset($this->_cpt_routes[ $this->_req_action ])) {
637
+			return;
638
+		}
639
+		$this->_cpt_route = true;
640
+		// $this->_cpt_route = isset($this->_cpt_routes[ $this->_req_action ]);
641
+		// add_action('FHEE__EE_Admin_Page___load_page_dependencies__after_load', array( $this, 'modify_current_screen') );
642
+		if (empty($this->_cpt_object)) {
643
+			$msg = sprintf(
644
+				esc_html__(
645
+					'This page has been set as being related to a registered custom post type, however, the custom post type object could not be retrieved. There are two possible reasons for this:  1. The "%s" does not match a registered post type. or 2. The custom post type is not registered for the "%s" action as indexed in the "$_cpt_routes" property on this class (%s).',
646
+					'event_espresso'
647
+				),
648
+				$this->page_slug,
649
+				$this->_req_action,
650
+				get_class($this)
651
+			);
652
+			throw new EE_Error($msg);
653
+		}
654
+		$this->_set_model_object($this->request->getRequestParam('post', 0, DataType::INT));
655
+	}
656
+
657
+
658
+	/**
659
+	 * Sets the _cpt_model_object property using what has been set for the _cpt_model_name and a given id.
660
+	 *
661
+	 * @param int    $id       The id to retrieve the model object for. If empty we set a default object.
662
+	 * @param bool   $ignore_route_check
663
+	 * @param string $req_type whether the current route is for inserting, updating, or deleting the CPT
664
+	 * @throws EE_Error
665
+	 * @throws InvalidArgumentException
666
+	 * @throws InvalidDataTypeException
667
+	 * @throws InvalidInterfaceException
668
+	 * @throws ReflectionException
669
+	 */
670
+	protected function _set_model_object(int $id = 0, bool $ignore_route_check = false, string $req_type = '')
671
+	{
672
+		$model = null;
673
+		if (
674
+			empty($this->_cpt_model_names)
675
+			|| (
676
+				! $ignore_route_check
677
+				&& ! isset($this->_cpt_routes[ $this->_req_action ])
678
+			)
679
+			|| (
680
+				$this->_cpt_model_obj instanceof EE_CPT_Base
681
+				&& $this->_cpt_model_obj->ID() === $id
682
+			)
683
+		) {
684
+			// get out cuz we either don't have a model name OR the object has already been set and it has the same id as what has been sent.
685
+			return;
686
+		}
687
+		// if ignore_route_check is true, then get the model name via CustomPostTypeDefinitions
688
+		if ($ignore_route_check) {
689
+			$post_type = get_post_type($id);
690
+			/** @var EventEspresso\core\domain\entities\custom_post_types\CustomPostTypeDefinitions $custom_post_types */
691
+			$custom_post_types = $this->loader->getShared(
692
+				'EventEspresso\core\domain\entities\custom_post_types\CustomPostTypeDefinitions'
693
+			);
694
+			$model_names       = $custom_post_types->getCustomPostTypeModelNames($post_type);
695
+			if (isset($model_names[ $post_type ])) {
696
+				$model = EE_Registry::instance()->load_model($model_names[ $post_type ]);
697
+			}
698
+		} else {
699
+			$model = EE_Registry::instance()->load_model($this->_cpt_model_names[ $this->_req_action ]);
700
+		}
701
+		if ($model instanceof EEM_Base) {
702
+			$this->_cpt_model_obj = ! empty($id) ? $model->get_one_by_ID($id) : $model->create_default_object();
703
+		}
704
+		do_action(
705
+			'AHEE__EE_Admin_Page_CPT__set_model_object__after_set_object',
706
+			$this->_cpt_model_obj,
707
+			$req_type
708
+		);
709
+	}
710
+
711
+
712
+	/**
713
+	 * admin_init_global
714
+	 * This runs all the code that we want executed within the WP admin_init hook.
715
+	 * This method executes for ALL EE Admin pages.
716
+	 *
717
+	 * @return void
718
+	 */
719
+	public function admin_init_global()
720
+	{
721
+		$post_ID = $this->request->getRequestParam('post', 0, DataType::INT);
722
+		// its possible this is a new save so let's catch that instead
723
+		$post_ID        = $this->request->getRequestParam('post_ID', $post_ID, DataType::INT);
724
+		$post           = get_post($post_ID);
725
+		$post_type      = $post instanceof WP_Post ? $post->post_type : false;
726
+		$current_route  = $this->request->getRequestParam('current_route', 'shouldneverwork');
727
+		$route_to_check = $post_type && isset($this->_cpt_routes[ $current_route ])
728
+			? $this->_cpt_routes[ $current_route ]
729
+			: '';
730
+		add_filter('get_delete_post_link', [$this, 'modify_delete_post_link'], 10, 2);
731
+		add_filter('get_edit_post_link', [$this, 'modify_edit_post_link'], 10, 2);
732
+		if ($post_type === $route_to_check) {
733
+			add_filter('redirect_post_location', [$this, 'cpt_post_location_redirect'], 10, 2);
734
+		}
735
+		// now let's filter redirect if we're on a revision page and the revision is for an event CPT.
736
+		$revision = $this->request->getRequestParam('revision');
737
+		if (! empty($revision)) {
738
+			$action = $this->request->getRequestParam('action');
739
+			// doing a restore?
740
+			if (! empty($action) && $action === 'restore') {
741
+				// get post for revision
742
+				$rev_post   = get_post($revision);
743
+				$rev_parent = get_post($rev_post->post_parent);
744
+				// only do our redirect filter AND our restore revision action if the post_type for the parent is one of our cpts.
745
+				if ($rev_parent && $rev_parent->post_type === $this->page_slug) {
746
+					add_filter('wp_redirect', [$this, 'revision_redirect']);
747
+					// restores of revisions
748
+					add_action('wp_restore_post_revision', [$this, 'restore_revision'], 10, 2);
749
+				}
750
+			}
751
+		}
752
+		// NOTE we ONLY want to run these hooks if we're on the right class for the given post type.  Otherwise we could see some really freaky things happen!
753
+		if ($post_type && $post_type === $route_to_check) {
754
+			// $post_id, $post
755
+			add_action('save_post', [$this, 'insert_update'], 10, 3);
756
+			// $post_id
757
+			add_action('trashed_post', [$this, 'before_trash_cpt_item']);
758
+			add_action('trashed_post', [$this, 'dont_permanently_delete_ee_cpts']);
759
+			add_action('untrashed_post', [$this, 'before_restore_cpt_item']);
760
+			add_action('after_delete_post', [$this, 'before_delete_cpt_item']);
761
+		}
762
+	}
763
+
764
+
765
+	/**
766
+	 * Callback for the WordPress trashed_post hook.
767
+	 * Execute some basic checks before calling the trash_cpt_item declared in the child class.
768
+	 *
769
+	 * @param int $post_id
770
+	 * @throws EE_Error
771
+	 * @throws ReflectionException
772
+	 */
773
+	public function before_trash_cpt_item(int $post_id)
774
+	{
775
+		$this->_set_model_object($post_id, true, 'trash');
776
+		// if our cpt object isn't existent then get out immediately.
777
+		if (! $this->_cpt_model_obj instanceof EE_CPT_Base || $this->_cpt_model_obj->ID() !== $post_id) {
778
+			return;
779
+		}
780
+		$this->trash_cpt_item($post_id);
781
+	}
782
+
783
+
784
+	/**
785
+	 * Callback for the WordPress untrashed_post hook.
786
+	 * Execute some basic checks before calling the restore_cpt_method in the child class.
787
+	 *
788
+	 * @param $post_id
789
+	 * @throws EE_Error
790
+	 * @throws ReflectionException
791
+	 */
792
+	public function before_restore_cpt_item($post_id)
793
+	{
794
+		$this->_set_model_object($post_id, true, 'restore');
795
+		// if our cpt object isn't existent then get out immediately.
796
+		if (! $this->_cpt_model_obj instanceof EE_CPT_Base || $this->_cpt_model_obj->ID() !== $post_id) {
797
+			return;
798
+		}
799
+		$this->restore_cpt_item($post_id);
800
+	}
801
+
802
+
803
+	/**
804
+	 * Callback for the WordPress after_delete_post hook.
805
+	 * Execute some basic checks before calling the delete_cpt_item method in the child class.
806
+	 *
807
+	 * @param $post_id
808
+	 * @throws EE_Error
809
+	 * @throws ReflectionException
810
+	 */
811
+	public function before_delete_cpt_item($post_id)
812
+	{
813
+		$this->_set_model_object($post_id, true, 'delete');
814
+		// if our cpt object isn't existent then get out immediately.
815
+		if (! $this->_cpt_model_obj instanceof EE_CPT_Base || $this->_cpt_model_obj->ID() !== $post_id) {
816
+			return;
817
+		}
818
+		$this->delete_cpt_item($post_id);
819
+	}
820
+
821
+
822
+	/**
823
+	 * This simply verifies if the cpt_model_object is instantiated for the given page and throws an error message
824
+	 * accordingly.
825
+	 *
826
+	 * @return void
827
+	 * @throws EE_Error
828
+	 * @throws ReflectionException
829
+	 */
830
+	public function verify_cpt_object()
831
+	{
832
+		$label = ! empty($this->_cpt_object) ? $this->_cpt_object->labels->singular_name : $this->page_label;
833
+		// verify event object
834
+		if (! $this->_cpt_model_obj instanceof EE_CPT_Base) {
835
+			throw new EE_Error(
836
+				sprintf(
837
+					esc_html__(
838
+						'Something has gone wrong with the page load because we are unable to set up the object for the %1$s.  This usually happens when the given id for the page route is NOT for the correct custom post type for this page',
839
+						'event_espresso'
840
+					),
841
+					$label
842
+				)
843
+			);
844
+		}
845
+		// if auto-draft then throw an error
846
+		if ($this->_cpt_model_obj->get('status') === 'auto-draft') {
847
+			EE_Error::overwrite_errors();
848
+			EE_Error::add_error(
849
+				sprintf(
850
+					esc_html__(
851
+						'This %1$s was saved without a title, description, or excerpt which means that none of the extra details you added were saved properly.  All autodrafts will show up in the "draft" view of your event list table.  You can delete them from there. Please click the "Add %1$s" button to refresh and restart.',
852
+						'event_espresso'
853
+					),
854
+					$label
855
+				),
856
+				__FILE__,
857
+				__FUNCTION__,
858
+				__LINE__
859
+			);
860
+		}
861
+	}
862
+
863
+
864
+	/**
865
+	 * admin_footer_scripts_global
866
+	 * Anything triggered by the 'admin_print_footer_scripts' WP hook should be put in here. This particular method
867
+	 * will apply on ALL EE_Admin pages.
868
+	 *
869
+	 * @return void
870
+	 */
871
+	public function admin_footer_scripts_global()
872
+	{
873
+		$this->_add_admin_page_ajax_loading_img();
874
+		$this->_add_admin_page_overlay();
875
+	}
876
+
877
+
878
+	/**
879
+	 * add in any global scripts for cpt routes
880
+	 *
881
+	 * @return void
882
+	 */
883
+	public function load_global_scripts_styles()
884
+	{
885
+		parent::load_global_scripts_styles();
886
+		if ($this->_cpt_model_obj instanceof EE_CPT_Base) {
887
+			// setup custom post status object for localize script but only if we've got a cpt object
888
+			$statuses = $this->_cpt_model_obj->get_custom_post_statuses();
889
+			if (! empty($statuses)) {
890
+				// get ALL statuses!
891
+				$statuses = $this->_cpt_model_obj->get_all_post_statuses();
892
+				// setup object
893
+				$ee_cpt_statuses = [];
894
+				foreach ($statuses as $status => $label) {
895
+					$ee_cpt_statuses[ $status ] = [
896
+						'label'      => $label,
897
+						'save_label' => sprintf(
898
+							wp_strip_all_tags(__('Save as %s', 'event_espresso')),
899
+							$label
900
+						),
901
+					];
902
+				}
903
+				wp_localize_script('ee_admin_js', 'eeCPTstatuses', $ee_cpt_statuses);
904
+			}
905
+		}
906
+	}
907
+
908
+
909
+	/**
910
+	 * This is a wrapper for the insert/update routes for cpt items so we can add things that are common to ALL
911
+	 * insert/updates
912
+	 *
913
+	 * @param int     $post_id ID of post being updated
914
+	 * @param WP_Post $post    Post object from WP
915
+	 * @param bool    $update  Whether this is an update or a new save.
916
+	 * @return void
917
+	 * @throws EE_Error
918
+	 * @throws ReflectionException
919
+	 */
920
+	public function insert_update(int $post_id, WP_Post $post, bool $update)
921
+	{
922
+		// make sure that if this is a revision OR trash action that we don't do any updates!
923
+		$action = $this->request->getRequestParam('action');
924
+		if ($action === 'restore' || $action === 'trash') {
925
+			return;
926
+		}
927
+		$this->_set_model_object($post_id, true, 'insert_update');
928
+		// if our cpt object is not instantiated and its NOT the same post_id as what is triggering this callback, then exit.
929
+		if (
930
+			$update
931
+			&& (
932
+				! $this->_cpt_model_obj instanceof EE_CPT_Base
933
+				|| $this->_cpt_model_obj->ID() !== $post_id
934
+			)
935
+		) {
936
+			return;
937
+		}
938
+		// check for autosave and update our req_data property accordingly.
939
+		/*if ( defined('DOING_AUTOSAVE') && DOING_AUTOSAVE && isset( $this->_req_data['ee_autosave_data'] ) ) {
940 940
             foreach( (array) $this->_req_data['ee_autosave_data'] as $id => $values ) {
941 941
 
942 942
                 foreach ( (array) $values as $key => $value ) {
@@ -946,549 +946,549 @@  discard block
 block discarded – undo
946 946
 
947 947
         }/**/ // TODO reactivate after autosave is implemented in 4.2
948 948
 
949
-        // take care of updating any selected page_template IF this cpt supports it.
950
-
951
-        $page_template = $this->request->getRequestParam('page_template');
952
-        if ($this->_supports_page_templates($post->post_type) && ! empty($page_template)) {
953
-            // wp version aware.
954
-            if (RecommendedVersions::compareWordPressVersion('4.7')) {
955
-                $page_templates = wp_get_theme()->get_page_templates();
956
-            } else {
957
-                $post->page_template = $page_template;
958
-                $page_templates      = wp_get_theme()->get_page_templates($post);
959
-            }
960
-            if ($page_template !== 'default' && ! isset($page_templates[ $page_template ])) {
961
-                EE_Error::add_error(
962
-                    esc_html__('Invalid Page Template.', 'event_espresso'),
963
-                    __FILE__,
964
-                    __FUNCTION__,
965
-                    __LINE__
966
-                );
967
-            } else {
968
-                update_post_meta($post_id, '_wp_page_template', $page_template);
969
-            }
970
-        }
971
-        if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
972
-            return;
973
-        } //TODO we'll remove this after reimplementing autosave in 4.2
974
-        $this->_insert_update_cpt_item($post_id, $post);
975
-    }
976
-
977
-
978
-    /**
979
-     * This hooks into the wp_trash_post() function and removes the `_wp_trash_meta_status` and `_wp_trash_meta_time`
980
-     * post meta IF the trashed post is one of our CPT's - note this method should only be called with our cpt routes
981
-     * so we don't have to check for our CPT.
982
-     *
983
-     * @param int $post_id ID of the post
984
-     * @return void
985
-     */
986
-    public function dont_permanently_delete_ee_cpts(int $post_id)
987
-    {
988
-        // only do this if we're actually processing one of our CPTs
989
-        // if our cpt object isn't existent then get out immediately.
990
-        if (! $this->_cpt_model_obj instanceof EE_CPT_Base) {
991
-            return;
992
-        }
993
-        delete_post_meta($post_id, '_wp_trash_meta_status');
994
-        delete_post_meta($post_id, '_wp_trash_meta_time');
995
-        // our cpts may have comments so let's take care of that too
996
-        delete_post_meta($post_id, '_wp_trash_meta_comments_status');
997
-    }
998
-
999
-
1000
-    /**
1001
-     * This is a wrapper for the restore_cpt_revision route for cpt items so we can make sure that when a revision is
1002
-     * triggered that we restore related items.  In order to work cpt classes MUST have a restore_cpt_revision method
1003
-     * in them. We also have our OWN action in here so addons can hook into the restore process easily.
1004
-     *
1005
-     * @param int $post_id     ID of cpt item
1006
-     * @param int $revision_id ID of revision being restored
1007
-     * @return void
1008
-     */
1009
-    public function restore_revision(int $post_id, int $revision_id)
1010
-    {
1011
-        $this->_restore_cpt_item($post_id, $revision_id);
1012
-        // global action
1013
-        do_action('AHEE_EE_Admin_Page_CPT__restore_revision', $post_id, $revision_id);
1014
-        // class specific action so you can limit hooking into a specific page.
1015
-        do_action('AHEE_EE_Admin_Page_CPT_' . get_class($this) . '__restore_revision', $post_id, $revision_id);
1016
-    }
1017
-
1018
-
1019
-    /**
1020
-     * @param int $post_id     ID of cpt item
1021
-     * @param int $revision_id ID of revision for item
1022
-     * @return void
1023
-     * @see restore_revision() for details
1024
-     */
1025
-    abstract protected function _restore_cpt_item(int $post_id, int $revision_id);
1026
-
1027
-
1028
-    /**
1029
-     * Execution of this method is added to the end of the load_page_dependencies method in the parent
1030
-     * so that we can fix a bug where default core metaboxes were not being called in the sidebar.
1031
-     * To fix we have to reset the current_screen using the page_slug
1032
-     * (which is identical - or should be - to our registered_post_type id.)
1033
-     * Also, since the core WP file loads the admin_header.php for WP
1034
-     * (and there are a bunch of other things edit-form-advanced.php loads that need to happen really early)
1035
-     * we need to load it NOW, hence our _route_admin_request in here. (Otherwise screen options won't be set).
1036
-     *
1037
-     * @return void
1038
-     * @throws EE_Error
1039
-     * @throws ReflectionException
1040
-     */
1041
-    public function modify_current_screen()
1042
-    {
1043
-        // ONLY do this if the current page_route IS a cpt route
1044
-        if (! $this->_cpt_route) {
1045
-            return;
1046
-        }
1047
-        // routing things REALLY early b/c this is a cpt admin page
1048
-        set_current_screen($this->_cpt_routes[ $this->_req_action ]);
1049
-        $this->_current_screen       = get_current_screen();
1050
-        $this->_current_screen->base = 'event-espresso';
1051
-        $this->_add_help_tabs(); // we make sure we add any help tabs back in!
1052
-        /*try {
949
+		// take care of updating any selected page_template IF this cpt supports it.
950
+
951
+		$page_template = $this->request->getRequestParam('page_template');
952
+		if ($this->_supports_page_templates($post->post_type) && ! empty($page_template)) {
953
+			// wp version aware.
954
+			if (RecommendedVersions::compareWordPressVersion('4.7')) {
955
+				$page_templates = wp_get_theme()->get_page_templates();
956
+			} else {
957
+				$post->page_template = $page_template;
958
+				$page_templates      = wp_get_theme()->get_page_templates($post);
959
+			}
960
+			if ($page_template !== 'default' && ! isset($page_templates[ $page_template ])) {
961
+				EE_Error::add_error(
962
+					esc_html__('Invalid Page Template.', 'event_espresso'),
963
+					__FILE__,
964
+					__FUNCTION__,
965
+					__LINE__
966
+				);
967
+			} else {
968
+				update_post_meta($post_id, '_wp_page_template', $page_template);
969
+			}
970
+		}
971
+		if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
972
+			return;
973
+		} //TODO we'll remove this after reimplementing autosave in 4.2
974
+		$this->_insert_update_cpt_item($post_id, $post);
975
+	}
976
+
977
+
978
+	/**
979
+	 * This hooks into the wp_trash_post() function and removes the `_wp_trash_meta_status` and `_wp_trash_meta_time`
980
+	 * post meta IF the trashed post is one of our CPT's - note this method should only be called with our cpt routes
981
+	 * so we don't have to check for our CPT.
982
+	 *
983
+	 * @param int $post_id ID of the post
984
+	 * @return void
985
+	 */
986
+	public function dont_permanently_delete_ee_cpts(int $post_id)
987
+	{
988
+		// only do this if we're actually processing one of our CPTs
989
+		// if our cpt object isn't existent then get out immediately.
990
+		if (! $this->_cpt_model_obj instanceof EE_CPT_Base) {
991
+			return;
992
+		}
993
+		delete_post_meta($post_id, '_wp_trash_meta_status');
994
+		delete_post_meta($post_id, '_wp_trash_meta_time');
995
+		// our cpts may have comments so let's take care of that too
996
+		delete_post_meta($post_id, '_wp_trash_meta_comments_status');
997
+	}
998
+
999
+
1000
+	/**
1001
+	 * This is a wrapper for the restore_cpt_revision route for cpt items so we can make sure that when a revision is
1002
+	 * triggered that we restore related items.  In order to work cpt classes MUST have a restore_cpt_revision method
1003
+	 * in them. We also have our OWN action in here so addons can hook into the restore process easily.
1004
+	 *
1005
+	 * @param int $post_id     ID of cpt item
1006
+	 * @param int $revision_id ID of revision being restored
1007
+	 * @return void
1008
+	 */
1009
+	public function restore_revision(int $post_id, int $revision_id)
1010
+	{
1011
+		$this->_restore_cpt_item($post_id, $revision_id);
1012
+		// global action
1013
+		do_action('AHEE_EE_Admin_Page_CPT__restore_revision', $post_id, $revision_id);
1014
+		// class specific action so you can limit hooking into a specific page.
1015
+		do_action('AHEE_EE_Admin_Page_CPT_' . get_class($this) . '__restore_revision', $post_id, $revision_id);
1016
+	}
1017
+
1018
+
1019
+	/**
1020
+	 * @param int $post_id     ID of cpt item
1021
+	 * @param int $revision_id ID of revision for item
1022
+	 * @return void
1023
+	 * @see restore_revision() for details
1024
+	 */
1025
+	abstract protected function _restore_cpt_item(int $post_id, int $revision_id);
1026
+
1027
+
1028
+	/**
1029
+	 * Execution of this method is added to the end of the load_page_dependencies method in the parent
1030
+	 * so that we can fix a bug where default core metaboxes were not being called in the sidebar.
1031
+	 * To fix we have to reset the current_screen using the page_slug
1032
+	 * (which is identical - or should be - to our registered_post_type id.)
1033
+	 * Also, since the core WP file loads the admin_header.php for WP
1034
+	 * (and there are a bunch of other things edit-form-advanced.php loads that need to happen really early)
1035
+	 * we need to load it NOW, hence our _route_admin_request in here. (Otherwise screen options won't be set).
1036
+	 *
1037
+	 * @return void
1038
+	 * @throws EE_Error
1039
+	 * @throws ReflectionException
1040
+	 */
1041
+	public function modify_current_screen()
1042
+	{
1043
+		// ONLY do this if the current page_route IS a cpt route
1044
+		if (! $this->_cpt_route) {
1045
+			return;
1046
+		}
1047
+		// routing things REALLY early b/c this is a cpt admin page
1048
+		set_current_screen($this->_cpt_routes[ $this->_req_action ]);
1049
+		$this->_current_screen       = get_current_screen();
1050
+		$this->_current_screen->base = 'event-espresso';
1051
+		$this->_add_help_tabs(); // we make sure we add any help tabs back in!
1052
+		/*try {
1053 1053
             $this->_route_admin_request();
1054 1054
         } catch ( EE_Error $e ) {
1055 1055
             $e->get_error();
1056 1056
         }/**/
1057
-    }
1058
-
1059
-
1060
-    /**
1061
-     * This allows child classes to modify the default editor title that appears when people add a new or edit an
1062
-     * existing CPT item.     * This uses the _labels property set by the child class via _define_page_props. Just make
1063
-     * sure you have a key in _labels property that equals 'editor_title' and the value can be whatever you want the
1064
-     * default to be.
1065
-     *
1066
-     * @param string|null $title The new title (or existing if there is no editor_title defined)
1067
-     * @return string|null
1068
-     */
1069
-    public function add_custom_editor_default_title(?string $title): ?string
1070
-    {
1071
-        return $this->_labels['editor_title'][ $this->_cpt_routes[ $this->_req_action ] ] ?? $title;
1072
-    }
1073
-
1074
-
1075
-    /**
1076
-     * hooks into the wp_get_shortlink button and makes sure that the shortlink gets generated
1077
-     *
1078
-     * @param string $shortlink   The already generated shortlink
1079
-     * @param int    $id          Post ID for this item
1080
-     * @return string
1081
-     * @deprecated 5.0.0.p
1082
-     * @see EventShortlinkButton::addButton()
1083
-     */
1084
-    public function add_shortlink_button_to_editor(string $shortlink, int $id): string
1085
-    {
1086
-        return EventShortlinkButton::addButton($shortlink, $id);
1087
-    }
1088
-
1089
-
1090
-    /**
1091
-     * overriding the parent route_admin_request method so we DON'T run the route twice on cpt core page loads (it's
1092
-     * already run in modify_current_screen())
1093
-     *
1094
-     * @return void
1095
-     * @throws EE_Error
1096
-     * @throws ReflectionException
1097
-     * @throws Throwable
1098
-     */
1099
-    public function route_admin_request()
1100
-    {
1101
-        if ($this->_cpt_route) {
1102
-            return;
1103
-        }
1104
-        try {
1105
-            $this->_route_admin_request();
1106
-        } catch (EE_Error $e) {
1107
-            $e->get_error();
1108
-        }
1109
-    }
1110
-
1111
-
1112
-    /**
1113
-     * Add a hidden form input to cpt core pages so that we know to do redirects to our routes on saves
1114
-     *
1115
-     * @return void
1116
-     */
1117
-    public function cpt_post_form_hidden_input()
1118
-    {
1119
-        // we're also going to add the route value and the current page so we can direct autosave parsing correctly
1120
-        echo '
1057
+	}
1058
+
1059
+
1060
+	/**
1061
+	 * This allows child classes to modify the default editor title that appears when people add a new or edit an
1062
+	 * existing CPT item.     * This uses the _labels property set by the child class via _define_page_props. Just make
1063
+	 * sure you have a key in _labels property that equals 'editor_title' and the value can be whatever you want the
1064
+	 * default to be.
1065
+	 *
1066
+	 * @param string|null $title The new title (or existing if there is no editor_title defined)
1067
+	 * @return string|null
1068
+	 */
1069
+	public function add_custom_editor_default_title(?string $title): ?string
1070
+	{
1071
+		return $this->_labels['editor_title'][ $this->_cpt_routes[ $this->_req_action ] ] ?? $title;
1072
+	}
1073
+
1074
+
1075
+	/**
1076
+	 * hooks into the wp_get_shortlink button and makes sure that the shortlink gets generated
1077
+	 *
1078
+	 * @param string $shortlink   The already generated shortlink
1079
+	 * @param int    $id          Post ID for this item
1080
+	 * @return string
1081
+	 * @deprecated 5.0.0.p
1082
+	 * @see EventShortlinkButton::addButton()
1083
+	 */
1084
+	public function add_shortlink_button_to_editor(string $shortlink, int $id): string
1085
+	{
1086
+		return EventShortlinkButton::addButton($shortlink, $id);
1087
+	}
1088
+
1089
+
1090
+	/**
1091
+	 * overriding the parent route_admin_request method so we DON'T run the route twice on cpt core page loads (it's
1092
+	 * already run in modify_current_screen())
1093
+	 *
1094
+	 * @return void
1095
+	 * @throws EE_Error
1096
+	 * @throws ReflectionException
1097
+	 * @throws Throwable
1098
+	 */
1099
+	public function route_admin_request()
1100
+	{
1101
+		if ($this->_cpt_route) {
1102
+			return;
1103
+		}
1104
+		try {
1105
+			$this->_route_admin_request();
1106
+		} catch (EE_Error $e) {
1107
+			$e->get_error();
1108
+		}
1109
+	}
1110
+
1111
+
1112
+	/**
1113
+	 * Add a hidden form input to cpt core pages so that we know to do redirects to our routes on saves
1114
+	 *
1115
+	 * @return void
1116
+	 */
1117
+	public function cpt_post_form_hidden_input()
1118
+	{
1119
+		// we're also going to add the route value and the current page so we can direct autosave parsing correctly
1120
+		echo '
1121 1121
         <input type="hidden" name="ee_cpt_item_redirect_url" value="' . esc_url_raw($this->_admin_base_url) . '"/>
1122 1122
         <div id="ee-cpt-hidden-inputs">
1123 1123
             <input type="hidden" id="current_route" name="current_route" value="' . esc_attr($this->_current_view) . '"/>
1124 1124
             <input type="hidden" id="current_page" name="current_page" value="' . esc_attr($this->page_slug) . '"/>
1125 1125
         </div>';
1126
-    }
1127
-
1128
-
1129
-    /**
1130
-     * This allows us to redirect the location of revision restores when they happen so it goes to our CPT routes.
1131
-     *
1132
-     * @param string $location Original location url
1133
-     * @return string           new (or original) url to redirect to.
1134
-     * @throws EE_Error
1135
-     */
1136
-    public function revision_redirect(string $location): string
1137
-    {
1138
-        // get revision
1139
-        $revision = $this->request->getRequestParam('revision');
1140
-        // can't do anything without revision so let's get out if not present
1141
-        if (empty($revision)) {
1142
-            return $location;
1143
-        }
1144
-        // get rev_post_data
1145
-        $rev        = get_post($revision);
1146
-        $admin_url  = $this->_admin_base_url;
1147
-        $query_args = [
1148
-            'action'   => 'edit',
1149
-            'post'     => $rev->post_parent,
1150
-            'revision' => $revision,
1151
-            'message'  => 5,
1152
-        ];
1153
-        $this->_process_notices($query_args, true);
1154
-        return EE_Admin_Page_CPT::add_query_args_and_nonce($query_args, $admin_url);
1155
-    }
1156
-
1157
-
1158
-    /**
1159
-     * Modify the edit post link generated by wp core function so that EE CPTs get setup differently.
1160
-     *
1161
-     * @param string|null $link     the original generated link
1162
-     * @param int    $id            post id
1163
-     * @return string               the link
1164
-     */
1165
-    public function modify_edit_post_link(?string $link, int $id): ?string
1166
-    {
1167
-        $post = get_post($id);
1168
-        $action = $this->request->getRequestParam('action');
1169
-        if (
1170
-            empty($action)
1171
-            || ! isset($this->_cpt_routes[ $action ])
1172
-            || $post->post_type !== $this->_cpt_routes[ $action ]
1173
-        ) {
1174
-            return $link;
1175
-        }
1176
-        $query_args = [
1177
-            'action' => $this->_cpt_edit_routes[ $post->post_type ] ?? 'edit',
1178
-            'post'   => $id,
1179
-        ];
1180
-        return EE_Admin_Page_CPT::add_query_args_and_nonce($query_args, $this->_admin_base_url);
1181
-    }
1182
-
1183
-
1184
-    /**
1185
-     * Modify the trash link on our cpt edit pages so it has the required query var for triggering redirect properly on
1186
-     * our routes.
1187
-     *
1188
-     * @param string|null $delete_link      original delete link
1189
-     * @param int    $post_id               id of cpt object
1190
-     * @return null|string                  new delete link
1191
-     * @throws EE_Error
1192
-     * @throws ReflectionException
1193
-     */
1194
-    public function modify_delete_post_link(?string $delete_link, int $post_id): ?string
1195
-    {
1196
-        $post = get_post($post_id);
1197
-        $action = $this->request->getRequestParam('action');
1198
-        if (
1199
-            ! $post instanceof WP_Post
1200
-            || empty($action)
1201
-            || ! isset($this->_cpt_routes[ $action ])
1202
-            || $post->post_type !== $this->_cpt_routes[ $action ]
1203
-        ) {
1204
-            return $delete_link;
1205
-        }
1206
-        $this->_set_model_object($post->ID, true);
1207
-
1208
-        // returns something like `trash_event` or `trash_attendee` or `trash_venue`
1209
-        $action = 'trash_' . str_replace('ee_', '', strtolower(get_class($this->_cpt_model_obj)));
1210
-
1211
-        return EE_Admin_Page::add_query_args_and_nonce(
1212
-            [
1213
-                'page'   => $this->request->getRequestParam('page'),
1214
-                'action' => $action,
1215
-                $this->_cpt_model_obj->get_model()->get_primary_key_field()->get_name() => $post->ID,
1216
-            ],
1217
-            admin_url()
1218
-        );
1219
-    }
1220
-
1221
-
1222
-    /**
1223
-     * This is the callback for the 'redirect_post_location' filter in wp-admin/post.php
1224
-     * so that we can hijack the default redirect locations for wp custom post types
1225
-     * that WE'RE using and send back to OUR routes.  This should only be hooked in on the right route.
1226
-     *
1227
-     * @param string $location This is the incoming currently set redirect location
1228
-     * @param string $post_id  This is the 'ID' value of the wp_posts table
1229
-     * @return string           the new location to redirect to
1230
-     * @throws EE_Error
1231
-     */
1232
-    public function cpt_post_location_redirect(string $location, string $post_id): string
1233
-    {
1234
-        // we DO have a match so let's setup the url
1235
-        // we have to get the post to determine our route
1236
-        $post       = get_post($post_id);
1237
-        $edit_route = $this->_cpt_edit_routes[ $post->post_type ];
1238
-        // shared query_args
1239
-        $query_args = ['action' => $edit_route, 'post' => $post_id];
1240
-
1241
-        $save = $this->request->getRequestParam('save');
1242
-        $publish = $this->request->getRequestParam('publish');
1243
-        $add_meta = $this->request->getRequestParam('addmeta');
1244
-        $delete_meta = $this->request->getRequestParam('deletemeta');
1245
-        if ($save || $publish) {
1246
-            $status = get_post_status($post_id);
1247
-            if ($publish) {
1248
-                switch ($status) {
1249
-                    case 'pending':
1250
-                        $message = 8;
1251
-                        break;
1252
-                    case 'future':
1253
-                        $message = 9;
1254
-                        break;
1255
-                    default:
1256
-                        $message = 6;
1257
-                }
1258
-            } else {
1259
-                $message = 'draft' === $status ? 10 : 1;
1260
-            }
1261
-        } elseif ($add_meta) {
1262
-            $message = 2;
1263
-        } elseif ($delete_meta) {
1264
-            $message = 3;
1265
-        } elseif ($this->request->getRequestParam('action') === 'post-quickpress-save-cont') {
1266
-            $message = 7;
1267
-        } else {
1268
-            $message = 4;
1269
-        }
1270
-        // change the message if the post type is not viewable on the frontend
1271
-        $this->_cpt_object = get_post_type_object($post->post_type);
1272
-
1273
-        $query_args['message'] = $message === 1 && ! $this->_cpt_object->publicly_queryable ? 4 : $message;
1274
-        $this->_process_notices($query_args, true);
1275
-        return EE_Admin_Page_CPT::add_query_args_and_nonce($query_args, $this->_admin_base_url);
1276
-    }
1277
-
1278
-
1279
-    /**
1280
-     * This method is called to inject nav tabs on core WP cpt pages
1281
-     *
1282
-     * @return void
1283
-     * @throws EE_Error
1284
-     */
1285
-    public function inject_nav_tabs()
1286
-    {
1287
-        echo wp_kses($this->_get_main_nav_tabs(), AllowedTags::getWithFormTags());
1288
-    }
1289
-
1290
-
1291
-    /**
1292
-     * This just sets up the post update messages when an update form is loaded
1293
-     *
1294
-     * @param array $messages the original messages array
1295
-     * @return array           the new messages array
1296
-     */
1297
-    public function post_update_messages(array $messages): array
1298
-    {
1299
-        global $post;
1300
-        $id       = $this->request->getRequestParam('post');
1301
-        $id       = empty($id) && is_object($post) ? $post->ID : null;
1302
-        $revision = $this->request->getRequestParam('revision', 0, 'int');
1303
-
1304
-        $messages[ $post->post_type ] = [
1305
-            0  => '', // Unused. Messages start at index 1.
1306
-            1  => sprintf(
1307
-                esc_html__('%1$s updated. %2$sView %1$s%3$s', 'event_espresso'),
1308
-                $this->_cpt_object->labels->singular_name,
1309
-                '<a href="' . esc_url(get_permalink($id)) . '">',
1310
-                '</a>'
1311
-            ),
1312
-            2  => esc_html__('Custom field updated', 'event_espresso'),
1313
-            3  => esc_html__('Custom field deleted.', 'event_espresso'),
1314
-            4  => sprintf(esc_html__('%1$s updated.', 'event_espresso'), $this->_cpt_object->labels->singular_name),
1315
-            5  => $revision
1316
-                ? sprintf(
1317
-                    esc_html__('%s restored to revision from %s', 'event_espresso'),
1318
-                    $this->_cpt_object->labels->singular_name,
1319
-                    wp_post_revision_title($revision, false)
1320
-                )
1321
-                : false,
1322
-            6  => sprintf(
1323
-                esc_html__('%1$s published. %2$sView %1$s%3$s', 'event_espresso'),
1324
-                $this->_cpt_object->labels->singular_name,
1325
-                '<a href="' . esc_url(get_permalink($id)) . '">',
1326
-                '</a>'
1327
-            ),
1328
-            7  => sprintf(esc_html__('%1$s saved.', 'event_espresso'), $this->_cpt_object->labels->singular_name),
1329
-            8  => sprintf(
1330
-                esc_html__('%1$s submitted. %2$sPreview %1$s%3$s', 'event_espresso'),
1331
-                $this->_cpt_object->labels->singular_name,
1332
-                '<a target="_blank" href="' . esc_url(add_query_arg('preview', 'true', get_permalink($id))) . '">',
1333
-                '</a>'
1334
-            ),
1335
-            9  => sprintf(
1336
-                esc_html__('%1$s scheduled for: %2$s. %3$s">Preview %1$s%3$s', 'event_espresso'),
1337
-                $this->_cpt_object->labels->singular_name,
1338
-                '<strong>' . date_i18n('M j, Y @ G:i', strtotime($post->post_date)) . '</strong>',
1339
-                '<a target="_blank" href="' . esc_url(get_permalink($id)),
1340
-                '</a>'
1341
-            ),
1342
-            10 => sprintf(
1343
-                esc_html__('%1$s draft updated. %2$s">Preview page%3$s', 'event_espresso'),
1344
-                $this->_cpt_object->labels->singular_name,
1345
-                '<a target="_blank" href="' . esc_url(add_query_arg('preview', 'true', get_permalink($id))),
1346
-                '</a>'
1347
-            ),
1348
-        ];
1349
-        return $messages;
1350
-    }
1351
-
1352
-
1353
-    /**
1354
-     * default method for the 'create_new' route for cpt admin pages.
1355
-     * For reference what to include in here, see wp-admin/post-new.php
1356
-     *
1357
-     * @return void
1358
-     */
1359
-    protected function _create_new_cpt_item()
1360
-    {
1361
-        // gather template vars for WP_ADMIN_PATH . 'edit-form-advanced.php'
1362
-        global $post, $title, $post_type, $post_type_object;
1363
-        $post_type        = $this->_cpt_routes[ $this->_req_action ];
1364
-        $post_type_object = $this->_cpt_object;
1365
-        $title            = $post_type_object->labels->add_new_item;
1366
-        $post             = $post = get_default_post_to_edit($this->_cpt_routes[ $this->_req_action ], true);
1367
-        add_action('admin_print_styles', [$this, 'add_new_admin_page_global']);
1368
-        // modify the default editor title field with default title.
1369
-        add_filter('enter_title_here', [$this, 'add_custom_editor_default_title']);
1370
-        $this->loadEditorTemplate();
1371
-    }
1372
-
1373
-
1374
-    /**
1375
-     * Enqueues auto-save and loads the editor template
1376
-     *
1377
-     * @param bool $creating
1378
-     */
1379
-    private function loadEditorTemplate(bool $creating = true)
1380
-    {
1381
-        if ($this->admin_config && ! $this->admin_config->useAdvancedEditor()) {
1382
-            add_filter('admin_body_class', function($classes)
1383
-            {
1384
-                $classes .= ' espresso-legacy-editor';
1385
-                return $classes;
1386
-            });
1387
-        }
1388
-
1389
-        global $post, $title, $is_IE, $post_type, $post_type_object;
1390
-        // these vars are used by the template
1391
-        $editing = true;
1392
-        $post_ID = $post->ID;
1393
-        if (apply_filters('FHEE__EE_Admin_Page_CPT___create_new_cpt_item__replace_editor', false, $post) === false) {
1394
-            // only enqueue autosave when creating event (necessary to get permalink/url generated)
1395
-            // otherwise EE doesn't support autosave fully, so to prevent user confusion we disable it in edit context.
1396
-            $action = $this->request->getRequestParam('action');
1397
-            if ($creating) {
1398
-                wp_enqueue_script('autosave');
1399
-            } elseif (
1400
-                isset($this->_cpt_routes[ $action ])
1401
-                && ! isset($this->_labels['hide_add_button_on_cpt_route'][ $action ])
1402
-            ) {
1403
-                $create_new_action = apply_filters(
1404
-                    'FHEE__EE_Admin_Page_CPT___edit_cpt_item__create_new_action',
1405
-                    'create_new',
1406
-                    $this
1407
-                );
1408
-                $post_new_file     = EE_Admin_Page::add_query_args_and_nonce(
1409
-                    [
1410
-                        'action' => $create_new_action,
1411
-                        'page'   => $this->page_slug,
1412
-                    ],
1413
-                    'admin.php'
1414
-                );
1415
-            }
1416
-            include_once WP_ADMIN_PATH . 'edit-form-advanced.php';
1417
-        }
1418
-    }
1419
-
1420
-
1421
-    public function add_new_admin_page_global()
1422
-    {
1423
-        $admin_page = $this->request->getRequestParam('post', 0, DataType::INT) !== 0
1424
-            ? 'post-php'
1425
-            : 'post-new-php';
1426
-        ?>
1126
+	}
1127
+
1128
+
1129
+	/**
1130
+	 * This allows us to redirect the location of revision restores when they happen so it goes to our CPT routes.
1131
+	 *
1132
+	 * @param string $location Original location url
1133
+	 * @return string           new (or original) url to redirect to.
1134
+	 * @throws EE_Error
1135
+	 */
1136
+	public function revision_redirect(string $location): string
1137
+	{
1138
+		// get revision
1139
+		$revision = $this->request->getRequestParam('revision');
1140
+		// can't do anything without revision so let's get out if not present
1141
+		if (empty($revision)) {
1142
+			return $location;
1143
+		}
1144
+		// get rev_post_data
1145
+		$rev        = get_post($revision);
1146
+		$admin_url  = $this->_admin_base_url;
1147
+		$query_args = [
1148
+			'action'   => 'edit',
1149
+			'post'     => $rev->post_parent,
1150
+			'revision' => $revision,
1151
+			'message'  => 5,
1152
+		];
1153
+		$this->_process_notices($query_args, true);
1154
+		return EE_Admin_Page_CPT::add_query_args_and_nonce($query_args, $admin_url);
1155
+	}
1156
+
1157
+
1158
+	/**
1159
+	 * Modify the edit post link generated by wp core function so that EE CPTs get setup differently.
1160
+	 *
1161
+	 * @param string|null $link     the original generated link
1162
+	 * @param int    $id            post id
1163
+	 * @return string               the link
1164
+	 */
1165
+	public function modify_edit_post_link(?string $link, int $id): ?string
1166
+	{
1167
+		$post = get_post($id);
1168
+		$action = $this->request->getRequestParam('action');
1169
+		if (
1170
+			empty($action)
1171
+			|| ! isset($this->_cpt_routes[ $action ])
1172
+			|| $post->post_type !== $this->_cpt_routes[ $action ]
1173
+		) {
1174
+			return $link;
1175
+		}
1176
+		$query_args = [
1177
+			'action' => $this->_cpt_edit_routes[ $post->post_type ] ?? 'edit',
1178
+			'post'   => $id,
1179
+		];
1180
+		return EE_Admin_Page_CPT::add_query_args_and_nonce($query_args, $this->_admin_base_url);
1181
+	}
1182
+
1183
+
1184
+	/**
1185
+	 * Modify the trash link on our cpt edit pages so it has the required query var for triggering redirect properly on
1186
+	 * our routes.
1187
+	 *
1188
+	 * @param string|null $delete_link      original delete link
1189
+	 * @param int    $post_id               id of cpt object
1190
+	 * @return null|string                  new delete link
1191
+	 * @throws EE_Error
1192
+	 * @throws ReflectionException
1193
+	 */
1194
+	public function modify_delete_post_link(?string $delete_link, int $post_id): ?string
1195
+	{
1196
+		$post = get_post($post_id);
1197
+		$action = $this->request->getRequestParam('action');
1198
+		if (
1199
+			! $post instanceof WP_Post
1200
+			|| empty($action)
1201
+			|| ! isset($this->_cpt_routes[ $action ])
1202
+			|| $post->post_type !== $this->_cpt_routes[ $action ]
1203
+		) {
1204
+			return $delete_link;
1205
+		}
1206
+		$this->_set_model_object($post->ID, true);
1207
+
1208
+		// returns something like `trash_event` or `trash_attendee` or `trash_venue`
1209
+		$action = 'trash_' . str_replace('ee_', '', strtolower(get_class($this->_cpt_model_obj)));
1210
+
1211
+		return EE_Admin_Page::add_query_args_and_nonce(
1212
+			[
1213
+				'page'   => $this->request->getRequestParam('page'),
1214
+				'action' => $action,
1215
+				$this->_cpt_model_obj->get_model()->get_primary_key_field()->get_name() => $post->ID,
1216
+			],
1217
+			admin_url()
1218
+		);
1219
+	}
1220
+
1221
+
1222
+	/**
1223
+	 * This is the callback for the 'redirect_post_location' filter in wp-admin/post.php
1224
+	 * so that we can hijack the default redirect locations for wp custom post types
1225
+	 * that WE'RE using and send back to OUR routes.  This should only be hooked in on the right route.
1226
+	 *
1227
+	 * @param string $location This is the incoming currently set redirect location
1228
+	 * @param string $post_id  This is the 'ID' value of the wp_posts table
1229
+	 * @return string           the new location to redirect to
1230
+	 * @throws EE_Error
1231
+	 */
1232
+	public function cpt_post_location_redirect(string $location, string $post_id): string
1233
+	{
1234
+		// we DO have a match so let's setup the url
1235
+		// we have to get the post to determine our route
1236
+		$post       = get_post($post_id);
1237
+		$edit_route = $this->_cpt_edit_routes[ $post->post_type ];
1238
+		// shared query_args
1239
+		$query_args = ['action' => $edit_route, 'post' => $post_id];
1240
+
1241
+		$save = $this->request->getRequestParam('save');
1242
+		$publish = $this->request->getRequestParam('publish');
1243
+		$add_meta = $this->request->getRequestParam('addmeta');
1244
+		$delete_meta = $this->request->getRequestParam('deletemeta');
1245
+		if ($save || $publish) {
1246
+			$status = get_post_status($post_id);
1247
+			if ($publish) {
1248
+				switch ($status) {
1249
+					case 'pending':
1250
+						$message = 8;
1251
+						break;
1252
+					case 'future':
1253
+						$message = 9;
1254
+						break;
1255
+					default:
1256
+						$message = 6;
1257
+				}
1258
+			} else {
1259
+				$message = 'draft' === $status ? 10 : 1;
1260
+			}
1261
+		} elseif ($add_meta) {
1262
+			$message = 2;
1263
+		} elseif ($delete_meta) {
1264
+			$message = 3;
1265
+		} elseif ($this->request->getRequestParam('action') === 'post-quickpress-save-cont') {
1266
+			$message = 7;
1267
+		} else {
1268
+			$message = 4;
1269
+		}
1270
+		// change the message if the post type is not viewable on the frontend
1271
+		$this->_cpt_object = get_post_type_object($post->post_type);
1272
+
1273
+		$query_args['message'] = $message === 1 && ! $this->_cpt_object->publicly_queryable ? 4 : $message;
1274
+		$this->_process_notices($query_args, true);
1275
+		return EE_Admin_Page_CPT::add_query_args_and_nonce($query_args, $this->_admin_base_url);
1276
+	}
1277
+
1278
+
1279
+	/**
1280
+	 * This method is called to inject nav tabs on core WP cpt pages
1281
+	 *
1282
+	 * @return void
1283
+	 * @throws EE_Error
1284
+	 */
1285
+	public function inject_nav_tabs()
1286
+	{
1287
+		echo wp_kses($this->_get_main_nav_tabs(), AllowedTags::getWithFormTags());
1288
+	}
1289
+
1290
+
1291
+	/**
1292
+	 * This just sets up the post update messages when an update form is loaded
1293
+	 *
1294
+	 * @param array $messages the original messages array
1295
+	 * @return array           the new messages array
1296
+	 */
1297
+	public function post_update_messages(array $messages): array
1298
+	{
1299
+		global $post;
1300
+		$id       = $this->request->getRequestParam('post');
1301
+		$id       = empty($id) && is_object($post) ? $post->ID : null;
1302
+		$revision = $this->request->getRequestParam('revision', 0, 'int');
1303
+
1304
+		$messages[ $post->post_type ] = [
1305
+			0  => '', // Unused. Messages start at index 1.
1306
+			1  => sprintf(
1307
+				esc_html__('%1$s updated. %2$sView %1$s%3$s', 'event_espresso'),
1308
+				$this->_cpt_object->labels->singular_name,
1309
+				'<a href="' . esc_url(get_permalink($id)) . '">',
1310
+				'</a>'
1311
+			),
1312
+			2  => esc_html__('Custom field updated', 'event_espresso'),
1313
+			3  => esc_html__('Custom field deleted.', 'event_espresso'),
1314
+			4  => sprintf(esc_html__('%1$s updated.', 'event_espresso'), $this->_cpt_object->labels->singular_name),
1315
+			5  => $revision
1316
+				? sprintf(
1317
+					esc_html__('%s restored to revision from %s', 'event_espresso'),
1318
+					$this->_cpt_object->labels->singular_name,
1319
+					wp_post_revision_title($revision, false)
1320
+				)
1321
+				: false,
1322
+			6  => sprintf(
1323
+				esc_html__('%1$s published. %2$sView %1$s%3$s', 'event_espresso'),
1324
+				$this->_cpt_object->labels->singular_name,
1325
+				'<a href="' . esc_url(get_permalink($id)) . '">',
1326
+				'</a>'
1327
+			),
1328
+			7  => sprintf(esc_html__('%1$s saved.', 'event_espresso'), $this->_cpt_object->labels->singular_name),
1329
+			8  => sprintf(
1330
+				esc_html__('%1$s submitted. %2$sPreview %1$s%3$s', 'event_espresso'),
1331
+				$this->_cpt_object->labels->singular_name,
1332
+				'<a target="_blank" href="' . esc_url(add_query_arg('preview', 'true', get_permalink($id))) . '">',
1333
+				'</a>'
1334
+			),
1335
+			9  => sprintf(
1336
+				esc_html__('%1$s scheduled for: %2$s. %3$s">Preview %1$s%3$s', 'event_espresso'),
1337
+				$this->_cpt_object->labels->singular_name,
1338
+				'<strong>' . date_i18n('M j, Y @ G:i', strtotime($post->post_date)) . '</strong>',
1339
+				'<a target="_blank" href="' . esc_url(get_permalink($id)),
1340
+				'</a>'
1341
+			),
1342
+			10 => sprintf(
1343
+				esc_html__('%1$s draft updated. %2$s">Preview page%3$s', 'event_espresso'),
1344
+				$this->_cpt_object->labels->singular_name,
1345
+				'<a target="_blank" href="' . esc_url(add_query_arg('preview', 'true', get_permalink($id))),
1346
+				'</a>'
1347
+			),
1348
+		];
1349
+		return $messages;
1350
+	}
1351
+
1352
+
1353
+	/**
1354
+	 * default method for the 'create_new' route for cpt admin pages.
1355
+	 * For reference what to include in here, see wp-admin/post-new.php
1356
+	 *
1357
+	 * @return void
1358
+	 */
1359
+	protected function _create_new_cpt_item()
1360
+	{
1361
+		// gather template vars for WP_ADMIN_PATH . 'edit-form-advanced.php'
1362
+		global $post, $title, $post_type, $post_type_object;
1363
+		$post_type        = $this->_cpt_routes[ $this->_req_action ];
1364
+		$post_type_object = $this->_cpt_object;
1365
+		$title            = $post_type_object->labels->add_new_item;
1366
+		$post             = $post = get_default_post_to_edit($this->_cpt_routes[ $this->_req_action ], true);
1367
+		add_action('admin_print_styles', [$this, 'add_new_admin_page_global']);
1368
+		// modify the default editor title field with default title.
1369
+		add_filter('enter_title_here', [$this, 'add_custom_editor_default_title']);
1370
+		$this->loadEditorTemplate();
1371
+	}
1372
+
1373
+
1374
+	/**
1375
+	 * Enqueues auto-save and loads the editor template
1376
+	 *
1377
+	 * @param bool $creating
1378
+	 */
1379
+	private function loadEditorTemplate(bool $creating = true)
1380
+	{
1381
+		if ($this->admin_config && ! $this->admin_config->useAdvancedEditor()) {
1382
+			add_filter('admin_body_class', function($classes)
1383
+			{
1384
+				$classes .= ' espresso-legacy-editor';
1385
+				return $classes;
1386
+			});
1387
+		}
1388
+
1389
+		global $post, $title, $is_IE, $post_type, $post_type_object;
1390
+		// these vars are used by the template
1391
+		$editing = true;
1392
+		$post_ID = $post->ID;
1393
+		if (apply_filters('FHEE__EE_Admin_Page_CPT___create_new_cpt_item__replace_editor', false, $post) === false) {
1394
+			// only enqueue autosave when creating event (necessary to get permalink/url generated)
1395
+			// otherwise EE doesn't support autosave fully, so to prevent user confusion we disable it in edit context.
1396
+			$action = $this->request->getRequestParam('action');
1397
+			if ($creating) {
1398
+				wp_enqueue_script('autosave');
1399
+			} elseif (
1400
+				isset($this->_cpt_routes[ $action ])
1401
+				&& ! isset($this->_labels['hide_add_button_on_cpt_route'][ $action ])
1402
+			) {
1403
+				$create_new_action = apply_filters(
1404
+					'FHEE__EE_Admin_Page_CPT___edit_cpt_item__create_new_action',
1405
+					'create_new',
1406
+					$this
1407
+				);
1408
+				$post_new_file     = EE_Admin_Page::add_query_args_and_nonce(
1409
+					[
1410
+						'action' => $create_new_action,
1411
+						'page'   => $this->page_slug,
1412
+					],
1413
+					'admin.php'
1414
+				);
1415
+			}
1416
+			include_once WP_ADMIN_PATH . 'edit-form-advanced.php';
1417
+		}
1418
+	}
1419
+
1420
+
1421
+	public function add_new_admin_page_global()
1422
+	{
1423
+		$admin_page = $this->request->getRequestParam('post', 0, DataType::INT) !== 0
1424
+			? 'post-php'
1425
+			: 'post-new-php';
1426
+		?>
1427 1427
         <script type="text/javascript">
1428 1428
             adminpage = '<?php echo esc_js($admin_page); ?>';
1429 1429
         </script>
1430 1430
         <?php
1431
-    }
1432
-
1433
-
1434
-    /**
1435
-     * default method for the 'edit' route for cpt admin pages
1436
-     * For reference on what to put in here, refer to wp-admin/post.php
1437
-     *
1438
-     * @return void
1439
-     */
1440
-    protected function _edit_cpt_item()
1441
-    {
1442
-        global $post, $post_type, $post_type_object, $title;
1443
-        $post_id = $this->request->getRequestParam('post', 0, DataType::INT);
1444
-        $post    = $post_id ? get_post($post_id, OBJECT, 'edit') : null;
1445
-        if (empty($post)) {
1446
-            wp_die(
1447
-                esc_html__(
1448
-                    "You attempted to edit an item that doesn't exist. Perhaps it was deleted?",
1449
-                    'event_espresso'
1450
-                )
1451
-            );
1452
-        }
1453
-
1454
-        $post_lock = $this->request->getRequestParam('get-post-lock');
1455
-        if ($post_lock) {
1456
-            wp_set_post_lock($post_id);
1457
-            EEH_URL::safeRedirectAndExit(get_edit_post_link($post_id, 'url'));
1458
-        }
1459
-
1460
-        // template vars for WP_ADMIN_PATH . 'edit-form-advanced.php'
1461
-        $post_type        = $this->_cpt_routes[ $this->_req_action ];
1462
-        $post_type_object = $this->_cpt_object;
1463
-        $title = $this->_labels['editor_title'][ $this->_cpt_routes[ $this->_req_action ] ]
1464
-                 ?? $post_type_object->labels->edit_item;
1465
-
1466
-        if (! wp_check_post_lock($post->ID)) {
1467
-            wp_set_post_lock($post->ID);
1468
-        }
1469
-        add_action('admin_footer', '_admin_notice_post_locked');
1470
-        if (post_type_supports($this->_cpt_routes[ $this->_req_action ], 'comments')) {
1471
-            wp_enqueue_script('admin-comments');
1472
-            enqueue_comment_hotkeys_js();
1473
-        }
1474
-        add_action('admin_print_styles', [$this, 'add_new_admin_page_global']);
1475
-        // modify the default editor title field with default title.
1476
-        add_filter('enter_title_here', [$this, 'add_custom_editor_default_title']);
1477
-        $this->loadEditorTemplate(false);
1478
-    }
1479
-
1480
-
1481
-
1482
-    /**
1483
-     * some getters
1484
-     */
1485
-    /**
1486
-     * This returns the protected _cpt_model_obj property
1487
-     *
1488
-     * @return EE_CPT_Base|null
1489
-     */
1490
-    public function get_cpt_model_obj(): ?EE_CPT_Base
1491
-    {
1492
-        return $this->_cpt_model_obj;
1493
-    }
1431
+	}
1432
+
1433
+
1434
+	/**
1435
+	 * default method for the 'edit' route for cpt admin pages
1436
+	 * For reference on what to put in here, refer to wp-admin/post.php
1437
+	 *
1438
+	 * @return void
1439
+	 */
1440
+	protected function _edit_cpt_item()
1441
+	{
1442
+		global $post, $post_type, $post_type_object, $title;
1443
+		$post_id = $this->request->getRequestParam('post', 0, DataType::INT);
1444
+		$post    = $post_id ? get_post($post_id, OBJECT, 'edit') : null;
1445
+		if (empty($post)) {
1446
+			wp_die(
1447
+				esc_html__(
1448
+					"You attempted to edit an item that doesn't exist. Perhaps it was deleted?",
1449
+					'event_espresso'
1450
+				)
1451
+			);
1452
+		}
1453
+
1454
+		$post_lock = $this->request->getRequestParam('get-post-lock');
1455
+		if ($post_lock) {
1456
+			wp_set_post_lock($post_id);
1457
+			EEH_URL::safeRedirectAndExit(get_edit_post_link($post_id, 'url'));
1458
+		}
1459
+
1460
+		// template vars for WP_ADMIN_PATH . 'edit-form-advanced.php'
1461
+		$post_type        = $this->_cpt_routes[ $this->_req_action ];
1462
+		$post_type_object = $this->_cpt_object;
1463
+		$title = $this->_labels['editor_title'][ $this->_cpt_routes[ $this->_req_action ] ]
1464
+				 ?? $post_type_object->labels->edit_item;
1465
+
1466
+		if (! wp_check_post_lock($post->ID)) {
1467
+			wp_set_post_lock($post->ID);
1468
+		}
1469
+		add_action('admin_footer', '_admin_notice_post_locked');
1470
+		if (post_type_supports($this->_cpt_routes[ $this->_req_action ], 'comments')) {
1471
+			wp_enqueue_script('admin-comments');
1472
+			enqueue_comment_hotkeys_js();
1473
+		}
1474
+		add_action('admin_print_styles', [$this, 'add_new_admin_page_global']);
1475
+		// modify the default editor title field with default title.
1476
+		add_filter('enter_title_here', [$this, 'add_custom_editor_default_title']);
1477
+		$this->loadEditorTemplate(false);
1478
+	}
1479
+
1480
+
1481
+
1482
+	/**
1483
+	 * some getters
1484
+	 */
1485
+	/**
1486
+	 * This returns the protected _cpt_model_obj property
1487
+	 *
1488
+	 * @return EE_CPT_Base|null
1489
+	 */
1490
+	public function get_cpt_model_obj(): ?EE_CPT_Base
1491
+	{
1492
+		return $this->_cpt_model_obj;
1493
+	}
1494 1494
 }
Please login to merge, or discard this patch.
core/admin/EE_Admin_Page.core.php 2 patches
Indentation   +4217 added lines, -4217 removed lines patch added patch discarded remove patch
@@ -24,4309 +24,4309 @@
 block discarded – undo
24 24
  */
25 25
 abstract class EE_Admin_Page extends EE_Base implements InterminableInterface
26 26
 {
27
-    protected ?EE_Admin_Config     $admin_config       = null;
27
+	protected ?EE_Admin_Config     $admin_config       = null;
28 28
 
29
-    protected ?EE_Admin_Hooks      $_hook_obj          = null;
29
+	protected ?EE_Admin_Hooks      $_hook_obj          = null;
30 30
 
31
-    protected ?EE_Admin_List_Table $_list_table_object = null;
31
+	protected ?EE_Admin_List_Table $_list_table_object = null;
32 32
 
33
-    protected ?EE_Capabilities     $capabilities       = null;
33
+	protected ?EE_Capabilities     $capabilities       = null;
34 34
 
35
-    protected ?EE_Registry         $EE                 = null;
35
+	protected ?EE_Registry         $EE                 = null;
36 36
 
37
-    protected ?FeatureFlags        $feature            = null;
37
+	protected ?FeatureFlags        $feature            = null;
38 38
 
39
-    protected ?LoaderInterface     $loader             = null;
39
+	protected ?LoaderInterface     $loader             = null;
40 40
 
41
-    protected ?RequestInterface    $request            = null;
41
+	protected ?RequestInterface    $request            = null;
42 42
 
43
-    protected ?WP_Screen           $_current_screen    = null;
43
+	protected ?WP_Screen           $_current_screen    = null;
44 44
 
45
-    /**
46
-     * @var array
47
-     * @since 5.0.0.p
48
-     */
49
-    private array $publish_post_meta_box_hidden_fields = [];
50
-
51
-    /**
52
-     * some default things shared by all child classes
53
-     *
54
-     * @var string[]
55
-     */
56
-    protected array $_default_espresso_metaboxes = [
57
-        '_espresso_news_post_box',
58
-        '_espresso_links_post_box',
59
-        '_espresso_ratings_request',
60
-        '_espresso_sponsors_post_box',
61
-    ];
62
-
63
-    /**
64
-     * Used to hold default query args for list table routes to help preserve stickiness of filters for carried out
65
-     * actions.
66
-     *
67
-     * @since 4.6.x
68
-     */
69
-    protected array $_default_route_query_args = [];
70
-
71
-    protected array $_labels                   = [];
72
-
73
-    protected array $_nav_tabs                 = [];
74
-
75
-    protected array $_page_config              = [];
76
-
77
-    /**
78
-     * action => method pairs used for routing incoming requests
79
-     *
80
-     * @var array
81
-     */
82
-    protected array $_page_routes   = [];
83
-
84
-    protected array $_req_data      = [];
85
-
86
-    protected array $_route_config  = [];
87
-
88
-    protected array $_template_args = [];
89
-
90
-    protected array $_views         = [];
91
-
92
-    /**
93
-     * yes / no array for admin form fields
94
-     *
95
-     * @var array|array[]
96
-     */
97
-    protected array $_yes_no_values = [];
98
-
99
-    /**
100
-     * this starts at null so we can have no header routes progress through two states.
101
-     */
102
-    protected ?bool $_is_UI_request = null;
103
-
104
-    protected bool  $_is_caf        = false;                                                                                                                                                                                                                                  // This is just a property that flags whether the given route is a caffeinated route or not.
105
-
106
-    protected bool  $_routing       = false;
107
-
108
-    /**
109
-     * whether initializePage() has run
110
-     *
111
-     * @var bool
112
-     */
113
-    protected bool $initialized = false;
114
-
115
-
116
-    protected string $_admin_base_path      = '';
117
-
118
-    protected string $_admin_base_url       = '';
119
-
120
-    protected string $_admin_page_title     = '';
121
-
122
-    protected string $_column_template_path = '';
123
-
124
-    protected bool   $_cpt_route            = false;
125
-
126
-    /**
127
-     * set via request page and action args.
128
-     */
129
-    protected string $_current_page          = '';
130
-
131
-    protected string $_current_page_view_url = '';
132
-
133
-    protected string $_current_view          = '';
134
-
135
-    protected string $_default_nav_tab_name  = 'overview';
136
-
137
-    /**
138
-     * sanitized request action
139
-     */
140
-    protected string $_req_action = '';
141
-
142
-    /**
143
-     * sanitized request action nonce
144
-     */
145
-    protected string $_req_nonce        = '';
146
-
147
-    protected string $_search_btn_label = '';
148
-
149
-    protected string $_template_path    = '';
150
-
151
-    protected string $_view             = '';
152
-
153
-    /**
154
-     * set early within EE_Admin_Init
155
-     *
156
-     * @var string
157
-     */
158
-    protected string $_wp_page_slug = '';
159
-
160
-    /**
161
-     * if the current class is an admin page extension, like: Extend_Events_Admin_Page,
162
-     * then this would be the parent classname: Events_Admin_Page
163
-     *
164
-     * @var string
165
-     */
166
-    public string $base_class_name = '';
167
-
168
-    public string $class_name      = '';
169
-
170
-    /**
171
-     * unprocessed value for the 'action' request param (default '')
172
-     *
173
-     * @var string
174
-     */
175
-    protected string $raw_req_action = '';
176
-
177
-    /**
178
-     * unprocessed value for the 'page' request param (default '')
179
-     *
180
-     * @var string
181
-     */
182
-    protected string $raw_req_page = '';
183
-
184
-    public string    $page_folder  = '';
185
-
186
-    public string    $page_label   = '';
187
-
188
-    public string    $page_slug    = '';
189
-
190
-
191
-    /**
192
-     * the current page route and route config
193
-     *
194
-     * @var array|callable|string|null
195
-     */
196
-    protected $_route = null;
197
-
198
-
199
-    /**
200
-     * @Constructor
201
-     * @param bool $routing indicate whether we want to just load the object and handle routing or just load the object.
202
-     * @throws InvalidArgumentException
203
-     * @throws InvalidDataTypeException
204
-     * @throws InvalidInterfaceException
205
-     * @throws ReflectionException
206
-     */
207
-    public function __construct($routing = true)
208
-    {
209
-        $this->loader       = LoaderFactory::getLoader();
210
-        $this->admin_config = $this->loader->getShared(EE_Admin_Config::class);
211
-        $this->feature      = $this->loader->getShared(FeatureFlags::class);
212
-        $this->request      = $this->loader->getShared(RequestInterface::class);
213
-        $this->capabilities = $this->loader->getShared(EE_Capabilities::class);
214
-        // routing enabled?
215
-        $this->_routing = $routing;
216
-
217
-        $this->class_name      = get_class($this);
218
-        $this->base_class_name = strpos($this->class_name, 'Extend_') === 0
219
-            ? str_replace('Extend_', '', $this->class_name)
220
-            : '';
221
-
222
-        if (strpos($this->_get_dir(), 'caffeinated') !== false) {
223
-            $this->_is_caf = true;
224
-        }
225
-        $this->_yes_no_values = [
226
-            ['id' => true, 'text' => esc_html__('Yes', 'event_espresso')],
227
-            ['id' => false, 'text' => esc_html__('No', 'event_espresso')],
228
-        ];
229
-        // set the _req_data property.
230
-        $this->_req_data = $this->request->requestParams();
231
-    }
232
-
233
-
234
-    /**
235
-     * @return EE_Admin_Config
236
-     */
237
-    public function adminConfig(): EE_Admin_Config
238
-    {
239
-        return $this->admin_config;
240
-    }
241
-
242
-
243
-    /**
244
-     * @return FeatureFlags
245
-     */
246
-    public function feature(): FeatureFlags
247
-    {
248
-        return $this->feature;
249
-    }
250
-
251
-
252
-    /**
253
-     * This logic used to be in the constructor, but that caused a chicken <--> egg scenario
254
-     * for child classes that needed to set properties prior to these methods getting called,
255
-     * but also needed the parent class to have its construction completed as well.
256
-     * Bottom line is that constructors should ONLY be used for setting initial properties
257
-     * and any complex initialization logic should only run after instantiation is complete.
258
-     * This method gets called immediately after construction from within
259
-     *      EE_Admin_Page_Init::_initialize_admin_page()
260
-     *
261
-     * @throws EE_Error
262
-     * @throws InvalidArgumentException
263
-     * @throws InvalidDataTypeException
264
-     * @throws InvalidInterfaceException
265
-     * @throws ReflectionException
266
-     * @throws Throwable
267
-     * @since 5.0.0.p
268
-     */
269
-    public function initializePage()
270
-    {
271
-        if ($this->initialized) {
272
-            return;
273
-        }
274
-        // set initial page props (child method)
275
-        $this->_init_page_props();
276
-        // set global defaults
277
-        $this->_set_defaults();
278
-        // set early because incoming requests could be ajax related and we need to register those hooks.
279
-        $this->_global_ajax_hooks();
280
-        $this->_ajax_hooks();
281
-        // other_page_hooks have to be early too.
282
-        $this->_do_other_page_hooks();
283
-        // set up page dependencies
284
-        $this->_before_page_setup();
285
-        $this->_page_setup();
286
-        $this->initialized = true;
287
-    }
288
-
289
-
290
-    /**
291
-     * _init_page_props
292
-     * Child classes use to set at least the following properties:
293
-     * $page_slug.
294
-     * $page_label.
295
-     *
296
-     * @abstract
297
-     * @return void
298
-     */
299
-    abstract protected function _init_page_props();
300
-
301
-
302
-    /**
303
-     * _ajax_hooks
304
-     * child classes put all their add_action('wp_ajax_{name_of_hook}') hooks in here.
305
-     * Note: within the ajax callback methods.
306
-     *
307
-     * @abstract
308
-     * @return void
309
-     */
310
-    abstract protected function _ajax_hooks();
311
-
312
-
313
-    /**
314
-     * _define_page_props
315
-     * child classes define page properties in here.  Must include at least:
316
-     * $_admin_base_url = base_url for all admin pages
317
-     * $_admin_page_title = default admin_page_title for admin pages
318
-     * $_labels = array of default labels for various automatically generated elements:
319
-     *    array(
320
-     *        'buttons' => array(
321
-     *            'add' => esc_html__('label for add new button'),
322
-     *            'edit' => esc_html__('label for edit button'),
323
-     *            'delete' => esc_html__('label for delete button')
324
-     *            )
325
-     *        )
326
-     *
327
-     * @abstract
328
-     * @return void
329
-     */
330
-    abstract protected function _define_page_props();
331
-
332
-
333
-    /**
334
-     * _set_page_routes
335
-     * child classes use this to define the page routes for all subpages handled by the class.  Page routes are
336
-     * assigned to an action => method pairs in an array and to the $_page_routes property.  Each page route must also
337
-     * have a 'default' route. Here's the format
338
-     * $this->_page_routes = array(
339
-     *        'default' => array(
340
-     *            'func' => '_default_method_handling_route',
341
-     *            'args' => array('array','of','args'),
342
-     *            'noheader' => true, //add this in if this page route is processed before any headers are loaded (i.e.
343
-     *            ajax request, backend processing)
344
-     *            'headers_sent_route'=>'headers_route_reference', //add this if noheader=>true, and you want to load a
345
-     *            headers route after.  The string you enter here should match the defined route reference for a
346
-     *            headers sent route.
347
-     *            'capability' => 'route_capability', //indicate a string for minimum capability required to access
348
-     *            this route.
349
-     *            'obj_id' => 10 // if this route has an object id, then this can include it (used for capability
350
-     *            checks).
351
-     *        ),
352
-     *        'insert_item' => '_method_for_handling_insert_item' //this can be used if all we need to have is a
353
-     *        handling method.
354
-     *        )
355
-     * )
356
-     *
357
-     * @abstract
358
-     * @return void
359
-     */
360
-    abstract protected function _set_page_routes();
361
-
362
-
363
-    /**
364
-     * _set_page_config
365
-     * child classes use this to define the _page_config array for all subpages handled by the class. Each key in the
366
-     * array corresponds to the page_route for the loaded page. Format:
367
-     * $this->_page_config = array(
368
-     *        'default' => array(
369
-     *            'labels' => array(
370
-     *                'buttons' => array(
371
-     *                    'add' => esc_html__('label for adding item'),
372
-     *                    'edit' => esc_html__('label for editing item'),
373
-     *                    'delete' => esc_html__('label for deleting item')
374
-     *                ),
375
-     *                'publishbox' => esc_html__('Localized Title for Publish metabox', 'event_espresso')
376
-     *            ), //optional an array of custom labels for various automatically generated elements to use on the
377
-     *            page. If this isn't present then the defaults will be used as set for the $this->_labels in
378
-     *            _define_page_props() method
379
-     *            'nav' => array(
380
-     *                'label' => esc_html__('Label for Tab', 'event_espresso').
381
-     *                'url' => 'http://someurl', //automatically generated UNLESS you define
382
-     *                'css_class' => 'css-class', //automatically generated UNLESS you define
383
-     *                'order' => 10, //required to indicate tab position.
384
-     *                'persistent' => false //if you want the nav tab to ONLY display when the specific route is
385
-     *                displayed then add this parameter.
386
-     *            'list_table' => 'name_of_list_table' //string for list table class to be loaded for this admin_page.
387
-     *            'metaboxes' => array('metabox1', 'metabox2'), //if present this key indicates we want to load
388
-     *            metaboxes set for eventespresso admin pages.
389
-     *            'has_metaboxes' => true, //this boolean flag can simply be used to indicate if the route will have
390
-     *            metaboxes.  Typically this is used if the 'metaboxes' index is not used because metaboxes are added
391
-     *            later.  We just use this flag to make sure the necessary js gets enqueued on page load.
392
-     *            'has_help_popups' => false //defaults(true) //this boolean flag can simply be used to indicate if the
393
-     *            given route has help popups setup and if it does then we need to make sure thickbox is enqueued.
394
-     *            'columns' => array(4, 2), //this key triggers the setup of a page that uses columns (metaboxes).  The
395
-     *            array indicates the max number of columns (4) and the default number of columns on page load (2).
396
-     *            There is an option in the "screen_options" dropdown that is set up so users can pick what columns they
397
-     *            want to display.
398
-     *            'help_tabs' => array( //this is used for adding help tabs to a page
399
-     *                'tab_id' => array(
400
-     *                    'title' => 'tab_title',
401
-     *                    'filename' => 'name_of_file_containing_content', //this is the primary method for setting
402
-     *                    help tab content.  The fallback if it isn't present is to try the callback.  Filename
403
-     *                    should match a file in the admin folder's "help_tabs" dir (ie..
404
-     *                    events/help_tabs/name_of_file_containing_content.help_tab.php)
405
-     *                    'callback' => 'callback_method_for_content', //if 'filename' isn't present then system will
406
-     *                    attempt to use the callback which should match the name of a method in the class
407
-     *                    ),
408
-     *                'tab2_id' => array(
409
-     *                    'title' => 'tab2 title',
410
-     *                    'filename' => 'file_name_2'
411
-     *                    'callback' => 'callback_method_for_content',
412
-     *                 ),
413
-     *            'help_sidebar' => 'callback_for_sidebar_content', //this is used for setting up the sidebar in the
414
-     *            help tab area on an admin page. @return void
415
-     *
416
-     * @abstract
417
-     */
418
-    abstract protected function _set_page_config();
45
+	/**
46
+	 * @var array
47
+	 * @since 5.0.0.p
48
+	 */
49
+	private array $publish_post_meta_box_hidden_fields = [];
419 50
 
51
+	/**
52
+	 * some default things shared by all child classes
53
+	 *
54
+	 * @var string[]
55
+	 */
56
+	protected array $_default_espresso_metaboxes = [
57
+		'_espresso_news_post_box',
58
+		'_espresso_links_post_box',
59
+		'_espresso_ratings_request',
60
+		'_espresso_sponsors_post_box',
61
+	];
420 62
 
421
-    /**
422
-     * _add_screen_options
423
-     * Child classes can add any extra wp_screen_options within this method using built-in WP functions/methods for
424
-     * doing so. Note child classes can also define _add_screen_options_($this->_current_view) to limit screen options
425
-     * to a particular view.
426
-     *
427
-     * @link   http://chrismarslender.com/wp-tutorials/wordpress-screen-options-tutorial/
428
-     *         see also WP_Screen object documents...
429
-     * @link   http://codex.wordpress.org/Class_Reference/WP_Screen
430
-     * @abstract
431
-     * @return void
432
-     */
433
-    abstract protected function _add_screen_options();
63
+	/**
64
+	 * Used to hold default query args for list table routes to help preserve stickiness of filters for carried out
65
+	 * actions.
66
+	 *
67
+	 * @since 4.6.x
68
+	 */
69
+	protected array $_default_route_query_args = [];
434 70
 
71
+	protected array $_labels                   = [];
435 72
 
436
-    /**
437
-     * _add_feature_pointers
438
-     * Child classes should use this method for implementing any "feature pointers" (using built-in WP styling js).
439
-     * Note child classes can also define _add_feature_pointers_($this->_current_view) to limit screen options to a
440
-     * particular view. Note: this is just a placeholder for now.  Implementation will come down the road See:
441
-     * WP_Internal_Pointers class in wp-admin/includes/template.php for example (it's a final class so can't be
442
-     * extended) also see:
443
-     *
444
-     * @link   http://eamann.com/tech/wordpress-portland/
445
-     * @abstract
446
-     * @return void
447
-     */
448
-    abstract protected function _add_feature_pointers();
73
+	protected array $_nav_tabs                 = [];
449 74
 
75
+	protected array $_page_config              = [];
450 76
 
451
-    /**
452
-     * load_scripts_styles
453
-     * child classes put their wp_enqueue_script and wp_enqueue_style hooks in here for anything they need loaded for
454
-     * their pages/subpages.  Note this is for all pages/subpages of the system.  You can also load only specific
455
-     * scripts/styles per view by putting them in a dynamic function in this format
456
-     * (load_scripts_styles_{$this->_current_view}) which matches your page route (action request arg)
457
-     *
458
-     * @abstract
459
-     * @return void
460
-     */
461
-    abstract public function load_scripts_styles();
77
+	/**
78
+	 * action => method pairs used for routing incoming requests
79
+	 *
80
+	 * @var array
81
+	 */
82
+	protected array $_page_routes   = [];
462 83
 
84
+	protected array $_req_data      = [];
463 85
 
464
-    /**
465
-     * admin_init
466
-     * Anything that should be set/executed at 'admin_init' WP hook runtime should be put in here.  This will apply to
467
-     * all pages/views loaded by child class.
468
-     *
469
-     * @abstract
470
-     * @return void
471
-     */
472
-    abstract public function admin_init();
473
-
86
+	protected array $_route_config  = [];
474 87
 
475
-    /**
476
-     * admin_notices
477
-     * Anything triggered by the 'admin_notices' WP hook should be put in here.  This particular method will apply to
478
-     * all pages/views loaded by child class.
479
-     *
480
-     * @abstract
481
-     * @return void
482
-     */
483
-    abstract public function admin_notices();
88
+	protected array $_template_args = [];
484 89
 
90
+	protected array $_views         = [];
485 91
 
486
-    /**
487
-     * admin_footer_scripts
488
-     * Anything triggered by the 'admin_print_footer_scripts' WP hook should be put in here. This particular method
489
-     * will apply to all pages/views loaded by child class.
490
-     *
491
-     * @return void
492
-     */
493
-    abstract public function admin_footer_scripts();
92
+	/**
93
+	 * yes / no array for admin form fields
94
+	 *
95
+	 * @var array|array[]
96
+	 */
97
+	protected array $_yes_no_values = [];
494 98
 
99
+	/**
100
+	 * this starts at null so we can have no header routes progress through two states.
101
+	 */
102
+	protected ?bool $_is_UI_request = null;
495 103
 
496
-    /**
497
-     * admin_footer
498
-     * anything triggered by the 'admin_footer' WP action hook should be added to here. This particular method will
499
-     * apply to all pages/views loaded by child class.
500
-     *
501
-     * @return void
502
-     */
503
-    public function admin_footer()
504
-    {
505
-    }
104
+	protected bool  $_is_caf        = false;                                                                                                                                                                                                                                  // This is just a property that flags whether the given route is a caffeinated route or not.
506 105
 
106
+	protected bool  $_routing       = false;
507 107
 
508
-    /**
509
-     * _global_ajax_hooks
510
-     * all global add_action('wp_ajax_{name_of_hook}') hooks in here.
511
-     * Note: within the ajax callback methods.
512
-     *
513
-     * @abstract
514
-     * @return void
515
-     */
516
-    protected function _global_ajax_hooks()
517
-    {
518
-        // for lazy loading of metabox content
519
-        add_action('wp_ajax_espresso-ajax-content', [$this, 'ajax_metabox_content']);
520
-
521
-        add_action(
522
-            'wp_ajax_espresso_hide_status_change_notice',
523
-            [$this, 'hideStatusChangeNotice']
524
-        );
525
-        add_action(
526
-            'wp_ajax_nopriv_espresso_hide_status_change_notice',
527
-            [$this, 'hideStatusChangeNotice']
528
-        );
529
-    }
530
-
531
-
532
-    public function ajax_metabox_content()
533
-    {
534
-        $content_id  = $this->request->getRequestParam('contentid', '');
535
-        $content_url = $this->request->getRequestParam('contenturl', '', DataType::URL);
536
-        EE_Admin_Page::cached_rss_display($content_id, $content_url);
537
-        wp_die();
538
-    }
539
-
540
-
541
-    public function hideStatusChangeNotice()
542
-    {
543
-        $response = [];
544
-        try {
545
-            /** @var StatusChangeNotice $status_change_notice */
546
-            $status_change_notice = $this->loader->getShared(
547
-                'EventEspresso\core\domain\services\admin\notices\status_change\StatusChangeNotice'
548
-            );
549
-            $response['success']  = $status_change_notice->dismiss() > -1;
550
-        } catch (Exception $exception) {
551
-            $response['errors'] = $exception->getMessage();
552
-        }
553
-        wp_send_json($response);
554
-    }
108
+	/**
109
+	 * whether initializePage() has run
110
+	 *
111
+	 * @var bool
112
+	 */
113
+	protected bool $initialized = false;
555 114
 
556 115
 
557
-    /**
558
-     * allows extending classes do something specific before the parent constructor runs _page_setup().
559
-     *
560
-     * @return void
561
-     */
562
-    protected function _before_page_setup()
563
-    {
564
-        // default is to do nothing
565
-    }
116
+	protected string $_admin_base_path      = '';
566 117
 
118
+	protected string $_admin_base_url       = '';
567 119
 
568
-    /**
569
-     * Makes sure any things that need to be loaded early get handled.
570
-     * We also escape early here if the page requested doesn't match the object.
571
-     *
572
-     * @final
573
-     * @return void
574
-     * @throws EE_Error
575
-     * @throws InvalidArgumentException
576
-     * @throws ReflectionException
577
-     * @throws InvalidDataTypeException
578
-     * @throws InvalidInterfaceException
579
-     * @throws Throwable
580
-     */
581
-    final protected function _page_setup()
582
-    {
583
-        // requires?
584
-        // admin_init stuff - global - we're setting this REALLY early
585
-        // so if EE_Admin pages have to hook into other WP pages they can.
586
-        // But keep in mind, not everything is available from the EE_Admin Page object at this point.
587
-        add_action('admin_init', [$this, 'admin_init_global'], 5);
588
-        // next verify if we need to load anything...
589
-        $this->_current_page = $this->request->getRequestParam('page', '', DataType::KEY);
590
-        $this->_current_page = $this->request->getRequestParam('current_page', $this->_current_page, DataType::KEY);
591
-        $this->page_folder   = strtolower(
592
-            str_replace(['_Admin_Page', 'Extend_'], '', $this->class_name)
593
-        );
594
-        global $ee_menu_slugs;
595
-        $ee_menu_slugs = (array) $ee_menu_slugs;
596
-        if (
597
-            ! $this->request->isAjax()
598
-            && (! $this->_current_page || ! isset($ee_menu_slugs[ $this->_current_page ]))
599
-        ) {
600
-            return;
601
-        }
602
-        // because WP List tables have two duplicate select inputs for choosing bulk actions,
603
-        // we need to copy the action from the second to the first
604
-        $action     = $this->request->getRequestParam('action', '-1', DataType::KEY);
605
-        $action2    = $this->request->getRequestParam('action2', '-1', DataType::KEY);
606
-        $action     = $action !== '-1' ? $action : $action2;
607
-        $req_action = $action !== '-1' ? $action : 'default';
608
-
609
-        // if a specific 'route' has been set, and the action is 'default' OR we are doing_ajax
610
-        // then let's use the route as the action.
611
-        // This covers cases where we're coming in from a list table that isn't on the default route.
612
-        $route             = $this->request->getRequestParam('route');
613
-        $this->_req_action = $route && ($req_action === 'default' || $this->request->isAjax())
614
-            ? $route
615
-            : $req_action;
616
-        $this->_current_view = $this->_req_action;
617
-        $this->_req_nonce    = $this->_req_action . '_nonce';
618
-        $this->_define_page_props();
619
-        $this->_current_page_view_url = add_query_arg(
620
-            ['page' => $this->_current_page, 'action' => $this->_current_view],
621
-            $this->_admin_base_url
622
-        );
623
-        // set page configs
624
-        $this->_set_page_routes();
625
-        $this->_set_page_config();
626
-        // let's include any referrer data in our default_query_args for this route for "stickiness".
627
-        if ($this->request->requestParamIsSet('wp_referer')) {
628
-            $wp_referer = $this->request->getRequestParam('wp_referer');
629
-            if ($wp_referer) {
630
-                $this->_default_route_query_args['wp_referer'] = $wp_referer;
631
-            }
632
-        }
633
-        // for CPT and other extended functionality.
634
-        // If there is an _extend_page_config_for_cpt
635
-        // then let's run that to modify all the various page configuration arrays.
636
-        if (method_exists($this, '_extend_page_config_for_cpt')) {
637
-            $this->_extend_page_config_for_cpt();
638
-        }
639
-        // filter routes and page_config so addons can add their stuff. Filtering done per class
640
-        $this->_page_routes = apply_filters(
641
-            'FHEE__' . $this->class_name . '__page_setup__page_routes',
642
-            $this->_page_routes,
643
-            $this
644
-        );
645
-        $this->_page_config = apply_filters(
646
-            'FHEE__' . $this->class_name . '__page_setup__page_config',
647
-            $this->_page_config,
648
-            $this
649
-        );
650
-        if ($this->base_class_name !== '') {
651
-            $this->_page_routes = apply_filters(
652
-                'FHEE__' . $this->base_class_name . '__page_setup__page_routes',
653
-                $this->_page_routes,
654
-                $this
655
-            );
656
-            $this->_page_config = apply_filters(
657
-                'FHEE__' . $this->base_class_name . '__page_setup__page_config',
658
-                $this->_page_config,
659
-                $this
660
-            );
661
-        }
662
-        // if AHEE__EE_Admin_Page__route_admin_request_$this->_current_view method is present
663
-        // then we call it hooked into the AHEE__EE_Admin_Page__route_admin_request action
664
-        if (method_exists($this, 'AHEE__EE_Admin_Page__route_admin_request_' . $this->_current_view)) {
665
-            add_action(
666
-                'AHEE__EE_Admin_Page__route_admin_request',
667
-                [$this, 'AHEE__EE_Admin_Page__route_admin_request_' . $this->_current_view],
668
-                10,
669
-                2
670
-            );
671
-        }
672
-        // next route only if routing enabled
673
-        if ($this->_routing && ! $this->request->isAjax()) {
674
-            $this->_verify_routes();
675
-            // next let's just check user_access and kill if no access
676
-            $this->check_user_access();
677
-            if ($this->_is_UI_request) {
678
-                // admin_init stuff - global, all views for this page class, specific view
679
-                add_action('admin_init', [$this, 'admin_init']);
680
-                if (method_exists($this, 'admin_init_' . $this->_current_view)) {
681
-                    add_action('admin_init', [$this, 'admin_init_' . $this->_current_view], 15);
682
-                }
683
-            } else {
684
-                // hijack regular WP loading and route admin request immediately
685
-                @ini_set('memory_limit', apply_filters('admin_memory_limit', WP_MAX_MEMORY_LIMIT));
686
-                $this->route_admin_request();
687
-            }
688
-        }
689
-    }
120
+	protected string $_admin_page_title     = '';
690 121
 
122
+	protected string $_column_template_path = '';
691 123
 
692
-    /**
693
-     * Provides a way for related child admin pages to load stuff on the loaded admin page.
694
-     *
695
-     * @return void
696
-     * @throws EE_Error
697
-     */
698
-    private function _do_other_page_hooks()
699
-    {
700
-        $registered_pages = apply_filters('FHEE_do_other_page_hooks_' . $this->page_slug, []);
701
-        foreach ($registered_pages as $page) {
702
-            // now let's set up the file name and class that should be present
703
-            $classname = str_replace('.class.php', '', $page);
704
-            // autoloaders should take care of loading file
705
-            if (! class_exists($classname)) {
706
-                $error_msg[] = sprintf(
707
-                    esc_html__(
708
-                        'Something went wrong with loading the %s admin hooks page.',
709
-                        'event_espresso'
710
-                    ),
711
-                    $page
712
-                );
713
-                $error_msg[] = $error_msg[0]
714
-                               . "\r\n"
715
-                               . sprintf(
716
-                                   esc_html__(
717
-                                       'There is no class in place for the %1$s admin hooks page.%2$sMake sure you have %3$s defined. If this is a non-EE-core admin page then you also must have an autoloader in place for your class',
718
-                                       'event_espresso'
719
-                                   ),
720
-                                   $page,
721
-                                   '<br />',
722
-                                   '<strong>' . $classname . '</strong>'
723
-                               );
724
-                throw new EE_Error(implode('||', $error_msg));
725
-            }
726
-            // don't load the same class twice
727
-            static $loaded = [];
728
-            if (in_array($classname, $loaded, true)) {
729
-                continue;
730
-            }
731
-            $loaded[] = $classname;
732
-            // notice we are passing the instance of this class to the hook object.
733
-            $this->loader->getShared($classname, [$this]);
734
-        }
735
-    }
124
+	protected bool   $_cpt_route            = false;
736 125
 
126
+	/**
127
+	 * set via request page and action args.
128
+	 */
129
+	protected string $_current_page          = '';
737 130
 
738
-    /**
739
-     * @throws ReflectionException
740
-     * @throws EE_Error
741
-     */
742
-    public function load_page_dependencies()
743
-    {
744
-        try {
745
-            $this->_load_page_dependencies();
746
-        } catch (EE_Error $e) {
747
-            $e->get_error();
748
-        }
749
-    }
131
+	protected string $_current_page_view_url = '';
750 132
 
133
+	protected string $_current_view          = '';
751 134
 
752
-    /**
753
-     * load_page_dependencies
754
-     * loads things specific to this page class when it's loaded.  Really helps with efficiency.
755
-     *
756
-     * @return void
757
-     * @throws DomainException
758
-     * @throws EE_Error
759
-     * @throws InvalidArgumentException
760
-     * @throws InvalidDataTypeException
761
-     * @throws InvalidInterfaceException
762
-     * @throws ReflectionException
763
-     */
764
-    protected function _load_page_dependencies()
765
-    {
766
-        // let's set the current_screen and screen options to override what WP set
767
-        $this->_current_screen = get_current_screen();
768
-        // load admin_notices - global, page class, and view specific
769
-        add_action('admin_notices', [$this, 'admin_notices_global'], 5);
770
-        add_action('admin_notices', [$this, 'admin_notices']);
771
-        if (method_exists($this, 'admin_notices_' . $this->_current_view)) {
772
-            add_action('admin_notices', [$this, 'admin_notices_' . $this->_current_view], 15);
773
-        }
774
-        // load network admin_notices - global, page class, and view specific
775
-        add_action('network_admin_notices', [$this, 'network_admin_notices_global'], 5);
776
-        if (method_exists($this, 'network_admin_notices_' . $this->_current_view)) {
777
-            add_action('network_admin_notices', [$this, 'network_admin_notices_' . $this->_current_view]);
778
-        }
779
-        // this will save any per_page screen options if they are present
780
-        $this->_set_per_page_screen_options();
781
-        // setup list table properties
782
-        $this->_set_list_table();
783
-        // child classes can "register" a metabox to be automatically handled via the _page_config array property.
784
-        // However in some cases the metaboxes will need to be added within a route handling callback.
785
-        add_action('add_meta_boxes', [$this, 'addRegisteredMetaBoxes'], 99);
786
-        // hack because promos admin was loading the edited promotion object in the metaboxes callback
787
-        // which should NOT be generated on non-UI requests like POST updates/inserts
788
-        if (
789
-            $this->class_name === 'Promotions_Admin_Page'
790
-            && ($this->_req_action === 'edit' || $this->_req_action === 'create_new')
791
-        ) {
792
-            $this->addRegisteredMetaBoxes();
793
-        }
794
-        $this->_add_screen_columns();
795
-        // add screen options - global, page child class, and view specific
796
-        $this->_add_global_screen_options();
797
-        $this->_add_screen_options();
798
-        $add_screen_options = "_add_screen_options_$this->_current_view";
799
-        if (method_exists($this, $add_screen_options)) {
800
-            $this->{$add_screen_options}();
801
-        }
802
-        // add help tab(s) - set via page_config and qtips.
803
-        $this->_add_help_tabs();
804
-        $this->_add_qtips();
805
-        // add feature_pointers - global, page child class, and view specific
806
-        $this->_add_feature_pointers();
807
-        $this->_add_global_feature_pointers();
808
-        $add_feature_pointer = "_add_feature_pointer_$this->_current_view";
809
-        if (method_exists($this, $add_feature_pointer)) {
810
-            $this->{$add_feature_pointer}();
811
-        }
812
-        // enqueue scripts/styles - global, page class, and view specific
813
-        add_action('admin_enqueue_scripts', [$this, 'load_global_scripts_styles'], 5);
814
-        add_action('admin_enqueue_scripts', [$this, 'load_scripts_styles']);
815
-        if (method_exists($this, "load_scripts_styles_$this->_current_view")) {
816
-            add_action('admin_enqueue_scripts', [$this, "load_scripts_styles_$this->_current_view"], 15);
817
-        }
818
-        add_action('admin_enqueue_scripts', [$this, 'admin_footer_scripts_eei18n_js_strings'], 100);
819
-        // admin_print_footer_scripts - global, page child class, and view specific.
820
-        // NOTE, despite the name, whenever possible, scripts should NOT be loaded using this.
821
-        // In most cases that's doing_it_wrong().  But adding hidden container elements etc.
822
-        // is a good use case. Notice the late priority we're giving these
823
-        add_action('admin_print_footer_scripts', [$this, 'admin_footer_scripts_global'], 99);
824
-        add_action('admin_print_footer_scripts', [$this, 'admin_footer_scripts'], 100);
825
-        if (method_exists($this, "admin_footer_scripts_$this->_current_view")) {
826
-            add_action('admin_print_footer_scripts', [$this, "admin_footer_scripts_$this->_current_view"], 101);
827
-        }
828
-        // admin footer scripts
829
-        add_action('admin_footer', [$this, 'admin_footer_global'], 99);
830
-        add_action('admin_footer', [$this, 'admin_footer'], 100);
831
-        if (method_exists($this, "admin_footer_$this->_current_view")) {
832
-            add_action('admin_footer', [$this, "admin_footer_$this->_current_view"], 101);
833
-        }
834
-        do_action('FHEE__EE_Admin_Page___load_page_dependencies__after_load', $this->page_slug);
835
-        // targeted hook
836
-        do_action(
837
-            "FHEE__EE_Admin_Page___load_page_dependencies__after_load__{$this->page_slug}__$this->_req_action"
838
-        );
839
-    }
840
-
841
-
842
-    /**
843
-     * _set_defaults
844
-     * This sets some global defaults for class properties.
845
-     */
846
-    private function _set_defaults()
847
-    {
848
-        // init template args
849
-        $this->set_template_args(
850
-            [
851
-                'admin_page_header'  => '',
852
-                'admin_page_content' => '',
853
-                'post_body_content'  => '',
854
-                'before_list_table'  => '',
855
-                'after_list_table'   => '',
856
-            ]
857
-        );
858
-    }
859
-
860
-
861
-    /**
862
-     * route_admin_request
863
-     *
864
-     * @return void
865
-     * @throws InvalidArgumentException
866
-     * @throws InvalidInterfaceException
867
-     * @throws InvalidDataTypeException
868
-     * @throws EE_Error
869
-     * @throws ReflectionException
870
-     * @throws Throwable
871
-     * @see    _route_admin_request()
872
-     */
873
-    public function route_admin_request()
874
-    {
875
-        try {
876
-            $this->_route_admin_request();
877
-        } catch (EE_Error $e) {
878
-            $e->get_error();
879
-        }
880
-    }
881
-
882
-
883
-    public function set_wp_page_slug($wp_page_slug)
884
-    {
885
-        $this->_wp_page_slug = $wp_page_slug;
886
-        // if in network admin then we need to append "-network" to the page slug. Why? Because that's how WP rolls...
887
-        if (is_network_admin()) {
888
-            $this->_wp_page_slug .= '-network';
889
-        }
890
-    }
891
-
892
-
893
-    /**
894
-     * _verify_routes
895
-     * All this method does is verify the incoming request and make sure that routes exist for it.  We do this early so
896
-     * we know if we need to drop out.
897
-     *
898
-     * @return bool
899
-     * @throws EE_Error
900
-     */
901
-    protected function _verify_routes(): bool
902
-    {
903
-        if (! $this->_current_page && ! $this->request->isAjax()) {
904
-            return false;
905
-        }
906
-        // check that the page_routes array is not empty
907
-        if (empty($this->_page_routes)) {
908
-            // user error msg
909
-            $error_msg = sprintf(
910
-                esc_html__('No page routes have been set for the %s admin page.', 'event_espresso'),
911
-                $this->_admin_page_title
912
-            );
913
-            // developer error msg
914
-            $error_msg .= '||' . $error_msg
915
-                          . esc_html__(
916
-                              ' Make sure the "set_page_routes()" method exists, and is setting the "_page_routes" array properly.',
917
-                              'event_espresso'
918
-                          );
919
-            throw new EE_Error($error_msg);
920
-        }
921
-        // route 'editpost' routes to CPT 'edit' routes
922
-        $alt_edit_route = $this instanceof EE_Admin_Page_CPT ? $this->cpt_editpost_route : 'edit';
923
-        if (
924
-            $this->_req_action === 'editpost'
925
-            && ! isset($this->_page_routes['editpost'])
926
-            && isset($this->_page_routes[$alt_edit_route])
927
-        ) {
928
-            $this->_req_action = $alt_edit_route;
929
-        }
930
-        // and that the requested page route exists
931
-        if (array_key_exists($this->_req_action, $this->_page_routes)) {
932
-            $this->_route        = $this->_page_routes[ $this->_req_action ];
933
-            $this->_route_config = $this->_page_config[ $this->_req_action ] ?? [];
934
-        } else {
935
-            // user error msg
936
-            $error_msg = sprintf(
937
-                esc_html__(
938
-                    'The requested page route does not exist for the %s admin page.',
939
-                    'event_espresso'
940
-                ),
941
-                $this->_admin_page_title
942
-            );
943
-            // developer error msg
944
-            $error_msg .= '||' . $error_msg
945
-                          . sprintf(
946
-                              esc_html__(
947
-                                  ' Create a key in the "_page_routes" array named "%s" and set its value to the appropriate method.',
948
-                                  'event_espresso'
949
-                              ),
950
-                              $this->_req_action
951
-                          );
952
-            throw new EE_Error($error_msg);
953
-        }
954
-        // and that a default route exists
955
-        if (! array_key_exists('default', $this->_page_routes)) {
956
-            // user error msg
957
-            $error_msg = sprintf(
958
-                esc_html__(
959
-                    'A default page route has not been set for the % admin page.',
960
-                    'event_espresso'
961
-                ),
962
-                $this->_admin_page_title
963
-            );
964
-            // developer error msg
965
-            $error_msg .= '||' . $error_msg
966
-                          . esc_html__(
967
-                              ' Create a key in the "_page_routes" array named "default" and set its value to your default page method.',
968
-                              'event_espresso'
969
-                          );
970
-            throw new EE_Error($error_msg);
971
-        }
972
-
973
-        // first lets' catch if the UI request has EVER been set.
974
-        if ($this->_is_UI_request === null) {
975
-            // let's set if this is a UI request or not.
976
-            $this->_is_UI_request = ! $this->request->getRequestParam('noheader', false, DataType::BOOL);
977
-            // wait a minute... we might have a noheader in the route array
978
-            $this->_is_UI_request = ! (isset($this->_route['noheader']) && $this->_route['noheader'])
979
-                ? $this->_is_UI_request
980
-                : false;
981
-        }
982
-        $this->_set_current_labels();
983
-        return true;
984
-    }
985
-
986
-
987
-    /**
988
-     * this method simply verifies a given route and makes sure it's an actual route available for the loaded page
989
-     *
990
-     * @param string $route the route name we're verifying
991
-     * @return bool we'll throw an exception if this isn't a valid route.
992
-     * @throws EE_Error
993
-     */
994
-    protected function _verify_route(string $route): bool
995
-    {
996
-        if (array_key_exists($this->_req_action, $this->_page_routes)) {
997
-            return true;
998
-        }
999
-        // user error msg
1000
-        $error_msg = sprintf(
1001
-            esc_html__('The given page route does not exist for the %s admin page.', 'event_espresso'),
1002
-            $this->_admin_page_title
1003
-        );
1004
-        // developer error msg
1005
-        $error_msg .= '||' . $error_msg
1006
-                      . sprintf(
1007
-                          esc_html__(
1008
-                              ' Check the route you are using in your method (%s) and make sure it matches a route set in your "_page_routes" array property',
1009
-                              'event_espresso'
1010
-                          ),
1011
-                          $route
1012
-                      );
1013
-        throw new EE_Error($error_msg);
1014
-    }
135
+	protected string $_default_nav_tab_name  = 'overview';
1015 136
 
1016
-
1017
-    /**
1018
-     * perform nonce verification
1019
-     * This method has be encapsulated here so that any ajax requests that bypass normal routes can verify their nonces
1020
-     * using this method (and save retyping!)
1021
-     *
1022
-     * @param string $nonce     The nonce sent
1023
-     * @param string $nonce_ref The nonce reference string (name0)
1024
-     * @return void
1025
-     * @throws EE_Error
1026
-     * @throws InvalidArgumentException
1027
-     * @throws InvalidDataTypeException
1028
-     * @throws InvalidInterfaceException
1029
-     */
1030
-    protected function _verify_nonce(string $nonce, string $nonce_ref)
1031
-    {
1032
-        // verify nonce against expected value
1033
-        if (! wp_verify_nonce($nonce, $nonce_ref)) {
1034
-            // these are not the droids you are looking for !!!
1035
-            $msg = sprintf(
1036
-                esc_html__('%sNonce Fail.%s', 'event_espresso'),
1037
-                '<a href="https://www.youtube.com/watch?v=56_S0WeTkzs">',
1038
-                '</a>'
1039
-            );
1040
-            if (WP_DEBUG) {
1041
-                $msg .= "\n  ";
1042
-                $msg .= sprintf(
1043
-                    esc_html__(
1044
-                        'In order to dynamically generate nonces for your actions, use the %s::add_query_args_and_nonce() method. May the Nonce be with you!',
1045
-                        'event_espresso'
1046
-                    ),
1047
-                    __CLASS__
1048
-                );
1049
-            }
1050
-            if (! $this->request->isAjax()) {
1051
-                wp_die($msg);
1052
-            }
1053
-            EE_Error::add_error($msg, __FILE__, __FUNCTION__, __LINE__);
1054
-            $this->_return_json();
1055
-        }
1056
-    }
1057
-
1058
-
1059
-    /**
1060
-     * _route_admin_request()
1061
-     * Meat and potatoes of the class.  Basically, this dude checks out what's being requested and sees if there are
1062
-     * some doodads to work the magic and handle the flingjangy. Translation:  Checks if the requested action is listed
1063
-     * in the page routes and then will try to load the corresponding method.
1064
-     *
1065
-     * @return void
1066
-     * @throws EE_Error
1067
-     * @throws InvalidArgumentException
1068
-     * @throws InvalidDataTypeException
1069
-     * @throws InvalidInterfaceException
1070
-     * @throws ReflectionException
1071
-     * @throws Throwable
1072
-     */
1073
-    protected function _route_admin_request()
1074
-    {
1075
-        if (! $this->_is_UI_request) {
1076
-            $this->_verify_routes();
1077
-        }
1078
-        $nonce_check = ! isset($this->_route_config['require_nonce']) || $this->_route_config['require_nonce'];
1079
-        if ($this->_req_action !== 'default' && $nonce_check) {
1080
-            // set nonce from post data
1081
-            $nonce = $this->request->getRequestParam($this->_req_nonce, '');
1082
-            $this->_verify_nonce($nonce, $this->_req_nonce);
1083
-        }
1084
-        // set the nav_tabs array but ONLY if this is  UI_request
1085
-        if ($this->_is_UI_request) {
1086
-            $this->_set_nav_tabs();
1087
-        }
1088
-        // grab callback function
1089
-        $func = $this->_route['func'] ?? $this->_route;
1090
-        // check if callback has args
1091
-        $args = $this->_route['args'] ?? [];
1092
-
1093
-        // action right before calling route
1094
-        // (hook is something like 'AHEE__Registrations_Admin_Page__route_admin_request')
1095
-        if (! did_action('AHEE__EE_Admin_Page__route_admin_request')) {
1096
-            do_action('AHEE__EE_Admin_Page__route_admin_request', $this->_current_view, $this);
1097
-        }
1098
-        // strip _wp_http_referer from the server REQUEST_URI
1099
-        // else it grows in length on every submission due to recursion,
1100
-        // ultimately causing a "Request-URI Too Large" error
1101
-        $this->request->unSetRequestParam('_wp_http_referer');
1102
-        $this->request->unSetServerParam('_wp_http_referer');
1103
-        $cleaner_request_uri = remove_query_arg(
1104
-            '_wp_http_referer',
1105
-            wp_unslash($this->request->getServerParam('REQUEST_URI'))
1106
-        );
1107
-        $this->request->setRequestParam('_wp_http_referer', $cleaner_request_uri, true);
1108
-        $this->request->setServerParam('REQUEST_URI', $cleaner_request_uri, true);
1109
-        $route_callback = [];
1110
-        if (! empty($func)) {
1111
-            if (is_array($func) && is_callable($func)) {
1112
-                $route_callback = $func;
1113
-            } elseif (is_string($func)) {
1114
-                if (strpos($func, '::') !== false) {
1115
-                    $route_callback = explode('::', $func);
1116
-                } elseif (method_exists($this, $func)) {
1117
-                    $route_callback = [$this, $func];
1118
-                } else {
1119
-                    $route_callback = $func;
1120
-                }
1121
-            }
1122
-            [$class, $method] = $route_callback;
1123
-            // is it neither a class method NOR a standalone function?
1124
-            if (! is_callable($route_callback)) {
1125
-                // user error msg
1126
-                $error_msg = esc_html__(
1127
-                    'An error occurred. The  requested page route could not be found.',
1128
-                    'event_espresso'
1129
-                );
1130
-                // developer error msg
1131
-                $error_msg .= '||';
1132
-                $error_msg .= sprintf(
1133
-                    esc_html__(
1134
-                        'Page route "%s" could not be called. Check that the spelling for method names and actions in the "_page_routes" array are all correct.',
1135
-                        'event_espresso'
1136
-                    ),
1137
-                    $method
1138
-                );
1139
-                throw new DomainException($error_msg);
1140
-            }
1141
-            if ($class !== $this && ! in_array($this, $args)) {
1142
-                // send along this admin page object for access by addons.
1143
-                $args['admin_page'] = $this;
1144
-            }
1145
-
1146
-            call_user_func_array($route_callback, $args);
1147
-        }
1148
-        // if we've routed and this route has a no headers route AND a sent_headers_route,
1149
-        // then we need to reset the routing properties to the new route.
1150
-        // now if UI request is FALSE and noheader is true AND we have a headers_sent_route in the route array then let's set UI_request to true because the no header route has a second func after headers have been sent.
1151
-        if (
1152
-            $this->_is_UI_request === false
1153
-            && is_array($this->_route)
1154
-            && ! empty($this->_route['headers_sent_route'])
1155
-        ) {
1156
-            $this->_reset_routing_properties($this->_route['headers_sent_route']);
1157
-        }
1158
-    }
1159
-
1160
-
1161
-    /**
1162
-     * This method just allows the resetting of page properties in the case where a no headers
1163
-     * route redirects to a headers route in its route config.
1164
-     *
1165
-     * @param string $new_route New (non header) route to redirect to.
1166
-     * @return void
1167
-     * @throws ReflectionException
1168
-     * @throws InvalidArgumentException
1169
-     * @throws InvalidInterfaceException
1170
-     * @throws InvalidDataTypeException
1171
-     * @throws EE_Error
1172
-     * @throws Throwable
1173
-     * @since   4.3.0
1174
-     */
1175
-    protected function _reset_routing_properties(string $new_route)
1176
-    {
1177
-        $this->_is_UI_request = true;
1178
-        // now we set the current route to whatever the headers_sent_route is set at
1179
-        $this->request->setRequestParam('action', $new_route);
1180
-        // rerun page setup
1181
-        $this->_page_setup();
1182
-    }
1183
-
1184
-
1185
-    /**
1186
-     * _add_query_arg
1187
-     * adds nonce to array of arguments then calls WP add_query_arg function
1188
-     *(internally just uses EEH_URL's function with the same name)
1189
-     *
1190
-     * @param array  $args
1191
-     * @param string $url
1192
-     * @param bool   $sticky                  if true, then the existing Request params will be appended to the
1193
-     *                                        generated url in an associative array indexed by the key 'wp_referer';
1194
-     *                                        Example usage: If the current page is:
1195
-     *                                        http://mydomain.com/wp-admin/admin.php?page=espresso_registrations
1196
-     *                                        &action=default&event_id=20&month_range=March%202015
1197
-     *                                        &_wpnonce=5467821
1198
-     *                                        and you call:
1199
-     *                                        EE_Admin_Page::add_query_args_and_nonce(
1200
-     *                                        array(
1201
-     *                                        'action' => 'resend_something',
1202
-     *                                        'page=>espresso_registrations'
1203
-     *                                        ),
1204
-     *                                        $some_url,
1205
-     *                                        true
1206
-     *                                        );
1207
-     *                                        It will produce a URL in this structure:
1208
-     *                                        http://{$some_url}/?page=espresso_registrations&action=resend_something
1209
-     *                                        &wp_referer[action]=default&wp_referer[event_id]=20&wpreferer[
1210
-     *                                        month_range]=March%202015
1211
-     * @param bool   $exclude_nonce           If true, the nonce will be excluded from the generated nonce.
1212
-     * @param int    $context
1213
-     * @return string
1214
-     */
1215
-    public static function add_query_args_and_nonce(
1216
-        array $args = [],
1217
-        string $url = '',
1218
-        bool $sticky = false,
1219
-        bool $exclude_nonce = false,
1220
-        int $context = EEH_URL::CONTEXT_NONE
1221
-    ): string {
1222
-        // if there is a _wp_http_referer include the values from the request but only if sticky = true
1223
-        if ($sticky) {
1224
-            /** @var RequestInterface $request */
1225
-            $request = LoaderFactory::getLoader()->getShared(RequestInterface::class);
1226
-            $request->unSetRequestParams(['_wp_http_referer', 'wp_referer'], true);
1227
-            $request->unSetServerParam('_wp_http_referer', true);
1228
-            foreach ($request->requestParams() as $key => $value) {
1229
-                // do not add nonces
1230
-                if (strpos($key, 'nonce') !== false) {
1231
-                    continue;
1232
-                }
1233
-                $args[ 'wp_referer[' . $key . ']' ] = is_string($value) ? htmlspecialchars($value) : $value;
1234
-            }
1235
-        }
1236
-        return EEH_URL::add_query_args_and_nonce($args, $url, $exclude_nonce, $context);
1237
-    }
1238
-
1239
-
1240
-    /**
1241
-     * This returns a generated link that will load the related help tab.
1242
-     *
1243
-     * @param string $help_tab_id the id for the connected help tab
1244
-     * @param string $icon_style  (optional) include css class for the style you want to use for the help icon.
1245
-     * @param string $help_text   (optional) send help text you want to use for the link if default not to be used
1246
-     * @return string              generated link
1247
-     * @uses EEH_Template::get_help_tab_link()
1248
-     */
1249
-    protected function _get_help_tab_link(string $help_tab_id, string $icon_style = '', string $help_text = ''): string
1250
-    {
1251
-        return EEH_Template::get_help_tab_link(
1252
-            $help_tab_id,
1253
-            $this->page_slug,
1254
-            $this->_req_action,
1255
-            $icon_style,
1256
-            $help_text
1257
-        );
1258
-    }
1259
-
1260
-
1261
-    /**
1262
-     * _add_help_tabs
1263
-     * Note child classes define their help tabs within the page_config array.
1264
-     *
1265
-     * @link   http://codex.wordpress.org/Function_Reference/add_help_tab
1266
-     * @return void
1267
-     * @throws DomainException
1268
-     * @throws EE_Error
1269
-     * @throws ReflectionException
1270
-     */
1271
-    protected function _add_help_tabs()
1272
-    {
1273
-        if (isset($this->_page_config[ $this->_req_action ])) {
1274
-            $config = $this->_page_config[ $this->_req_action ];
1275
-            // let's see if there is a help_sidebar set for the current route and we'll set that up for usage as well.
1276
-            if (is_array($config) && isset($config['help_sidebar'])) {
1277
-                // check that the callback given is valid
1278
-                if (! method_exists($this, $config['help_sidebar'])) {
1279
-                    throw new EE_Error(
1280
-                        sprintf(
1281
-                            esc_html__(
1282
-                                'The _page_config array has a callback set for the "help_sidebar" option.  However the callback given (%s) is not a valid callback.  Double check the spelling and make sure this method exists for the class %s',
1283
-                                'event_espresso'
1284
-                            ),
1285
-                            $config['help_sidebar'],
1286
-                            $this->class_name
1287
-                        )
1288
-                    );
1289
-                }
1290
-                $content = apply_filters(
1291
-                    'FHEE__' . $this->class_name . '__add_help_tabs__help_sidebar',
1292
-                    $this->{$config['help_sidebar']}()
1293
-                );
1294
-                $this->_current_screen->set_help_sidebar($content);
1295
-            }
1296
-            if (! isset($config['help_tabs'])) {
1297
-                return;
1298
-            } //no help tabs for this route
1299
-            foreach ((array) $config['help_tabs'] as $tab_id => $cfg) {
1300
-                // we're here so there ARE help tabs!
1301
-                // make sure we've got what we need
1302
-                if (! isset($cfg['title'])) {
1303
-                    throw new EE_Error(
1304
-                        esc_html__(
1305
-                            'The _page_config array is not set up properly for help tabs.  It is missing a title',
1306
-                            'event_espresso'
1307
-                        )
1308
-                    );
1309
-                }
1310
-                if (! isset($cfg['filename']) && ! isset($cfg['callback']) && ! isset($cfg['content'])) {
1311
-                    throw new EE_Error(
1312
-                        esc_html__(
1313
-                            'The _page_config array is not setup properly for help tabs. It is missing a either a filename reference, or a callback reference or a content reference so there is no way to know the content for the help tab',
1314
-                            'event_espresso'
1315
-                        )
1316
-                    );
1317
-                }
1318
-                // first priority goes to content.
1319
-                if (! empty($cfg['content'])) {
1320
-                    $content = $cfg['content'];
1321
-                    // second priority goes to filename
1322
-                } elseif (! empty($cfg['filename'])) {
1323
-                    $file_path = $this->_get_dir() . '/help_tabs/' . $cfg['filename'] . '.help_tab.php';
1324
-                    // it's possible that the file is located on decaf route (and above sets up for caf route, if this is the case then lets check decaf route too)
1325
-                    $file_path = ! is_readable($file_path) ? EE_ADMIN_PAGES
1326
-                                                             . basename($this->_get_dir())
1327
-                                                             . '/help_tabs/'
1328
-                                                             . $cfg['filename']
1329
-                                                             . '.help_tab.php' : $file_path;
1330
-                    // if file is STILL not readable then let's do an EE_Error so its more graceful than a fatal error.
1331
-                    if (! isset($cfg['callback']) && ! is_readable($file_path)) {
1332
-                        EE_Error::add_error(
1333
-                            sprintf(
1334
-                                esc_html__(
1335
-                                    'The filename given for the help tab %s is not a valid file and there is no other configuration for the tab content.  Please check that the string you set for the help tab on this route (%s) is the correct spelling.  The file should be in %s',
1336
-                                    'event_espresso'
1337
-                                ),
1338
-                                $tab_id,
1339
-                                key($config),
1340
-                                $file_path
1341
-                            ),
1342
-                            __FILE__,
1343
-                            __FUNCTION__,
1344
-                            __LINE__
1345
-                        );
1346
-                        return;
1347
-                    }
1348
-                    $template_args['admin_page_obj'] = $this;
1349
-                    $content                         = EEH_Template::display_template(
1350
-                        $file_path,
1351
-                        $template_args,
1352
-                        true
1353
-                    );
1354
-                } else {
1355
-                    $content = '';
1356
-                }
1357
-                // check if callback is valid
1358
-                if (
1359
-                    empty($content)
1360
-                    && (
1361
-                        ! isset($cfg['callback']) || ! method_exists($this, $cfg['callback'])
1362
-                    )
1363
-                ) {
1364
-                    EE_Error::add_error(
1365
-                        sprintf(
1366
-                            esc_html__(
1367
-                                'The callback given for a %s help tab on this page does not content OR a corresponding method for generating the content.  Check the spelling or make sure the method is present.',
1368
-                                'event_espresso'
1369
-                            ),
1370
-                            $cfg['title']
1371
-                        ),
1372
-                        __FILE__,
1373
-                        __FUNCTION__,
1374
-                        __LINE__
1375
-                    );
1376
-                    return;
1377
-                }
1378
-                // setup config array for help tab method
1379
-                $id  = $this->page_slug . '-' . $this->_req_action . '-' . $tab_id;
1380
-                $_ht = [
1381
-                    'id'       => $id,
1382
-                    'title'    => $cfg['title'],
1383
-                    'callback' => isset($cfg['callback']) && empty($content) ? [$this, $cfg['callback']] : null,
1384
-                    'content'  => $content,
1385
-                ];
1386
-                $this->_current_screen->add_help_tab($_ht);
1387
-            }
1388
-        }
1389
-    }
1390
-
1391
-
1392
-    /**
1393
-     * This simply sets up any qtips that have been defined in the page config
1394
-     *
1395
-     * @return void
1396
-     * @throws ReflectionException
1397
-     * @throws EE_Error
1398
-     */
1399
-    protected function _add_qtips()
1400
-    {
1401
-        if (isset($this->_route_config['qtips'])) {
1402
-            $qtips = (array) $this->_route_config['qtips'];
1403
-            // load qtip loader
1404
-            $path = [
1405
-                $this->_get_dir() . '/qtips/',
1406
-                EE_ADMIN_PAGES . basename($this->_get_dir()) . '/qtips/',
1407
-            ];
1408
-            EEH_Qtip_Loader::instance()->register($qtips, $path);
1409
-        }
1410
-    }
1411
-
1412
-
1413
-    /**
1414
-     * _set_nav_tabs
1415
-     * This sets up the nav tabs from the page_routes array.  This method can be overwritten by child classes if you
1416
-     * wish to add additional tabs or modify accordingly.
1417
-     *
1418
-     * @return void
1419
-     * @throws InvalidArgumentException
1420
-     * @throws InvalidInterfaceException
1421
-     * @throws InvalidDataTypeException
1422
-     */
1423
-    protected function _set_nav_tabs()
1424
-    {
1425
-        $i        = 0;
1426
-        $only_tab = count($this->_page_config) < 2;
1427
-        foreach ($this->_page_config as $slug => $config) {
1428
-            if (! is_array($config) || empty($config['nav'])) {
1429
-                continue;
1430
-            }
1431
-            // no nav tab for this config
1432
-            // check for persistent flag
1433
-            if ($slug !== $this->_req_action && isset($config['nav']['persistent']) && ! $config['nav']['persistent']) {
1434
-                // nav tab is only to appear when route requested.
1435
-                continue;
1436
-            }
1437
-            if (! $this->check_user_access($slug, true)) {
1438
-                // no nav tab because current user does not have access.
1439
-                continue;
1440
-            }
1441
-            $css_class = $config['css_class'] ?? '';
1442
-            $css_class .= $only_tab ? ' ee-only-tab' : '';
1443
-            $css_class .= " ee-nav-tab__$slug";
1444
-
1445
-            $this->_nav_tabs[ $slug ] = [
1446
-                'url'       => $config['nav']['url'] ?? EE_Admin_Page::add_query_args_and_nonce(
1447
-                        ['action' => $slug],
1448
-                        $this->_admin_base_url
1449
-                    ),
1450
-                'link_text' => $this->navTabLabel($config['nav'], $slug),
1451
-                'css_class' => $this->_req_action === $slug ? $css_class . ' nav-tab-active' : $css_class,
1452
-                'order'     => $config['nav']['order'] ?? $i,
1453
-            ];
1454
-            $i++;
1455
-        }
1456
-        // if $this->_nav_tabs is empty then lets set the default
1457
-        if (empty($this->_nav_tabs)) {
1458
-            $this->_nav_tabs[ $this->_default_nav_tab_name ] = [
1459
-                'url'       => $this->_admin_base_url,
1460
-                'link_text' => ucwords(str_replace('_', ' ', $this->_default_nav_tab_name)),
1461
-                'css_class' => 'nav-tab-active',
1462
-                'order'     => 10,
1463
-            ];
1464
-        }
1465
-        // now let's sort the tabs according to order
1466
-        usort($this->_nav_tabs, [$this, '_sort_nav_tabs']);
1467
-    }
1468
-
1469
-
1470
-    private function navTabLabel(array $nav_tab, string $slug): string
1471
-    {
1472
-        $label = $nav_tab['label'] ?? ucwords(str_replace('_', ' ', $slug));
1473
-        $icon  = $nav_tab['icon'] ?? null;
1474
-        $icon  = $icon ? '<span class="dashicons ' . $icon . '"></span>' : '';
1475
-        return '
137
+	/**
138
+	 * sanitized request action
139
+	 */
140
+	protected string $_req_action = '';
141
+
142
+	/**
143
+	 * sanitized request action nonce
144
+	 */
145
+	protected string $_req_nonce        = '';
146
+
147
+	protected string $_search_btn_label = '';
148
+
149
+	protected string $_template_path    = '';
150
+
151
+	protected string $_view             = '';
152
+
153
+	/**
154
+	 * set early within EE_Admin_Init
155
+	 *
156
+	 * @var string
157
+	 */
158
+	protected string $_wp_page_slug = '';
159
+
160
+	/**
161
+	 * if the current class is an admin page extension, like: Extend_Events_Admin_Page,
162
+	 * then this would be the parent classname: Events_Admin_Page
163
+	 *
164
+	 * @var string
165
+	 */
166
+	public string $base_class_name = '';
167
+
168
+	public string $class_name      = '';
169
+
170
+	/**
171
+	 * unprocessed value for the 'action' request param (default '')
172
+	 *
173
+	 * @var string
174
+	 */
175
+	protected string $raw_req_action = '';
176
+
177
+	/**
178
+	 * unprocessed value for the 'page' request param (default '')
179
+	 *
180
+	 * @var string
181
+	 */
182
+	protected string $raw_req_page = '';
183
+
184
+	public string    $page_folder  = '';
185
+
186
+	public string    $page_label   = '';
187
+
188
+	public string    $page_slug    = '';
189
+
190
+
191
+	/**
192
+	 * the current page route and route config
193
+	 *
194
+	 * @var array|callable|string|null
195
+	 */
196
+	protected $_route = null;
197
+
198
+
199
+	/**
200
+	 * @Constructor
201
+	 * @param bool $routing indicate whether we want to just load the object and handle routing or just load the object.
202
+	 * @throws InvalidArgumentException
203
+	 * @throws InvalidDataTypeException
204
+	 * @throws InvalidInterfaceException
205
+	 * @throws ReflectionException
206
+	 */
207
+	public function __construct($routing = true)
208
+	{
209
+		$this->loader       = LoaderFactory::getLoader();
210
+		$this->admin_config = $this->loader->getShared(EE_Admin_Config::class);
211
+		$this->feature      = $this->loader->getShared(FeatureFlags::class);
212
+		$this->request      = $this->loader->getShared(RequestInterface::class);
213
+		$this->capabilities = $this->loader->getShared(EE_Capabilities::class);
214
+		// routing enabled?
215
+		$this->_routing = $routing;
216
+
217
+		$this->class_name      = get_class($this);
218
+		$this->base_class_name = strpos($this->class_name, 'Extend_') === 0
219
+			? str_replace('Extend_', '', $this->class_name)
220
+			: '';
221
+
222
+		if (strpos($this->_get_dir(), 'caffeinated') !== false) {
223
+			$this->_is_caf = true;
224
+		}
225
+		$this->_yes_no_values = [
226
+			['id' => true, 'text' => esc_html__('Yes', 'event_espresso')],
227
+			['id' => false, 'text' => esc_html__('No', 'event_espresso')],
228
+		];
229
+		// set the _req_data property.
230
+		$this->_req_data = $this->request->requestParams();
231
+	}
232
+
233
+
234
+	/**
235
+	 * @return EE_Admin_Config
236
+	 */
237
+	public function adminConfig(): EE_Admin_Config
238
+	{
239
+		return $this->admin_config;
240
+	}
241
+
242
+
243
+	/**
244
+	 * @return FeatureFlags
245
+	 */
246
+	public function feature(): FeatureFlags
247
+	{
248
+		return $this->feature;
249
+	}
250
+
251
+
252
+	/**
253
+	 * This logic used to be in the constructor, but that caused a chicken <--> egg scenario
254
+	 * for child classes that needed to set properties prior to these methods getting called,
255
+	 * but also needed the parent class to have its construction completed as well.
256
+	 * Bottom line is that constructors should ONLY be used for setting initial properties
257
+	 * and any complex initialization logic should only run after instantiation is complete.
258
+	 * This method gets called immediately after construction from within
259
+	 *      EE_Admin_Page_Init::_initialize_admin_page()
260
+	 *
261
+	 * @throws EE_Error
262
+	 * @throws InvalidArgumentException
263
+	 * @throws InvalidDataTypeException
264
+	 * @throws InvalidInterfaceException
265
+	 * @throws ReflectionException
266
+	 * @throws Throwable
267
+	 * @since 5.0.0.p
268
+	 */
269
+	public function initializePage()
270
+	{
271
+		if ($this->initialized) {
272
+			return;
273
+		}
274
+		// set initial page props (child method)
275
+		$this->_init_page_props();
276
+		// set global defaults
277
+		$this->_set_defaults();
278
+		// set early because incoming requests could be ajax related and we need to register those hooks.
279
+		$this->_global_ajax_hooks();
280
+		$this->_ajax_hooks();
281
+		// other_page_hooks have to be early too.
282
+		$this->_do_other_page_hooks();
283
+		// set up page dependencies
284
+		$this->_before_page_setup();
285
+		$this->_page_setup();
286
+		$this->initialized = true;
287
+	}
288
+
289
+
290
+	/**
291
+	 * _init_page_props
292
+	 * Child classes use to set at least the following properties:
293
+	 * $page_slug.
294
+	 * $page_label.
295
+	 *
296
+	 * @abstract
297
+	 * @return void
298
+	 */
299
+	abstract protected function _init_page_props();
300
+
301
+
302
+	/**
303
+	 * _ajax_hooks
304
+	 * child classes put all their add_action('wp_ajax_{name_of_hook}') hooks in here.
305
+	 * Note: within the ajax callback methods.
306
+	 *
307
+	 * @abstract
308
+	 * @return void
309
+	 */
310
+	abstract protected function _ajax_hooks();
311
+
312
+
313
+	/**
314
+	 * _define_page_props
315
+	 * child classes define page properties in here.  Must include at least:
316
+	 * $_admin_base_url = base_url for all admin pages
317
+	 * $_admin_page_title = default admin_page_title for admin pages
318
+	 * $_labels = array of default labels for various automatically generated elements:
319
+	 *    array(
320
+	 *        'buttons' => array(
321
+	 *            'add' => esc_html__('label for add new button'),
322
+	 *            'edit' => esc_html__('label for edit button'),
323
+	 *            'delete' => esc_html__('label for delete button')
324
+	 *            )
325
+	 *        )
326
+	 *
327
+	 * @abstract
328
+	 * @return void
329
+	 */
330
+	abstract protected function _define_page_props();
331
+
332
+
333
+	/**
334
+	 * _set_page_routes
335
+	 * child classes use this to define the page routes for all subpages handled by the class.  Page routes are
336
+	 * assigned to an action => method pairs in an array and to the $_page_routes property.  Each page route must also
337
+	 * have a 'default' route. Here's the format
338
+	 * $this->_page_routes = array(
339
+	 *        'default' => array(
340
+	 *            'func' => '_default_method_handling_route',
341
+	 *            'args' => array('array','of','args'),
342
+	 *            'noheader' => true, //add this in if this page route is processed before any headers are loaded (i.e.
343
+	 *            ajax request, backend processing)
344
+	 *            'headers_sent_route'=>'headers_route_reference', //add this if noheader=>true, and you want to load a
345
+	 *            headers route after.  The string you enter here should match the defined route reference for a
346
+	 *            headers sent route.
347
+	 *            'capability' => 'route_capability', //indicate a string for minimum capability required to access
348
+	 *            this route.
349
+	 *            'obj_id' => 10 // if this route has an object id, then this can include it (used for capability
350
+	 *            checks).
351
+	 *        ),
352
+	 *        'insert_item' => '_method_for_handling_insert_item' //this can be used if all we need to have is a
353
+	 *        handling method.
354
+	 *        )
355
+	 * )
356
+	 *
357
+	 * @abstract
358
+	 * @return void
359
+	 */
360
+	abstract protected function _set_page_routes();
361
+
362
+
363
+	/**
364
+	 * _set_page_config
365
+	 * child classes use this to define the _page_config array for all subpages handled by the class. Each key in the
366
+	 * array corresponds to the page_route for the loaded page. Format:
367
+	 * $this->_page_config = array(
368
+	 *        'default' => array(
369
+	 *            'labels' => array(
370
+	 *                'buttons' => array(
371
+	 *                    'add' => esc_html__('label for adding item'),
372
+	 *                    'edit' => esc_html__('label for editing item'),
373
+	 *                    'delete' => esc_html__('label for deleting item')
374
+	 *                ),
375
+	 *                'publishbox' => esc_html__('Localized Title for Publish metabox', 'event_espresso')
376
+	 *            ), //optional an array of custom labels for various automatically generated elements to use on the
377
+	 *            page. If this isn't present then the defaults will be used as set for the $this->_labels in
378
+	 *            _define_page_props() method
379
+	 *            'nav' => array(
380
+	 *                'label' => esc_html__('Label for Tab', 'event_espresso').
381
+	 *                'url' => 'http://someurl', //automatically generated UNLESS you define
382
+	 *                'css_class' => 'css-class', //automatically generated UNLESS you define
383
+	 *                'order' => 10, //required to indicate tab position.
384
+	 *                'persistent' => false //if you want the nav tab to ONLY display when the specific route is
385
+	 *                displayed then add this parameter.
386
+	 *            'list_table' => 'name_of_list_table' //string for list table class to be loaded for this admin_page.
387
+	 *            'metaboxes' => array('metabox1', 'metabox2'), //if present this key indicates we want to load
388
+	 *            metaboxes set for eventespresso admin pages.
389
+	 *            'has_metaboxes' => true, //this boolean flag can simply be used to indicate if the route will have
390
+	 *            metaboxes.  Typically this is used if the 'metaboxes' index is not used because metaboxes are added
391
+	 *            later.  We just use this flag to make sure the necessary js gets enqueued on page load.
392
+	 *            'has_help_popups' => false //defaults(true) //this boolean flag can simply be used to indicate if the
393
+	 *            given route has help popups setup and if it does then we need to make sure thickbox is enqueued.
394
+	 *            'columns' => array(4, 2), //this key triggers the setup of a page that uses columns (metaboxes).  The
395
+	 *            array indicates the max number of columns (4) and the default number of columns on page load (2).
396
+	 *            There is an option in the "screen_options" dropdown that is set up so users can pick what columns they
397
+	 *            want to display.
398
+	 *            'help_tabs' => array( //this is used for adding help tabs to a page
399
+	 *                'tab_id' => array(
400
+	 *                    'title' => 'tab_title',
401
+	 *                    'filename' => 'name_of_file_containing_content', //this is the primary method for setting
402
+	 *                    help tab content.  The fallback if it isn't present is to try the callback.  Filename
403
+	 *                    should match a file in the admin folder's "help_tabs" dir (ie..
404
+	 *                    events/help_tabs/name_of_file_containing_content.help_tab.php)
405
+	 *                    'callback' => 'callback_method_for_content', //if 'filename' isn't present then system will
406
+	 *                    attempt to use the callback which should match the name of a method in the class
407
+	 *                    ),
408
+	 *                'tab2_id' => array(
409
+	 *                    'title' => 'tab2 title',
410
+	 *                    'filename' => 'file_name_2'
411
+	 *                    'callback' => 'callback_method_for_content',
412
+	 *                 ),
413
+	 *            'help_sidebar' => 'callback_for_sidebar_content', //this is used for setting up the sidebar in the
414
+	 *            help tab area on an admin page. @return void
415
+	 *
416
+	 * @abstract
417
+	 */
418
+	abstract protected function _set_page_config();
419
+
420
+
421
+	/**
422
+	 * _add_screen_options
423
+	 * Child classes can add any extra wp_screen_options within this method using built-in WP functions/methods for
424
+	 * doing so. Note child classes can also define _add_screen_options_($this->_current_view) to limit screen options
425
+	 * to a particular view.
426
+	 *
427
+	 * @link   http://chrismarslender.com/wp-tutorials/wordpress-screen-options-tutorial/
428
+	 *         see also WP_Screen object documents...
429
+	 * @link   http://codex.wordpress.org/Class_Reference/WP_Screen
430
+	 * @abstract
431
+	 * @return void
432
+	 */
433
+	abstract protected function _add_screen_options();
434
+
435
+
436
+	/**
437
+	 * _add_feature_pointers
438
+	 * Child classes should use this method for implementing any "feature pointers" (using built-in WP styling js).
439
+	 * Note child classes can also define _add_feature_pointers_($this->_current_view) to limit screen options to a
440
+	 * particular view. Note: this is just a placeholder for now.  Implementation will come down the road See:
441
+	 * WP_Internal_Pointers class in wp-admin/includes/template.php for example (it's a final class so can't be
442
+	 * extended) also see:
443
+	 *
444
+	 * @link   http://eamann.com/tech/wordpress-portland/
445
+	 * @abstract
446
+	 * @return void
447
+	 */
448
+	abstract protected function _add_feature_pointers();
449
+
450
+
451
+	/**
452
+	 * load_scripts_styles
453
+	 * child classes put their wp_enqueue_script and wp_enqueue_style hooks in here for anything they need loaded for
454
+	 * their pages/subpages.  Note this is for all pages/subpages of the system.  You can also load only specific
455
+	 * scripts/styles per view by putting them in a dynamic function in this format
456
+	 * (load_scripts_styles_{$this->_current_view}) which matches your page route (action request arg)
457
+	 *
458
+	 * @abstract
459
+	 * @return void
460
+	 */
461
+	abstract public function load_scripts_styles();
462
+
463
+
464
+	/**
465
+	 * admin_init
466
+	 * Anything that should be set/executed at 'admin_init' WP hook runtime should be put in here.  This will apply to
467
+	 * all pages/views loaded by child class.
468
+	 *
469
+	 * @abstract
470
+	 * @return void
471
+	 */
472
+	abstract public function admin_init();
473
+
474
+
475
+	/**
476
+	 * admin_notices
477
+	 * Anything triggered by the 'admin_notices' WP hook should be put in here.  This particular method will apply to
478
+	 * all pages/views loaded by child class.
479
+	 *
480
+	 * @abstract
481
+	 * @return void
482
+	 */
483
+	abstract public function admin_notices();
484
+
485
+
486
+	/**
487
+	 * admin_footer_scripts
488
+	 * Anything triggered by the 'admin_print_footer_scripts' WP hook should be put in here. This particular method
489
+	 * will apply to all pages/views loaded by child class.
490
+	 *
491
+	 * @return void
492
+	 */
493
+	abstract public function admin_footer_scripts();
494
+
495
+
496
+	/**
497
+	 * admin_footer
498
+	 * anything triggered by the 'admin_footer' WP action hook should be added to here. This particular method will
499
+	 * apply to all pages/views loaded by child class.
500
+	 *
501
+	 * @return void
502
+	 */
503
+	public function admin_footer()
504
+	{
505
+	}
506
+
507
+
508
+	/**
509
+	 * _global_ajax_hooks
510
+	 * all global add_action('wp_ajax_{name_of_hook}') hooks in here.
511
+	 * Note: within the ajax callback methods.
512
+	 *
513
+	 * @abstract
514
+	 * @return void
515
+	 */
516
+	protected function _global_ajax_hooks()
517
+	{
518
+		// for lazy loading of metabox content
519
+		add_action('wp_ajax_espresso-ajax-content', [$this, 'ajax_metabox_content']);
520
+
521
+		add_action(
522
+			'wp_ajax_espresso_hide_status_change_notice',
523
+			[$this, 'hideStatusChangeNotice']
524
+		);
525
+		add_action(
526
+			'wp_ajax_nopriv_espresso_hide_status_change_notice',
527
+			[$this, 'hideStatusChangeNotice']
528
+		);
529
+	}
530
+
531
+
532
+	public function ajax_metabox_content()
533
+	{
534
+		$content_id  = $this->request->getRequestParam('contentid', '');
535
+		$content_url = $this->request->getRequestParam('contenturl', '', DataType::URL);
536
+		EE_Admin_Page::cached_rss_display($content_id, $content_url);
537
+		wp_die();
538
+	}
539
+
540
+
541
+	public function hideStatusChangeNotice()
542
+	{
543
+		$response = [];
544
+		try {
545
+			/** @var StatusChangeNotice $status_change_notice */
546
+			$status_change_notice = $this->loader->getShared(
547
+				'EventEspresso\core\domain\services\admin\notices\status_change\StatusChangeNotice'
548
+			);
549
+			$response['success']  = $status_change_notice->dismiss() > -1;
550
+		} catch (Exception $exception) {
551
+			$response['errors'] = $exception->getMessage();
552
+		}
553
+		wp_send_json($response);
554
+	}
555
+
556
+
557
+	/**
558
+	 * allows extending classes do something specific before the parent constructor runs _page_setup().
559
+	 *
560
+	 * @return void
561
+	 */
562
+	protected function _before_page_setup()
563
+	{
564
+		// default is to do nothing
565
+	}
566
+
567
+
568
+	/**
569
+	 * Makes sure any things that need to be loaded early get handled.
570
+	 * We also escape early here if the page requested doesn't match the object.
571
+	 *
572
+	 * @final
573
+	 * @return void
574
+	 * @throws EE_Error
575
+	 * @throws InvalidArgumentException
576
+	 * @throws ReflectionException
577
+	 * @throws InvalidDataTypeException
578
+	 * @throws InvalidInterfaceException
579
+	 * @throws Throwable
580
+	 */
581
+	final protected function _page_setup()
582
+	{
583
+		// requires?
584
+		// admin_init stuff - global - we're setting this REALLY early
585
+		// so if EE_Admin pages have to hook into other WP pages they can.
586
+		// But keep in mind, not everything is available from the EE_Admin Page object at this point.
587
+		add_action('admin_init', [$this, 'admin_init_global'], 5);
588
+		// next verify if we need to load anything...
589
+		$this->_current_page = $this->request->getRequestParam('page', '', DataType::KEY);
590
+		$this->_current_page = $this->request->getRequestParam('current_page', $this->_current_page, DataType::KEY);
591
+		$this->page_folder   = strtolower(
592
+			str_replace(['_Admin_Page', 'Extend_'], '', $this->class_name)
593
+		);
594
+		global $ee_menu_slugs;
595
+		$ee_menu_slugs = (array) $ee_menu_slugs;
596
+		if (
597
+			! $this->request->isAjax()
598
+			&& (! $this->_current_page || ! isset($ee_menu_slugs[ $this->_current_page ]))
599
+		) {
600
+			return;
601
+		}
602
+		// because WP List tables have two duplicate select inputs for choosing bulk actions,
603
+		// we need to copy the action from the second to the first
604
+		$action     = $this->request->getRequestParam('action', '-1', DataType::KEY);
605
+		$action2    = $this->request->getRequestParam('action2', '-1', DataType::KEY);
606
+		$action     = $action !== '-1' ? $action : $action2;
607
+		$req_action = $action !== '-1' ? $action : 'default';
608
+
609
+		// if a specific 'route' has been set, and the action is 'default' OR we are doing_ajax
610
+		// then let's use the route as the action.
611
+		// This covers cases where we're coming in from a list table that isn't on the default route.
612
+		$route             = $this->request->getRequestParam('route');
613
+		$this->_req_action = $route && ($req_action === 'default' || $this->request->isAjax())
614
+			? $route
615
+			: $req_action;
616
+		$this->_current_view = $this->_req_action;
617
+		$this->_req_nonce    = $this->_req_action . '_nonce';
618
+		$this->_define_page_props();
619
+		$this->_current_page_view_url = add_query_arg(
620
+			['page' => $this->_current_page, 'action' => $this->_current_view],
621
+			$this->_admin_base_url
622
+		);
623
+		// set page configs
624
+		$this->_set_page_routes();
625
+		$this->_set_page_config();
626
+		// let's include any referrer data in our default_query_args for this route for "stickiness".
627
+		if ($this->request->requestParamIsSet('wp_referer')) {
628
+			$wp_referer = $this->request->getRequestParam('wp_referer');
629
+			if ($wp_referer) {
630
+				$this->_default_route_query_args['wp_referer'] = $wp_referer;
631
+			}
632
+		}
633
+		// for CPT and other extended functionality.
634
+		// If there is an _extend_page_config_for_cpt
635
+		// then let's run that to modify all the various page configuration arrays.
636
+		if (method_exists($this, '_extend_page_config_for_cpt')) {
637
+			$this->_extend_page_config_for_cpt();
638
+		}
639
+		// filter routes and page_config so addons can add their stuff. Filtering done per class
640
+		$this->_page_routes = apply_filters(
641
+			'FHEE__' . $this->class_name . '__page_setup__page_routes',
642
+			$this->_page_routes,
643
+			$this
644
+		);
645
+		$this->_page_config = apply_filters(
646
+			'FHEE__' . $this->class_name . '__page_setup__page_config',
647
+			$this->_page_config,
648
+			$this
649
+		);
650
+		if ($this->base_class_name !== '') {
651
+			$this->_page_routes = apply_filters(
652
+				'FHEE__' . $this->base_class_name . '__page_setup__page_routes',
653
+				$this->_page_routes,
654
+				$this
655
+			);
656
+			$this->_page_config = apply_filters(
657
+				'FHEE__' . $this->base_class_name . '__page_setup__page_config',
658
+				$this->_page_config,
659
+				$this
660
+			);
661
+		}
662
+		// if AHEE__EE_Admin_Page__route_admin_request_$this->_current_view method is present
663
+		// then we call it hooked into the AHEE__EE_Admin_Page__route_admin_request action
664
+		if (method_exists($this, 'AHEE__EE_Admin_Page__route_admin_request_' . $this->_current_view)) {
665
+			add_action(
666
+				'AHEE__EE_Admin_Page__route_admin_request',
667
+				[$this, 'AHEE__EE_Admin_Page__route_admin_request_' . $this->_current_view],
668
+				10,
669
+				2
670
+			);
671
+		}
672
+		// next route only if routing enabled
673
+		if ($this->_routing && ! $this->request->isAjax()) {
674
+			$this->_verify_routes();
675
+			// next let's just check user_access and kill if no access
676
+			$this->check_user_access();
677
+			if ($this->_is_UI_request) {
678
+				// admin_init stuff - global, all views for this page class, specific view
679
+				add_action('admin_init', [$this, 'admin_init']);
680
+				if (method_exists($this, 'admin_init_' . $this->_current_view)) {
681
+					add_action('admin_init', [$this, 'admin_init_' . $this->_current_view], 15);
682
+				}
683
+			} else {
684
+				// hijack regular WP loading and route admin request immediately
685
+				@ini_set('memory_limit', apply_filters('admin_memory_limit', WP_MAX_MEMORY_LIMIT));
686
+				$this->route_admin_request();
687
+			}
688
+		}
689
+	}
690
+
691
+
692
+	/**
693
+	 * Provides a way for related child admin pages to load stuff on the loaded admin page.
694
+	 *
695
+	 * @return void
696
+	 * @throws EE_Error
697
+	 */
698
+	private function _do_other_page_hooks()
699
+	{
700
+		$registered_pages = apply_filters('FHEE_do_other_page_hooks_' . $this->page_slug, []);
701
+		foreach ($registered_pages as $page) {
702
+			// now let's set up the file name and class that should be present
703
+			$classname = str_replace('.class.php', '', $page);
704
+			// autoloaders should take care of loading file
705
+			if (! class_exists($classname)) {
706
+				$error_msg[] = sprintf(
707
+					esc_html__(
708
+						'Something went wrong with loading the %s admin hooks page.',
709
+						'event_espresso'
710
+					),
711
+					$page
712
+				);
713
+				$error_msg[] = $error_msg[0]
714
+							   . "\r\n"
715
+							   . sprintf(
716
+								   esc_html__(
717
+									   'There is no class in place for the %1$s admin hooks page.%2$sMake sure you have %3$s defined. If this is a non-EE-core admin page then you also must have an autoloader in place for your class',
718
+									   'event_espresso'
719
+								   ),
720
+								   $page,
721
+								   '<br />',
722
+								   '<strong>' . $classname . '</strong>'
723
+							   );
724
+				throw new EE_Error(implode('||', $error_msg));
725
+			}
726
+			// don't load the same class twice
727
+			static $loaded = [];
728
+			if (in_array($classname, $loaded, true)) {
729
+				continue;
730
+			}
731
+			$loaded[] = $classname;
732
+			// notice we are passing the instance of this class to the hook object.
733
+			$this->loader->getShared($classname, [$this]);
734
+		}
735
+	}
736
+
737
+
738
+	/**
739
+	 * @throws ReflectionException
740
+	 * @throws EE_Error
741
+	 */
742
+	public function load_page_dependencies()
743
+	{
744
+		try {
745
+			$this->_load_page_dependencies();
746
+		} catch (EE_Error $e) {
747
+			$e->get_error();
748
+		}
749
+	}
750
+
751
+
752
+	/**
753
+	 * load_page_dependencies
754
+	 * loads things specific to this page class when it's loaded.  Really helps with efficiency.
755
+	 *
756
+	 * @return void
757
+	 * @throws DomainException
758
+	 * @throws EE_Error
759
+	 * @throws InvalidArgumentException
760
+	 * @throws InvalidDataTypeException
761
+	 * @throws InvalidInterfaceException
762
+	 * @throws ReflectionException
763
+	 */
764
+	protected function _load_page_dependencies()
765
+	{
766
+		// let's set the current_screen and screen options to override what WP set
767
+		$this->_current_screen = get_current_screen();
768
+		// load admin_notices - global, page class, and view specific
769
+		add_action('admin_notices', [$this, 'admin_notices_global'], 5);
770
+		add_action('admin_notices', [$this, 'admin_notices']);
771
+		if (method_exists($this, 'admin_notices_' . $this->_current_view)) {
772
+			add_action('admin_notices', [$this, 'admin_notices_' . $this->_current_view], 15);
773
+		}
774
+		// load network admin_notices - global, page class, and view specific
775
+		add_action('network_admin_notices', [$this, 'network_admin_notices_global'], 5);
776
+		if (method_exists($this, 'network_admin_notices_' . $this->_current_view)) {
777
+			add_action('network_admin_notices', [$this, 'network_admin_notices_' . $this->_current_view]);
778
+		}
779
+		// this will save any per_page screen options if they are present
780
+		$this->_set_per_page_screen_options();
781
+		// setup list table properties
782
+		$this->_set_list_table();
783
+		// child classes can "register" a metabox to be automatically handled via the _page_config array property.
784
+		// However in some cases the metaboxes will need to be added within a route handling callback.
785
+		add_action('add_meta_boxes', [$this, 'addRegisteredMetaBoxes'], 99);
786
+		// hack because promos admin was loading the edited promotion object in the metaboxes callback
787
+		// which should NOT be generated on non-UI requests like POST updates/inserts
788
+		if (
789
+			$this->class_name === 'Promotions_Admin_Page'
790
+			&& ($this->_req_action === 'edit' || $this->_req_action === 'create_new')
791
+		) {
792
+			$this->addRegisteredMetaBoxes();
793
+		}
794
+		$this->_add_screen_columns();
795
+		// add screen options - global, page child class, and view specific
796
+		$this->_add_global_screen_options();
797
+		$this->_add_screen_options();
798
+		$add_screen_options = "_add_screen_options_$this->_current_view";
799
+		if (method_exists($this, $add_screen_options)) {
800
+			$this->{$add_screen_options}();
801
+		}
802
+		// add help tab(s) - set via page_config and qtips.
803
+		$this->_add_help_tabs();
804
+		$this->_add_qtips();
805
+		// add feature_pointers - global, page child class, and view specific
806
+		$this->_add_feature_pointers();
807
+		$this->_add_global_feature_pointers();
808
+		$add_feature_pointer = "_add_feature_pointer_$this->_current_view";
809
+		if (method_exists($this, $add_feature_pointer)) {
810
+			$this->{$add_feature_pointer}();
811
+		}
812
+		// enqueue scripts/styles - global, page class, and view specific
813
+		add_action('admin_enqueue_scripts', [$this, 'load_global_scripts_styles'], 5);
814
+		add_action('admin_enqueue_scripts', [$this, 'load_scripts_styles']);
815
+		if (method_exists($this, "load_scripts_styles_$this->_current_view")) {
816
+			add_action('admin_enqueue_scripts', [$this, "load_scripts_styles_$this->_current_view"], 15);
817
+		}
818
+		add_action('admin_enqueue_scripts', [$this, 'admin_footer_scripts_eei18n_js_strings'], 100);
819
+		// admin_print_footer_scripts - global, page child class, and view specific.
820
+		// NOTE, despite the name, whenever possible, scripts should NOT be loaded using this.
821
+		// In most cases that's doing_it_wrong().  But adding hidden container elements etc.
822
+		// is a good use case. Notice the late priority we're giving these
823
+		add_action('admin_print_footer_scripts', [$this, 'admin_footer_scripts_global'], 99);
824
+		add_action('admin_print_footer_scripts', [$this, 'admin_footer_scripts'], 100);
825
+		if (method_exists($this, "admin_footer_scripts_$this->_current_view")) {
826
+			add_action('admin_print_footer_scripts', [$this, "admin_footer_scripts_$this->_current_view"], 101);
827
+		}
828
+		// admin footer scripts
829
+		add_action('admin_footer', [$this, 'admin_footer_global'], 99);
830
+		add_action('admin_footer', [$this, 'admin_footer'], 100);
831
+		if (method_exists($this, "admin_footer_$this->_current_view")) {
832
+			add_action('admin_footer', [$this, "admin_footer_$this->_current_view"], 101);
833
+		}
834
+		do_action('FHEE__EE_Admin_Page___load_page_dependencies__after_load', $this->page_slug);
835
+		// targeted hook
836
+		do_action(
837
+			"FHEE__EE_Admin_Page___load_page_dependencies__after_load__{$this->page_slug}__$this->_req_action"
838
+		);
839
+	}
840
+
841
+
842
+	/**
843
+	 * _set_defaults
844
+	 * This sets some global defaults for class properties.
845
+	 */
846
+	private function _set_defaults()
847
+	{
848
+		// init template args
849
+		$this->set_template_args(
850
+			[
851
+				'admin_page_header'  => '',
852
+				'admin_page_content' => '',
853
+				'post_body_content'  => '',
854
+				'before_list_table'  => '',
855
+				'after_list_table'   => '',
856
+			]
857
+		);
858
+	}
859
+
860
+
861
+	/**
862
+	 * route_admin_request
863
+	 *
864
+	 * @return void
865
+	 * @throws InvalidArgumentException
866
+	 * @throws InvalidInterfaceException
867
+	 * @throws InvalidDataTypeException
868
+	 * @throws EE_Error
869
+	 * @throws ReflectionException
870
+	 * @throws Throwable
871
+	 * @see    _route_admin_request()
872
+	 */
873
+	public function route_admin_request()
874
+	{
875
+		try {
876
+			$this->_route_admin_request();
877
+		} catch (EE_Error $e) {
878
+			$e->get_error();
879
+		}
880
+	}
881
+
882
+
883
+	public function set_wp_page_slug($wp_page_slug)
884
+	{
885
+		$this->_wp_page_slug = $wp_page_slug;
886
+		// if in network admin then we need to append "-network" to the page slug. Why? Because that's how WP rolls...
887
+		if (is_network_admin()) {
888
+			$this->_wp_page_slug .= '-network';
889
+		}
890
+	}
891
+
892
+
893
+	/**
894
+	 * _verify_routes
895
+	 * All this method does is verify the incoming request and make sure that routes exist for it.  We do this early so
896
+	 * we know if we need to drop out.
897
+	 *
898
+	 * @return bool
899
+	 * @throws EE_Error
900
+	 */
901
+	protected function _verify_routes(): bool
902
+	{
903
+		if (! $this->_current_page && ! $this->request->isAjax()) {
904
+			return false;
905
+		}
906
+		// check that the page_routes array is not empty
907
+		if (empty($this->_page_routes)) {
908
+			// user error msg
909
+			$error_msg = sprintf(
910
+				esc_html__('No page routes have been set for the %s admin page.', 'event_espresso'),
911
+				$this->_admin_page_title
912
+			);
913
+			// developer error msg
914
+			$error_msg .= '||' . $error_msg
915
+						  . esc_html__(
916
+							  ' Make sure the "set_page_routes()" method exists, and is setting the "_page_routes" array properly.',
917
+							  'event_espresso'
918
+						  );
919
+			throw new EE_Error($error_msg);
920
+		}
921
+		// route 'editpost' routes to CPT 'edit' routes
922
+		$alt_edit_route = $this instanceof EE_Admin_Page_CPT ? $this->cpt_editpost_route : 'edit';
923
+		if (
924
+			$this->_req_action === 'editpost'
925
+			&& ! isset($this->_page_routes['editpost'])
926
+			&& isset($this->_page_routes[$alt_edit_route])
927
+		) {
928
+			$this->_req_action = $alt_edit_route;
929
+		}
930
+		// and that the requested page route exists
931
+		if (array_key_exists($this->_req_action, $this->_page_routes)) {
932
+			$this->_route        = $this->_page_routes[ $this->_req_action ];
933
+			$this->_route_config = $this->_page_config[ $this->_req_action ] ?? [];
934
+		} else {
935
+			// user error msg
936
+			$error_msg = sprintf(
937
+				esc_html__(
938
+					'The requested page route does not exist for the %s admin page.',
939
+					'event_espresso'
940
+				),
941
+				$this->_admin_page_title
942
+			);
943
+			// developer error msg
944
+			$error_msg .= '||' . $error_msg
945
+						  . sprintf(
946
+							  esc_html__(
947
+								  ' Create a key in the "_page_routes" array named "%s" and set its value to the appropriate method.',
948
+								  'event_espresso'
949
+							  ),
950
+							  $this->_req_action
951
+						  );
952
+			throw new EE_Error($error_msg);
953
+		}
954
+		// and that a default route exists
955
+		if (! array_key_exists('default', $this->_page_routes)) {
956
+			// user error msg
957
+			$error_msg = sprintf(
958
+				esc_html__(
959
+					'A default page route has not been set for the % admin page.',
960
+					'event_espresso'
961
+				),
962
+				$this->_admin_page_title
963
+			);
964
+			// developer error msg
965
+			$error_msg .= '||' . $error_msg
966
+						  . esc_html__(
967
+							  ' Create a key in the "_page_routes" array named "default" and set its value to your default page method.',
968
+							  'event_espresso'
969
+						  );
970
+			throw new EE_Error($error_msg);
971
+		}
972
+
973
+		// first lets' catch if the UI request has EVER been set.
974
+		if ($this->_is_UI_request === null) {
975
+			// let's set if this is a UI request or not.
976
+			$this->_is_UI_request = ! $this->request->getRequestParam('noheader', false, DataType::BOOL);
977
+			// wait a minute... we might have a noheader in the route array
978
+			$this->_is_UI_request = ! (isset($this->_route['noheader']) && $this->_route['noheader'])
979
+				? $this->_is_UI_request
980
+				: false;
981
+		}
982
+		$this->_set_current_labels();
983
+		return true;
984
+	}
985
+
986
+
987
+	/**
988
+	 * this method simply verifies a given route and makes sure it's an actual route available for the loaded page
989
+	 *
990
+	 * @param string $route the route name we're verifying
991
+	 * @return bool we'll throw an exception if this isn't a valid route.
992
+	 * @throws EE_Error
993
+	 */
994
+	protected function _verify_route(string $route): bool
995
+	{
996
+		if (array_key_exists($this->_req_action, $this->_page_routes)) {
997
+			return true;
998
+		}
999
+		// user error msg
1000
+		$error_msg = sprintf(
1001
+			esc_html__('The given page route does not exist for the %s admin page.', 'event_espresso'),
1002
+			$this->_admin_page_title
1003
+		);
1004
+		// developer error msg
1005
+		$error_msg .= '||' . $error_msg
1006
+					  . sprintf(
1007
+						  esc_html__(
1008
+							  ' Check the route you are using in your method (%s) and make sure it matches a route set in your "_page_routes" array property',
1009
+							  'event_espresso'
1010
+						  ),
1011
+						  $route
1012
+					  );
1013
+		throw new EE_Error($error_msg);
1014
+	}
1015
+
1016
+
1017
+	/**
1018
+	 * perform nonce verification
1019
+	 * This method has be encapsulated here so that any ajax requests that bypass normal routes can verify their nonces
1020
+	 * using this method (and save retyping!)
1021
+	 *
1022
+	 * @param string $nonce     The nonce sent
1023
+	 * @param string $nonce_ref The nonce reference string (name0)
1024
+	 * @return void
1025
+	 * @throws EE_Error
1026
+	 * @throws InvalidArgumentException
1027
+	 * @throws InvalidDataTypeException
1028
+	 * @throws InvalidInterfaceException
1029
+	 */
1030
+	protected function _verify_nonce(string $nonce, string $nonce_ref)
1031
+	{
1032
+		// verify nonce against expected value
1033
+		if (! wp_verify_nonce($nonce, $nonce_ref)) {
1034
+			// these are not the droids you are looking for !!!
1035
+			$msg = sprintf(
1036
+				esc_html__('%sNonce Fail.%s', 'event_espresso'),
1037
+				'<a href="https://www.youtube.com/watch?v=56_S0WeTkzs">',
1038
+				'</a>'
1039
+			);
1040
+			if (WP_DEBUG) {
1041
+				$msg .= "\n  ";
1042
+				$msg .= sprintf(
1043
+					esc_html__(
1044
+						'In order to dynamically generate nonces for your actions, use the %s::add_query_args_and_nonce() method. May the Nonce be with you!',
1045
+						'event_espresso'
1046
+					),
1047
+					__CLASS__
1048
+				);
1049
+			}
1050
+			if (! $this->request->isAjax()) {
1051
+				wp_die($msg);
1052
+			}
1053
+			EE_Error::add_error($msg, __FILE__, __FUNCTION__, __LINE__);
1054
+			$this->_return_json();
1055
+		}
1056
+	}
1057
+
1058
+
1059
+	/**
1060
+	 * _route_admin_request()
1061
+	 * Meat and potatoes of the class.  Basically, this dude checks out what's being requested and sees if there are
1062
+	 * some doodads to work the magic and handle the flingjangy. Translation:  Checks if the requested action is listed
1063
+	 * in the page routes and then will try to load the corresponding method.
1064
+	 *
1065
+	 * @return void
1066
+	 * @throws EE_Error
1067
+	 * @throws InvalidArgumentException
1068
+	 * @throws InvalidDataTypeException
1069
+	 * @throws InvalidInterfaceException
1070
+	 * @throws ReflectionException
1071
+	 * @throws Throwable
1072
+	 */
1073
+	protected function _route_admin_request()
1074
+	{
1075
+		if (! $this->_is_UI_request) {
1076
+			$this->_verify_routes();
1077
+		}
1078
+		$nonce_check = ! isset($this->_route_config['require_nonce']) || $this->_route_config['require_nonce'];
1079
+		if ($this->_req_action !== 'default' && $nonce_check) {
1080
+			// set nonce from post data
1081
+			$nonce = $this->request->getRequestParam($this->_req_nonce, '');
1082
+			$this->_verify_nonce($nonce, $this->_req_nonce);
1083
+		}
1084
+		// set the nav_tabs array but ONLY if this is  UI_request
1085
+		if ($this->_is_UI_request) {
1086
+			$this->_set_nav_tabs();
1087
+		}
1088
+		// grab callback function
1089
+		$func = $this->_route['func'] ?? $this->_route;
1090
+		// check if callback has args
1091
+		$args = $this->_route['args'] ?? [];
1092
+
1093
+		// action right before calling route
1094
+		// (hook is something like 'AHEE__Registrations_Admin_Page__route_admin_request')
1095
+		if (! did_action('AHEE__EE_Admin_Page__route_admin_request')) {
1096
+			do_action('AHEE__EE_Admin_Page__route_admin_request', $this->_current_view, $this);
1097
+		}
1098
+		// strip _wp_http_referer from the server REQUEST_URI
1099
+		// else it grows in length on every submission due to recursion,
1100
+		// ultimately causing a "Request-URI Too Large" error
1101
+		$this->request->unSetRequestParam('_wp_http_referer');
1102
+		$this->request->unSetServerParam('_wp_http_referer');
1103
+		$cleaner_request_uri = remove_query_arg(
1104
+			'_wp_http_referer',
1105
+			wp_unslash($this->request->getServerParam('REQUEST_URI'))
1106
+		);
1107
+		$this->request->setRequestParam('_wp_http_referer', $cleaner_request_uri, true);
1108
+		$this->request->setServerParam('REQUEST_URI', $cleaner_request_uri, true);
1109
+		$route_callback = [];
1110
+		if (! empty($func)) {
1111
+			if (is_array($func) && is_callable($func)) {
1112
+				$route_callback = $func;
1113
+			} elseif (is_string($func)) {
1114
+				if (strpos($func, '::') !== false) {
1115
+					$route_callback = explode('::', $func);
1116
+				} elseif (method_exists($this, $func)) {
1117
+					$route_callback = [$this, $func];
1118
+				} else {
1119
+					$route_callback = $func;
1120
+				}
1121
+			}
1122
+			[$class, $method] = $route_callback;
1123
+			// is it neither a class method NOR a standalone function?
1124
+			if (! is_callable($route_callback)) {
1125
+				// user error msg
1126
+				$error_msg = esc_html__(
1127
+					'An error occurred. The  requested page route could not be found.',
1128
+					'event_espresso'
1129
+				);
1130
+				// developer error msg
1131
+				$error_msg .= '||';
1132
+				$error_msg .= sprintf(
1133
+					esc_html__(
1134
+						'Page route "%s" could not be called. Check that the spelling for method names and actions in the "_page_routes" array are all correct.',
1135
+						'event_espresso'
1136
+					),
1137
+					$method
1138
+				);
1139
+				throw new DomainException($error_msg);
1140
+			}
1141
+			if ($class !== $this && ! in_array($this, $args)) {
1142
+				// send along this admin page object for access by addons.
1143
+				$args['admin_page'] = $this;
1144
+			}
1145
+
1146
+			call_user_func_array($route_callback, $args);
1147
+		}
1148
+		// if we've routed and this route has a no headers route AND a sent_headers_route,
1149
+		// then we need to reset the routing properties to the new route.
1150
+		// now if UI request is FALSE and noheader is true AND we have a headers_sent_route in the route array then let's set UI_request to true because the no header route has a second func after headers have been sent.
1151
+		if (
1152
+			$this->_is_UI_request === false
1153
+			&& is_array($this->_route)
1154
+			&& ! empty($this->_route['headers_sent_route'])
1155
+		) {
1156
+			$this->_reset_routing_properties($this->_route['headers_sent_route']);
1157
+		}
1158
+	}
1159
+
1160
+
1161
+	/**
1162
+	 * This method just allows the resetting of page properties in the case where a no headers
1163
+	 * route redirects to a headers route in its route config.
1164
+	 *
1165
+	 * @param string $new_route New (non header) route to redirect to.
1166
+	 * @return void
1167
+	 * @throws ReflectionException
1168
+	 * @throws InvalidArgumentException
1169
+	 * @throws InvalidInterfaceException
1170
+	 * @throws InvalidDataTypeException
1171
+	 * @throws EE_Error
1172
+	 * @throws Throwable
1173
+	 * @since   4.3.0
1174
+	 */
1175
+	protected function _reset_routing_properties(string $new_route)
1176
+	{
1177
+		$this->_is_UI_request = true;
1178
+		// now we set the current route to whatever the headers_sent_route is set at
1179
+		$this->request->setRequestParam('action', $new_route);
1180
+		// rerun page setup
1181
+		$this->_page_setup();
1182
+	}
1183
+
1184
+
1185
+	/**
1186
+	 * _add_query_arg
1187
+	 * adds nonce to array of arguments then calls WP add_query_arg function
1188
+	 *(internally just uses EEH_URL's function with the same name)
1189
+	 *
1190
+	 * @param array  $args
1191
+	 * @param string $url
1192
+	 * @param bool   $sticky                  if true, then the existing Request params will be appended to the
1193
+	 *                                        generated url in an associative array indexed by the key 'wp_referer';
1194
+	 *                                        Example usage: If the current page is:
1195
+	 *                                        http://mydomain.com/wp-admin/admin.php?page=espresso_registrations
1196
+	 *                                        &action=default&event_id=20&month_range=March%202015
1197
+	 *                                        &_wpnonce=5467821
1198
+	 *                                        and you call:
1199
+	 *                                        EE_Admin_Page::add_query_args_and_nonce(
1200
+	 *                                        array(
1201
+	 *                                        'action' => 'resend_something',
1202
+	 *                                        'page=>espresso_registrations'
1203
+	 *                                        ),
1204
+	 *                                        $some_url,
1205
+	 *                                        true
1206
+	 *                                        );
1207
+	 *                                        It will produce a URL in this structure:
1208
+	 *                                        http://{$some_url}/?page=espresso_registrations&action=resend_something
1209
+	 *                                        &wp_referer[action]=default&wp_referer[event_id]=20&wpreferer[
1210
+	 *                                        month_range]=March%202015
1211
+	 * @param bool   $exclude_nonce           If true, the nonce will be excluded from the generated nonce.
1212
+	 * @param int    $context
1213
+	 * @return string
1214
+	 */
1215
+	public static function add_query_args_and_nonce(
1216
+		array $args = [],
1217
+		string $url = '',
1218
+		bool $sticky = false,
1219
+		bool $exclude_nonce = false,
1220
+		int $context = EEH_URL::CONTEXT_NONE
1221
+	): string {
1222
+		// if there is a _wp_http_referer include the values from the request but only if sticky = true
1223
+		if ($sticky) {
1224
+			/** @var RequestInterface $request */
1225
+			$request = LoaderFactory::getLoader()->getShared(RequestInterface::class);
1226
+			$request->unSetRequestParams(['_wp_http_referer', 'wp_referer'], true);
1227
+			$request->unSetServerParam('_wp_http_referer', true);
1228
+			foreach ($request->requestParams() as $key => $value) {
1229
+				// do not add nonces
1230
+				if (strpos($key, 'nonce') !== false) {
1231
+					continue;
1232
+				}
1233
+				$args[ 'wp_referer[' . $key . ']' ] = is_string($value) ? htmlspecialchars($value) : $value;
1234
+			}
1235
+		}
1236
+		return EEH_URL::add_query_args_and_nonce($args, $url, $exclude_nonce, $context);
1237
+	}
1238
+
1239
+
1240
+	/**
1241
+	 * This returns a generated link that will load the related help tab.
1242
+	 *
1243
+	 * @param string $help_tab_id the id for the connected help tab
1244
+	 * @param string $icon_style  (optional) include css class for the style you want to use for the help icon.
1245
+	 * @param string $help_text   (optional) send help text you want to use for the link if default not to be used
1246
+	 * @return string              generated link
1247
+	 * @uses EEH_Template::get_help_tab_link()
1248
+	 */
1249
+	protected function _get_help_tab_link(string $help_tab_id, string $icon_style = '', string $help_text = ''): string
1250
+	{
1251
+		return EEH_Template::get_help_tab_link(
1252
+			$help_tab_id,
1253
+			$this->page_slug,
1254
+			$this->_req_action,
1255
+			$icon_style,
1256
+			$help_text
1257
+		);
1258
+	}
1259
+
1260
+
1261
+	/**
1262
+	 * _add_help_tabs
1263
+	 * Note child classes define their help tabs within the page_config array.
1264
+	 *
1265
+	 * @link   http://codex.wordpress.org/Function_Reference/add_help_tab
1266
+	 * @return void
1267
+	 * @throws DomainException
1268
+	 * @throws EE_Error
1269
+	 * @throws ReflectionException
1270
+	 */
1271
+	protected function _add_help_tabs()
1272
+	{
1273
+		if (isset($this->_page_config[ $this->_req_action ])) {
1274
+			$config = $this->_page_config[ $this->_req_action ];
1275
+			// let's see if there is a help_sidebar set for the current route and we'll set that up for usage as well.
1276
+			if (is_array($config) && isset($config['help_sidebar'])) {
1277
+				// check that the callback given is valid
1278
+				if (! method_exists($this, $config['help_sidebar'])) {
1279
+					throw new EE_Error(
1280
+						sprintf(
1281
+							esc_html__(
1282
+								'The _page_config array has a callback set for the "help_sidebar" option.  However the callback given (%s) is not a valid callback.  Double check the spelling and make sure this method exists for the class %s',
1283
+								'event_espresso'
1284
+							),
1285
+							$config['help_sidebar'],
1286
+							$this->class_name
1287
+						)
1288
+					);
1289
+				}
1290
+				$content = apply_filters(
1291
+					'FHEE__' . $this->class_name . '__add_help_tabs__help_sidebar',
1292
+					$this->{$config['help_sidebar']}()
1293
+				);
1294
+				$this->_current_screen->set_help_sidebar($content);
1295
+			}
1296
+			if (! isset($config['help_tabs'])) {
1297
+				return;
1298
+			} //no help tabs for this route
1299
+			foreach ((array) $config['help_tabs'] as $tab_id => $cfg) {
1300
+				// we're here so there ARE help tabs!
1301
+				// make sure we've got what we need
1302
+				if (! isset($cfg['title'])) {
1303
+					throw new EE_Error(
1304
+						esc_html__(
1305
+							'The _page_config array is not set up properly for help tabs.  It is missing a title',
1306
+							'event_espresso'
1307
+						)
1308
+					);
1309
+				}
1310
+				if (! isset($cfg['filename']) && ! isset($cfg['callback']) && ! isset($cfg['content'])) {
1311
+					throw new EE_Error(
1312
+						esc_html__(
1313
+							'The _page_config array is not setup properly for help tabs. It is missing a either a filename reference, or a callback reference or a content reference so there is no way to know the content for the help tab',
1314
+							'event_espresso'
1315
+						)
1316
+					);
1317
+				}
1318
+				// first priority goes to content.
1319
+				if (! empty($cfg['content'])) {
1320
+					$content = $cfg['content'];
1321
+					// second priority goes to filename
1322
+				} elseif (! empty($cfg['filename'])) {
1323
+					$file_path = $this->_get_dir() . '/help_tabs/' . $cfg['filename'] . '.help_tab.php';
1324
+					// it's possible that the file is located on decaf route (and above sets up for caf route, if this is the case then lets check decaf route too)
1325
+					$file_path = ! is_readable($file_path) ? EE_ADMIN_PAGES
1326
+															 . basename($this->_get_dir())
1327
+															 . '/help_tabs/'
1328
+															 . $cfg['filename']
1329
+															 . '.help_tab.php' : $file_path;
1330
+					// if file is STILL not readable then let's do an EE_Error so its more graceful than a fatal error.
1331
+					if (! isset($cfg['callback']) && ! is_readable($file_path)) {
1332
+						EE_Error::add_error(
1333
+							sprintf(
1334
+								esc_html__(
1335
+									'The filename given for the help tab %s is not a valid file and there is no other configuration for the tab content.  Please check that the string you set for the help tab on this route (%s) is the correct spelling.  The file should be in %s',
1336
+									'event_espresso'
1337
+								),
1338
+								$tab_id,
1339
+								key($config),
1340
+								$file_path
1341
+							),
1342
+							__FILE__,
1343
+							__FUNCTION__,
1344
+							__LINE__
1345
+						);
1346
+						return;
1347
+					}
1348
+					$template_args['admin_page_obj'] = $this;
1349
+					$content                         = EEH_Template::display_template(
1350
+						$file_path,
1351
+						$template_args,
1352
+						true
1353
+					);
1354
+				} else {
1355
+					$content = '';
1356
+				}
1357
+				// check if callback is valid
1358
+				if (
1359
+					empty($content)
1360
+					&& (
1361
+						! isset($cfg['callback']) || ! method_exists($this, $cfg['callback'])
1362
+					)
1363
+				) {
1364
+					EE_Error::add_error(
1365
+						sprintf(
1366
+							esc_html__(
1367
+								'The callback given for a %s help tab on this page does not content OR a corresponding method for generating the content.  Check the spelling or make sure the method is present.',
1368
+								'event_espresso'
1369
+							),
1370
+							$cfg['title']
1371
+						),
1372
+						__FILE__,
1373
+						__FUNCTION__,
1374
+						__LINE__
1375
+					);
1376
+					return;
1377
+				}
1378
+				// setup config array for help tab method
1379
+				$id  = $this->page_slug . '-' . $this->_req_action . '-' . $tab_id;
1380
+				$_ht = [
1381
+					'id'       => $id,
1382
+					'title'    => $cfg['title'],
1383
+					'callback' => isset($cfg['callback']) && empty($content) ? [$this, $cfg['callback']] : null,
1384
+					'content'  => $content,
1385
+				];
1386
+				$this->_current_screen->add_help_tab($_ht);
1387
+			}
1388
+		}
1389
+	}
1390
+
1391
+
1392
+	/**
1393
+	 * This simply sets up any qtips that have been defined in the page config
1394
+	 *
1395
+	 * @return void
1396
+	 * @throws ReflectionException
1397
+	 * @throws EE_Error
1398
+	 */
1399
+	protected function _add_qtips()
1400
+	{
1401
+		if (isset($this->_route_config['qtips'])) {
1402
+			$qtips = (array) $this->_route_config['qtips'];
1403
+			// load qtip loader
1404
+			$path = [
1405
+				$this->_get_dir() . '/qtips/',
1406
+				EE_ADMIN_PAGES . basename($this->_get_dir()) . '/qtips/',
1407
+			];
1408
+			EEH_Qtip_Loader::instance()->register($qtips, $path);
1409
+		}
1410
+	}
1411
+
1412
+
1413
+	/**
1414
+	 * _set_nav_tabs
1415
+	 * This sets up the nav tabs from the page_routes array.  This method can be overwritten by child classes if you
1416
+	 * wish to add additional tabs or modify accordingly.
1417
+	 *
1418
+	 * @return void
1419
+	 * @throws InvalidArgumentException
1420
+	 * @throws InvalidInterfaceException
1421
+	 * @throws InvalidDataTypeException
1422
+	 */
1423
+	protected function _set_nav_tabs()
1424
+	{
1425
+		$i        = 0;
1426
+		$only_tab = count($this->_page_config) < 2;
1427
+		foreach ($this->_page_config as $slug => $config) {
1428
+			if (! is_array($config) || empty($config['nav'])) {
1429
+				continue;
1430
+			}
1431
+			// no nav tab for this config
1432
+			// check for persistent flag
1433
+			if ($slug !== $this->_req_action && isset($config['nav']['persistent']) && ! $config['nav']['persistent']) {
1434
+				// nav tab is only to appear when route requested.
1435
+				continue;
1436
+			}
1437
+			if (! $this->check_user_access($slug, true)) {
1438
+				// no nav tab because current user does not have access.
1439
+				continue;
1440
+			}
1441
+			$css_class = $config['css_class'] ?? '';
1442
+			$css_class .= $only_tab ? ' ee-only-tab' : '';
1443
+			$css_class .= " ee-nav-tab__$slug";
1444
+
1445
+			$this->_nav_tabs[ $slug ] = [
1446
+				'url'       => $config['nav']['url'] ?? EE_Admin_Page::add_query_args_and_nonce(
1447
+						['action' => $slug],
1448
+						$this->_admin_base_url
1449
+					),
1450
+				'link_text' => $this->navTabLabel($config['nav'], $slug),
1451
+				'css_class' => $this->_req_action === $slug ? $css_class . ' nav-tab-active' : $css_class,
1452
+				'order'     => $config['nav']['order'] ?? $i,
1453
+			];
1454
+			$i++;
1455
+		}
1456
+		// if $this->_nav_tabs is empty then lets set the default
1457
+		if (empty($this->_nav_tabs)) {
1458
+			$this->_nav_tabs[ $this->_default_nav_tab_name ] = [
1459
+				'url'       => $this->_admin_base_url,
1460
+				'link_text' => ucwords(str_replace('_', ' ', $this->_default_nav_tab_name)),
1461
+				'css_class' => 'nav-tab-active',
1462
+				'order'     => 10,
1463
+			];
1464
+		}
1465
+		// now let's sort the tabs according to order
1466
+		usort($this->_nav_tabs, [$this, '_sort_nav_tabs']);
1467
+	}
1468
+
1469
+
1470
+	private function navTabLabel(array $nav_tab, string $slug): string
1471
+	{
1472
+		$label = $nav_tab['label'] ?? ucwords(str_replace('_', ' ', $slug));
1473
+		$icon  = $nav_tab['icon'] ?? null;
1474
+		$icon  = $icon ? '<span class="dashicons ' . $icon . '"></span>' : '';
1475
+		return '
1476 1476
             <span class="ee-admin-screen-tab__label">
1477 1477
                 ' . $icon . '
1478 1478
                 <span class="ee-nav-label__text">' . $label . '</span>
1479 1479
             </span>';
1480
-    }
1481
-
1482
-
1483
-    /**
1484
-     * _set_current_labels
1485
-     * This method modifies the _labels property with any optional specific labels indicated in the _page_routes
1486
-     * property array
1487
-     *
1488
-     * @return void
1489
-     */
1490
-    private function _set_current_labels()
1491
-    {
1492
-        if (isset($this->_route_config['labels'])) {
1493
-            foreach ($this->_route_config['labels'] as $label => $text) {
1494
-                if (is_array($text)) {
1495
-                    foreach ($text as $sublabel => $subtext) {
1496
-                        $this->_labels[ $label ][ $sublabel ] = $subtext;
1497
-                    }
1498
-                } else {
1499
-                    $this->_labels[ $label ] = $text;
1500
-                }
1501
-            }
1502
-        }
1503
-    }
1504
-
1505
-
1506
-    /**
1507
-     *        verifies user access for this admin page
1508
-     *
1509
-     * @param string $route_to_check if present then the capability for the route matching this string is checked.
1510
-     * @param bool   $verify_only    Default is FALSE which means if user check fails then wp_die().  Otherwise just
1511
-     *                               return false if verify fail.
1512
-     * @return bool
1513
-     * @throws InvalidArgumentException
1514
-     * @throws InvalidDataTypeException
1515
-     * @throws InvalidInterfaceException
1516
-     */
1517
-    public function check_user_access(string $route_to_check = '', bool $verify_only = false): bool
1518
-    {
1519
-        // if no route_to_check is passed in then use the current route set via _req_action
1520
-        $action = $route_to_check ?: $this->_req_action;
1521
-        $capability = ! empty($this->_page_routes[ $action ]['capability'])
1522
-            ? $this->_page_routes[ $action ]['capability']
1523
-            : null;
1524
-
1525
-        if (empty($capability)) {
1526
-            $capability = empty($route_to_check) && ! empty($this->_route['capability'])
1527
-                ? $this->_route['capability']
1528
-                : 'manage_options';
1529
-        }
1530
-
1531
-        $id = $this->_route['obj_id'] ?? 0;
1532
-
1533
-        if (
1534
-            ! $this->request->isAjax()
1535
-            && (
1536
-                ! function_exists('is_admin')
1537
-                || ! $this->capabilities->current_user_can($capability, "{$this->page_slug}_$route_to_check", $id)
1538
-            )
1539
-        ) {
1540
-            if ($verify_only) {
1541
-                return false;
1542
-            }
1543
-            if (is_user_logged_in()) {
1544
-                wp_die(esc_html__('You do not have access to this route.', 'event_espresso'));
1545
-            }
1546
-            return false;
1547
-        }
1548
-        return true;
1549
-    }
1550
-
1551
-
1552
-    /**
1553
-     * @param string                 $box_id
1554
-     * @param string                 $title
1555
-     * @param callable|string|null   $callback
1556
-     * @param string|array|WP_Screen $screen
1557
-     * @param string                 $context       Post edit screen contexts include 'normal', 'side', and 'advanced'.
1558
-     *                                              Comments screen contexts include 'normal' and 'side'.
1559
-     *                                              Menus meta boxes (accordion sections) all use the 'side' context.
1560
-     * @param string                 $priority      Accepts 'high', 'core', 'default', or 'low'.
1561
-     * @param array|null             $callback_args
1562
-     */
1563
-    protected function addMetaBox(
1564
-        string $box_id,
1565
-        string $title,
1566
-        $callback,
1567
-        $screen,
1568
-        string $context = 'normal',
1569
-        string $priority = 'default',
1570
-        ?array $callback_args = null
1571
-    ) {
1572
-        if (! (is_callable($callback) || ! function_exists($callback))) {
1573
-            return;
1574
-        }
1575
-
1576
-        add_meta_box($box_id, $title, $callback, $screen, $context, $priority, $callback_args);
1577
-        add_filter(
1578
-            "postbox_classes_{$this->_wp_page_slug}_$box_id",
1579
-            function ($classes) {
1580
-                $classes[] = 'ee-admin-container';
1581
-                return $classes;
1582
-            }
1583
-        );
1584
-    }
1585
-
1586
-
1587
-    /**
1588
-     * admin_init_global
1589
-     * This runs all the code that we want executed within the WP admin_init hook.
1590
-     * This method executes for ALL EE Admin pages.
1591
-     *
1592
-     * @return void
1593
-     */
1594
-    public function admin_init_global()
1595
-    {
1596
-    }
1597
-
1598
-
1599
-    /**
1600
-     * wp_loaded_global
1601
-     * This runs all the code that we want executed within the WP wp_loaded hook.  This method is optional for an
1602
-     * EE_Admin page and will execute on every EE Admin Page load
1603
-     *
1604
-     * @return void
1605
-     */
1606
-    public function wp_loaded()
1607
-    {
1608
-    }
1609
-
1610
-
1611
-    /**
1612
-     * admin_notices
1613
-     * Anything triggered by the 'admin_notices' WP hook should be put in here.  This particular method will apply on
1614
-     * ALL EE_Admin pages.
1615
-     *
1616
-     * @return void
1617
-     */
1618
-    public function admin_notices_global()
1619
-    {
1620
-        $this->_display_no_javascript_warning();
1621
-        $this->_display_espresso_notices();
1622
-    }
1623
-
1624
-
1625
-    public function network_admin_notices_global()
1626
-    {
1627
-        $this->_display_no_javascript_warning();
1628
-        $this->_display_espresso_notices();
1629
-    }
1630
-
1631
-
1632
-    /**
1633
-     * admin_footer_scripts_global
1634
-     * Anything triggered by the 'admin_print_footer_scripts' WP hook should be put in here. This particular method
1635
-     * will apply on ALL EE_Admin pages.
1636
-     *
1637
-     * @return void
1638
-     */
1639
-    public function admin_footer_scripts_global()
1640
-    {
1641
-        $this->_add_admin_page_ajax_loading_img();
1642
-        $this->_add_admin_page_overlay();
1643
-        // if metaboxes are present we need to add the nonce field
1644
-        if (
1645
-            isset($this->_route_config['metaboxes'])
1646
-            || isset($this->_route_config['list_table'])
1647
-            || (isset($this->_route_config['has_metaboxes']) && $this->_route_config['has_metaboxes'])
1648
-        ) {
1649
-            wp_nonce_field('closedpostboxes', 'closedpostboxesnonce', false);
1650
-            wp_nonce_field('meta-box-order', 'meta-box-order-nonce', false);
1651
-        }
1652
-    }
1653
-
1654
-
1655
-    /**
1656
-     * admin_footer_global
1657
-     * Anything triggered by the wp 'admin_footer' wp hook should be put in here.
1658
-     * This particular method will apply on ALL EE_Admin Pages.
1659
-     *
1660
-     * @return void
1661
-     */
1662
-    public function admin_footer_global()
1663
-    {
1664
-        // dialog container for dialog helper
1665
-        echo '
1480
+	}
1481
+
1482
+
1483
+	/**
1484
+	 * _set_current_labels
1485
+	 * This method modifies the _labels property with any optional specific labels indicated in the _page_routes
1486
+	 * property array
1487
+	 *
1488
+	 * @return void
1489
+	 */
1490
+	private function _set_current_labels()
1491
+	{
1492
+		if (isset($this->_route_config['labels'])) {
1493
+			foreach ($this->_route_config['labels'] as $label => $text) {
1494
+				if (is_array($text)) {
1495
+					foreach ($text as $sublabel => $subtext) {
1496
+						$this->_labels[ $label ][ $sublabel ] = $subtext;
1497
+					}
1498
+				} else {
1499
+					$this->_labels[ $label ] = $text;
1500
+				}
1501
+			}
1502
+		}
1503
+	}
1504
+
1505
+
1506
+	/**
1507
+	 *        verifies user access for this admin page
1508
+	 *
1509
+	 * @param string $route_to_check if present then the capability for the route matching this string is checked.
1510
+	 * @param bool   $verify_only    Default is FALSE which means if user check fails then wp_die().  Otherwise just
1511
+	 *                               return false if verify fail.
1512
+	 * @return bool
1513
+	 * @throws InvalidArgumentException
1514
+	 * @throws InvalidDataTypeException
1515
+	 * @throws InvalidInterfaceException
1516
+	 */
1517
+	public function check_user_access(string $route_to_check = '', bool $verify_only = false): bool
1518
+	{
1519
+		// if no route_to_check is passed in then use the current route set via _req_action
1520
+		$action = $route_to_check ?: $this->_req_action;
1521
+		$capability = ! empty($this->_page_routes[ $action ]['capability'])
1522
+			? $this->_page_routes[ $action ]['capability']
1523
+			: null;
1524
+
1525
+		if (empty($capability)) {
1526
+			$capability = empty($route_to_check) && ! empty($this->_route['capability'])
1527
+				? $this->_route['capability']
1528
+				: 'manage_options';
1529
+		}
1530
+
1531
+		$id = $this->_route['obj_id'] ?? 0;
1532
+
1533
+		if (
1534
+			! $this->request->isAjax()
1535
+			&& (
1536
+				! function_exists('is_admin')
1537
+				|| ! $this->capabilities->current_user_can($capability, "{$this->page_slug}_$route_to_check", $id)
1538
+			)
1539
+		) {
1540
+			if ($verify_only) {
1541
+				return false;
1542
+			}
1543
+			if (is_user_logged_in()) {
1544
+				wp_die(esc_html__('You do not have access to this route.', 'event_espresso'));
1545
+			}
1546
+			return false;
1547
+		}
1548
+		return true;
1549
+	}
1550
+
1551
+
1552
+	/**
1553
+	 * @param string                 $box_id
1554
+	 * @param string                 $title
1555
+	 * @param callable|string|null   $callback
1556
+	 * @param string|array|WP_Screen $screen
1557
+	 * @param string                 $context       Post edit screen contexts include 'normal', 'side', and 'advanced'.
1558
+	 *                                              Comments screen contexts include 'normal' and 'side'.
1559
+	 *                                              Menus meta boxes (accordion sections) all use the 'side' context.
1560
+	 * @param string                 $priority      Accepts 'high', 'core', 'default', or 'low'.
1561
+	 * @param array|null             $callback_args
1562
+	 */
1563
+	protected function addMetaBox(
1564
+		string $box_id,
1565
+		string $title,
1566
+		$callback,
1567
+		$screen,
1568
+		string $context = 'normal',
1569
+		string $priority = 'default',
1570
+		?array $callback_args = null
1571
+	) {
1572
+		if (! (is_callable($callback) || ! function_exists($callback))) {
1573
+			return;
1574
+		}
1575
+
1576
+		add_meta_box($box_id, $title, $callback, $screen, $context, $priority, $callback_args);
1577
+		add_filter(
1578
+			"postbox_classes_{$this->_wp_page_slug}_$box_id",
1579
+			function ($classes) {
1580
+				$classes[] = 'ee-admin-container';
1581
+				return $classes;
1582
+			}
1583
+		);
1584
+	}
1585
+
1586
+
1587
+	/**
1588
+	 * admin_init_global
1589
+	 * This runs all the code that we want executed within the WP admin_init hook.
1590
+	 * This method executes for ALL EE Admin pages.
1591
+	 *
1592
+	 * @return void
1593
+	 */
1594
+	public function admin_init_global()
1595
+	{
1596
+	}
1597
+
1598
+
1599
+	/**
1600
+	 * wp_loaded_global
1601
+	 * This runs all the code that we want executed within the WP wp_loaded hook.  This method is optional for an
1602
+	 * EE_Admin page and will execute on every EE Admin Page load
1603
+	 *
1604
+	 * @return void
1605
+	 */
1606
+	public function wp_loaded()
1607
+	{
1608
+	}
1609
+
1610
+
1611
+	/**
1612
+	 * admin_notices
1613
+	 * Anything triggered by the 'admin_notices' WP hook should be put in here.  This particular method will apply on
1614
+	 * ALL EE_Admin pages.
1615
+	 *
1616
+	 * @return void
1617
+	 */
1618
+	public function admin_notices_global()
1619
+	{
1620
+		$this->_display_no_javascript_warning();
1621
+		$this->_display_espresso_notices();
1622
+	}
1623
+
1624
+
1625
+	public function network_admin_notices_global()
1626
+	{
1627
+		$this->_display_no_javascript_warning();
1628
+		$this->_display_espresso_notices();
1629
+	}
1630
+
1631
+
1632
+	/**
1633
+	 * admin_footer_scripts_global
1634
+	 * Anything triggered by the 'admin_print_footer_scripts' WP hook should be put in here. This particular method
1635
+	 * will apply on ALL EE_Admin pages.
1636
+	 *
1637
+	 * @return void
1638
+	 */
1639
+	public function admin_footer_scripts_global()
1640
+	{
1641
+		$this->_add_admin_page_ajax_loading_img();
1642
+		$this->_add_admin_page_overlay();
1643
+		// if metaboxes are present we need to add the nonce field
1644
+		if (
1645
+			isset($this->_route_config['metaboxes'])
1646
+			|| isset($this->_route_config['list_table'])
1647
+			|| (isset($this->_route_config['has_metaboxes']) && $this->_route_config['has_metaboxes'])
1648
+		) {
1649
+			wp_nonce_field('closedpostboxes', 'closedpostboxesnonce', false);
1650
+			wp_nonce_field('meta-box-order', 'meta-box-order-nonce', false);
1651
+		}
1652
+	}
1653
+
1654
+
1655
+	/**
1656
+	 * admin_footer_global
1657
+	 * Anything triggered by the wp 'admin_footer' wp hook should be put in here.
1658
+	 * This particular method will apply on ALL EE_Admin Pages.
1659
+	 *
1660
+	 * @return void
1661
+	 */
1662
+	public function admin_footer_global()
1663
+	{
1664
+		// dialog container for dialog helper
1665
+		echo '
1666 1666
         <div class="ee-admin-dialog-container auto-hide hidden">
1667 1667
             <div class="ee-notices"></div>
1668 1668
             <div class="ee-admin-dialog-container-inner-content"></div>
1669 1669
         </div>
1670 1670
         <span id="current_timezone" class="hidden">' . esc_html(EEH_DTT_Helper::get_timezone()) . '</span>
1671 1671
         <input type="hidden" id="espresso_admin_current_page" value="' . esc_attr($this->_current_page) . '"/>';
1672
-    }
1673
-
1674
-
1675
-    /**
1676
-     * This function sees if there is a method for help popup content existing for the given route.  If there is then
1677
-     * we'll use the retrieved array to output the content using the template. For child classes: If you want to have
1678
-     * help popups then in your templates or your content you set "triggers" for the content using the
1679
-     * "_set_help_trigger('help_trigger_id')" where "help_trigger_id" is what you will use later in your custom method
1680
-     * for the help popup content on that page. Then in your Child_Admin_Page class you need to define a help popup
1681
-     * method for the content in the format "_help_popup_content_{route_name}()"  So if you are setting help content
1682
-     * for the
1683
-     * 'edit_event' route you should have a method named "_help_popup_content_edit_route". In your defined
1684
-     * "help_popup_content_..." method.  You must prepare and return an array in the following format array(
1685
-     *    'help_trigger_id' => array(
1686
-     *        'title' => esc_html__('localized title for popup', 'event_espresso'),
1687
-     *        'content' => esc_html__('localized content for popup', 'event_espresso')
1688
-     *    )
1689
-     * );
1690
-     * Then the EE_Admin_Parent will take care of making sure that is set up properly on the correct route.
1691
-     *
1692
-     * @param array $help_array
1693
-     * @param bool  $display
1694
-     * @return string content
1695
-     * @throws DomainException
1696
-     * @throws EE_Error
1697
-     */
1698
-    protected function _set_help_popup_content(array $help_array = [], bool $display = false): string
1699
-    {
1700
-        $content    = '';
1701
-        $help_array = empty($help_array) ? $this->_get_help_content() : $help_array;
1702
-        // loop through the array and setup content
1703
-        foreach ($help_array as $trigger => $help) {
1704
-            // make sure the array is set up properly
1705
-            if (! isset($help['title'], $help['content'])) {
1706
-                throw new EE_Error(
1707
-                    esc_html__(
1708
-                        'Does not look like the popup content array has been setup correctly.  Might want to double check that.  Read the comments for the _get_help_popup_content method found in "EE_Admin_Page" class',
1709
-                        'event_espresso'
1710
-                    )
1711
-                );
1712
-            }
1713
-            // we're good so let's set up the template vars and then assign parsed template content to our content.
1714
-            $template_args = [
1715
-                'help_popup_id'      => $trigger,
1716
-                'help_popup_title'   => $help['title'],
1717
-                'help_popup_content' => $help['content'],
1718
-            ];
1719
-            $content       .= EEH_Template::display_template(
1720
-                EE_ADMIN_TEMPLATE . 'admin_help_popup.template.php',
1721
-                $template_args,
1722
-                true
1723
-            );
1724
-        }
1725
-        if ($display) {
1726
-            echo wp_kses($content, AllowedTags::getWithFormTags());
1727
-            return '';
1728
-        }
1729
-        return $content;
1730
-    }
1731
-
1732
-
1733
-    /**
1734
-     * All this does is retrieve the help content array if set by the EE_Admin_Page child
1735
-     *
1736
-     * @return array properly formatted array for help popup content
1737
-     * @throws EE_Error
1738
-     */
1739
-    private function _get_help_content(): array
1740
-    {
1741
-        // what is the method we're looking for?
1742
-        $method_name = '_help_popup_content_' . $this->_req_action;
1743
-        // if method doesn't exist let's get out.
1744
-        if (! method_exists($this, $method_name)) {
1745
-            return [];
1746
-        }
1747
-        // k we're good to go let's retrieve the help array
1748
-        $help_array = $this->{$method_name}();
1749
-        // make sure we've got an array!
1750
-        if (! is_array($help_array)) {
1751
-            throw new EE_Error(
1752
-                esc_html__(
1753
-                    'Something went wrong with help popup content generation. Expecting an array and well, this ain\'t no array bub.',
1754
-                    'event_espresso'
1755
-                )
1756
-            );
1757
-        }
1758
-        return $help_array;
1759
-    }
1760
-
1761
-
1762
-    /**
1763
-     * EE Admin Pages can use this to set a properly formatted trigger for a help popup.
1764
-     * By default the trigger html is printed.  Otherwise it can be returned if the $display flag is set "false"
1765
-     * See comments made on the _set_help_content method for understanding other parts to the help popup tool.
1766
-     *
1767
-     * @param string $trigger_id reference for retrieving the trigger content for the popup
1768
-     * @param bool   $display    if false then we return the trigger string
1769
-     * @param array  $dimensions an array of dimensions for the box (array(h,w))
1770
-     * @return string
1771
-     * @throws DomainException
1772
-     * @throws EE_Error
1773
-     */
1774
-    protected function _set_help_trigger(string $trigger_id, bool $display = true, array $dimensions = ['400', '640'])
1775
-    {
1776
-        if ($this->request->isAjax()) {
1777
-            return '';
1778
-        }
1779
-        // let's check and see if there is any content set for this popup.  If there isn't then we'll include a default title and content so that developers know something needs to be corrected
1780
-        $help_array   = $this->_get_help_content();
1781
-        $help_content = '';
1782
-        if (empty($help_array) || ! isset($help_array[ $trigger_id ])) {
1783
-            $help_array[ $trigger_id ] = [
1784
-                'title'   => esc_html__('Missing Content', 'event_espresso'),
1785
-                'content' => esc_html__(
1786
-                    'A trigger has been set that doesn\'t have any corresponding content. Make sure you have set the help content. (see the "_set_help_popup_content" method in the EE_Admin_Page for instructions.)',
1787
-                    'event_espresso'
1788
-                ),
1789
-            ];
1790
-            $help_content              = $this->_set_help_popup_content($help_array);
1791
-        }
1792
-        $height   = esc_attr($dimensions[0]) ?? 400;
1793
-        $width    = esc_attr($dimensions[1]) ?? 640;
1794
-        $inlineId = esc_attr($trigger_id);
1795
-        // let's setup the trigger
1796
-        $content = "
1672
+	}
1673
+
1674
+
1675
+	/**
1676
+	 * This function sees if there is a method for help popup content existing for the given route.  If there is then
1677
+	 * we'll use the retrieved array to output the content using the template. For child classes: If you want to have
1678
+	 * help popups then in your templates or your content you set "triggers" for the content using the
1679
+	 * "_set_help_trigger('help_trigger_id')" where "help_trigger_id" is what you will use later in your custom method
1680
+	 * for the help popup content on that page. Then in your Child_Admin_Page class you need to define a help popup
1681
+	 * method for the content in the format "_help_popup_content_{route_name}()"  So if you are setting help content
1682
+	 * for the
1683
+	 * 'edit_event' route you should have a method named "_help_popup_content_edit_route". In your defined
1684
+	 * "help_popup_content_..." method.  You must prepare and return an array in the following format array(
1685
+	 *    'help_trigger_id' => array(
1686
+	 *        'title' => esc_html__('localized title for popup', 'event_espresso'),
1687
+	 *        'content' => esc_html__('localized content for popup', 'event_espresso')
1688
+	 *    )
1689
+	 * );
1690
+	 * Then the EE_Admin_Parent will take care of making sure that is set up properly on the correct route.
1691
+	 *
1692
+	 * @param array $help_array
1693
+	 * @param bool  $display
1694
+	 * @return string content
1695
+	 * @throws DomainException
1696
+	 * @throws EE_Error
1697
+	 */
1698
+	protected function _set_help_popup_content(array $help_array = [], bool $display = false): string
1699
+	{
1700
+		$content    = '';
1701
+		$help_array = empty($help_array) ? $this->_get_help_content() : $help_array;
1702
+		// loop through the array and setup content
1703
+		foreach ($help_array as $trigger => $help) {
1704
+			// make sure the array is set up properly
1705
+			if (! isset($help['title'], $help['content'])) {
1706
+				throw new EE_Error(
1707
+					esc_html__(
1708
+						'Does not look like the popup content array has been setup correctly.  Might want to double check that.  Read the comments for the _get_help_popup_content method found in "EE_Admin_Page" class',
1709
+						'event_espresso'
1710
+					)
1711
+				);
1712
+			}
1713
+			// we're good so let's set up the template vars and then assign parsed template content to our content.
1714
+			$template_args = [
1715
+				'help_popup_id'      => $trigger,
1716
+				'help_popup_title'   => $help['title'],
1717
+				'help_popup_content' => $help['content'],
1718
+			];
1719
+			$content       .= EEH_Template::display_template(
1720
+				EE_ADMIN_TEMPLATE . 'admin_help_popup.template.php',
1721
+				$template_args,
1722
+				true
1723
+			);
1724
+		}
1725
+		if ($display) {
1726
+			echo wp_kses($content, AllowedTags::getWithFormTags());
1727
+			return '';
1728
+		}
1729
+		return $content;
1730
+	}
1731
+
1732
+
1733
+	/**
1734
+	 * All this does is retrieve the help content array if set by the EE_Admin_Page child
1735
+	 *
1736
+	 * @return array properly formatted array for help popup content
1737
+	 * @throws EE_Error
1738
+	 */
1739
+	private function _get_help_content(): array
1740
+	{
1741
+		// what is the method we're looking for?
1742
+		$method_name = '_help_popup_content_' . $this->_req_action;
1743
+		// if method doesn't exist let's get out.
1744
+		if (! method_exists($this, $method_name)) {
1745
+			return [];
1746
+		}
1747
+		// k we're good to go let's retrieve the help array
1748
+		$help_array = $this->{$method_name}();
1749
+		// make sure we've got an array!
1750
+		if (! is_array($help_array)) {
1751
+			throw new EE_Error(
1752
+				esc_html__(
1753
+					'Something went wrong with help popup content generation. Expecting an array and well, this ain\'t no array bub.',
1754
+					'event_espresso'
1755
+				)
1756
+			);
1757
+		}
1758
+		return $help_array;
1759
+	}
1760
+
1761
+
1762
+	/**
1763
+	 * EE Admin Pages can use this to set a properly formatted trigger for a help popup.
1764
+	 * By default the trigger html is printed.  Otherwise it can be returned if the $display flag is set "false"
1765
+	 * See comments made on the _set_help_content method for understanding other parts to the help popup tool.
1766
+	 *
1767
+	 * @param string $trigger_id reference for retrieving the trigger content for the popup
1768
+	 * @param bool   $display    if false then we return the trigger string
1769
+	 * @param array  $dimensions an array of dimensions for the box (array(h,w))
1770
+	 * @return string
1771
+	 * @throws DomainException
1772
+	 * @throws EE_Error
1773
+	 */
1774
+	protected function _set_help_trigger(string $trigger_id, bool $display = true, array $dimensions = ['400', '640'])
1775
+	{
1776
+		if ($this->request->isAjax()) {
1777
+			return '';
1778
+		}
1779
+		// let's check and see if there is any content set for this popup.  If there isn't then we'll include a default title and content so that developers know something needs to be corrected
1780
+		$help_array   = $this->_get_help_content();
1781
+		$help_content = '';
1782
+		if (empty($help_array) || ! isset($help_array[ $trigger_id ])) {
1783
+			$help_array[ $trigger_id ] = [
1784
+				'title'   => esc_html__('Missing Content', 'event_espresso'),
1785
+				'content' => esc_html__(
1786
+					'A trigger has been set that doesn\'t have any corresponding content. Make sure you have set the help content. (see the "_set_help_popup_content" method in the EE_Admin_Page for instructions.)',
1787
+					'event_espresso'
1788
+				),
1789
+			];
1790
+			$help_content              = $this->_set_help_popup_content($help_array);
1791
+		}
1792
+		$height   = esc_attr($dimensions[0]) ?? 400;
1793
+		$width    = esc_attr($dimensions[1]) ?? 640;
1794
+		$inlineId = esc_attr($trigger_id);
1795
+		// let's setup the trigger
1796
+		$content = "
1797 1797
         <a class='ee-dialog' href='?height=$height&width=$width&inlineId=$inlineId' target='_blank'>
1798 1798
             <span class='question ee-help-popup-question'></span>
1799 1799
         </a>";
1800
-        $content .= $help_content;
1801
-        if ($display) {
1802
-            echo wp_kses($content, AllowedTags::getWithFormTags());
1803
-            return '';
1804
-        }
1805
-        return $content;
1806
-    }
1807
-
1808
-
1809
-    /**
1810
-     * _add_global_screen_options
1811
-     * Add any extra wp_screen_options within this method using built-in WP functions/methods for doing so.
1812
-     * This particular method will add_screen_options on ALL EE_Admin Pages
1813
-     *
1814
-     * @link   http://chrismarslender.com/wp-tutorials/wordpress-screen-options-tutorial/
1815
-     *         see also WP_Screen object documents...
1816
-     * @link   http://codex.wordpress.org/Class_Reference/WP_Screen
1817
-     * @abstract
1818
-     * @return void
1819
-     */
1820
-    private function _add_global_screen_options()
1821
-    {
1822
-    }
1823
-
1824
-
1825
-    /**
1826
-     * _add_global_feature_pointers
1827
-     * This method is used for implementing any "feature pointers" (using built-in WP styling js).
1828
-     * This particular method will implement feature pointers for ALL EE_Admin pages.
1829
-     * Note: this is just a placeholder for now.  Implementation will come down the road
1830
-     *
1831
-     * @see    WP_Internal_Pointers class in wp-admin/includes/template.php for example (it's a final class so can't be
1832
-     *         extended) also see:
1833
-     * @link   http://eamann.com/tech/wordpress-portland/
1834
-     * @abstract
1835
-     * @return void
1836
-     */
1837
-    private function _add_global_feature_pointers()
1838
-    {
1839
-    }
1840
-
1841
-
1842
-    /**
1843
-     * load_global_scripts_styles
1844
-     * The scripts and styles enqueued in here will be loaded on every EE Admin page
1845
-     *
1846
-     * @return void
1847
-     */
1848
-    public function load_global_scripts_styles()
1849
-    {
1850
-        // add debugging styles
1851
-        if (WP_DEBUG) {
1852
-            add_action('admin_head', [$this, 'add_xdebug_style']);
1853
-        }
1854
-        // taking care of metaboxes
1855
-        if (
1856
-            empty($this->_cpt_route)
1857
-            && (isset($this->_route_config['metaboxes']) || isset($this->_route_config['has_metaboxes']))
1858
-        ) {
1859
-            wp_enqueue_script('dashboard');
1860
-        }
1861
-
1862
-        wp_enqueue_script(JqueryAssetManager::JS_HANDLE_JQUERY_UI_TOUCH_PUNCH);
1863
-        wp_enqueue_script(EspressoLegacyAdminAssetManager::JS_HANDLE_EE_ADMIN);
1864
-        // LOCALIZED DATA
1865
-        // localize script for ajax lazy loading
1866
-        wp_localize_script(
1867
-            EspressoLegacyAdminAssetManager::JS_HANDLE_EE_ADMIN,
1868
-            'eeLazyLoadingContainers',
1869
-            apply_filters(
1870
-                'FHEE__EE_Admin_Page_Core__load_global_scripts_styles__loader_containers',
1871
-                ['espresso_news_post_box_content']
1872
-            )
1873
-        );
1874
-        StatusChangeNotice::loadAssets();
1875
-
1876
-        add_filter(
1877
-            'admin_body_class',
1878
-            function ($classes) {
1879
-                if (strpos($classes, 'espresso-admin') === false) {
1880
-                    $classes .= ' espresso-admin';
1881
-                }
1882
-                return $classes;
1883
-            }
1884
-        );
1885
-    }
1886
-
1887
-
1888
-    /**
1889
-     *        admin_footer_scripts_eei18n_js_strings
1890
-     *
1891
-     * @return        void
1892
-     */
1893
-    public function admin_footer_scripts_eei18n_js_strings()
1894
-    {
1895
-        EE_Registry::$i18n_js_strings['ajax_url']       = WP_AJAX_URL;
1896
-        EE_Registry::$i18n_js_strings['confirm_delete'] = wp_strip_all_tags(
1897
-            __(
1898
-                'Are you absolutely sure you want to delete this item?\nThis action will delete ALL DATA associated with this item!!!\nThis can NOT be undone!!!',
1899
-                'event_espresso'
1900
-            )
1901
-        );
1902
-        EE_Registry::$i18n_js_strings['January']        = wp_strip_all_tags(__('January', 'event_espresso'));
1903
-        EE_Registry::$i18n_js_strings['February']       = wp_strip_all_tags(__('February', 'event_espresso'));
1904
-        EE_Registry::$i18n_js_strings['March']          = wp_strip_all_tags(__('March', 'event_espresso'));
1905
-        EE_Registry::$i18n_js_strings['April']          = wp_strip_all_tags(__('April', 'event_espresso'));
1906
-        EE_Registry::$i18n_js_strings['May']            = wp_strip_all_tags(__('May', 'event_espresso'));
1907
-        EE_Registry::$i18n_js_strings['June']           = wp_strip_all_tags(__('June', 'event_espresso'));
1908
-        EE_Registry::$i18n_js_strings['July']           = wp_strip_all_tags(__('July', 'event_espresso'));
1909
-        EE_Registry::$i18n_js_strings['August']         = wp_strip_all_tags(__('August', 'event_espresso'));
1910
-        EE_Registry::$i18n_js_strings['September']      = wp_strip_all_tags(__('September', 'event_espresso'));
1911
-        EE_Registry::$i18n_js_strings['October']        = wp_strip_all_tags(__('October', 'event_espresso'));
1912
-        EE_Registry::$i18n_js_strings['November']       = wp_strip_all_tags(__('November', 'event_espresso'));
1913
-        EE_Registry::$i18n_js_strings['December']       = wp_strip_all_tags(__('December', 'event_espresso'));
1914
-        EE_Registry::$i18n_js_strings['Jan']            = wp_strip_all_tags(__('Jan', 'event_espresso'));
1915
-        EE_Registry::$i18n_js_strings['Feb']            = wp_strip_all_tags(__('Feb', 'event_espresso'));
1916
-        EE_Registry::$i18n_js_strings['Mar']            = wp_strip_all_tags(__('Mar', 'event_espresso'));
1917
-        EE_Registry::$i18n_js_strings['Apr']            = wp_strip_all_tags(__('Apr', 'event_espresso'));
1918
-        EE_Registry::$i18n_js_strings['May']            = wp_strip_all_tags(__('May', 'event_espresso'));
1919
-        EE_Registry::$i18n_js_strings['Jun']            = wp_strip_all_tags(__('Jun', 'event_espresso'));
1920
-        EE_Registry::$i18n_js_strings['Jul']            = wp_strip_all_tags(__('Jul', 'event_espresso'));
1921
-        EE_Registry::$i18n_js_strings['Aug']            = wp_strip_all_tags(__('Aug', 'event_espresso'));
1922
-        EE_Registry::$i18n_js_strings['Sep']            = wp_strip_all_tags(__('Sep', 'event_espresso'));
1923
-        EE_Registry::$i18n_js_strings['Oct']            = wp_strip_all_tags(__('Oct', 'event_espresso'));
1924
-        EE_Registry::$i18n_js_strings['Nov']            = wp_strip_all_tags(__('Nov', 'event_espresso'));
1925
-        EE_Registry::$i18n_js_strings['Dec']            = wp_strip_all_tags(__('Dec', 'event_espresso'));
1926
-        EE_Registry::$i18n_js_strings['Sunday']         = wp_strip_all_tags(__('Sunday', 'event_espresso'));
1927
-        EE_Registry::$i18n_js_strings['Monday']         = wp_strip_all_tags(__('Monday', 'event_espresso'));
1928
-        EE_Registry::$i18n_js_strings['Tuesday']        = wp_strip_all_tags(__('Tuesday', 'event_espresso'));
1929
-        EE_Registry::$i18n_js_strings['Wednesday']      = wp_strip_all_tags(__('Wednesday', 'event_espresso'));
1930
-        EE_Registry::$i18n_js_strings['Thursday']       = wp_strip_all_tags(__('Thursday', 'event_espresso'));
1931
-        EE_Registry::$i18n_js_strings['Friday']         = wp_strip_all_tags(__('Friday', 'event_espresso'));
1932
-        EE_Registry::$i18n_js_strings['Saturday']       = wp_strip_all_tags(__('Saturday', 'event_espresso'));
1933
-        EE_Registry::$i18n_js_strings['Sun']            = wp_strip_all_tags(__('Sun', 'event_espresso'));
1934
-        EE_Registry::$i18n_js_strings['Mon']            = wp_strip_all_tags(__('Mon', 'event_espresso'));
1935
-        EE_Registry::$i18n_js_strings['Tue']            = wp_strip_all_tags(__('Tue', 'event_espresso'));
1936
-        EE_Registry::$i18n_js_strings['Wed']            = wp_strip_all_tags(__('Wed', 'event_espresso'));
1937
-        EE_Registry::$i18n_js_strings['Thu']            = wp_strip_all_tags(__('Thu', 'event_espresso'));
1938
-        EE_Registry::$i18n_js_strings['Fri']            = wp_strip_all_tags(__('Fri', 'event_espresso'));
1939
-        EE_Registry::$i18n_js_strings['Sat']            = wp_strip_all_tags(__('Sat', 'event_espresso'));
1940
-    }
1941
-
1942
-
1943
-    /**
1944
-     *        load enhanced xdebug styles for ppl with failing eyesight
1945
-     *
1946
-     * @return        void
1947
-     */
1948
-    public function add_xdebug_style()
1949
-    {
1950
-        echo '<style>.xdebug-error { font-size:1.5em; }</style>';
1951
-    }
1952
-
1953
-
1954
-    /************************/
1955
-    /** LIST TABLE METHODS **/
1956
-    /************************/
1957
-    /**
1958
-     * this sets up the list table if the current view requires it.
1959
-     *
1960
-     * @return void
1961
-     * @throws EE_Error
1962
-     * @throws InvalidArgumentException
1963
-     * @throws InvalidDataTypeException
1964
-     * @throws InvalidInterfaceException
1965
-     */
1966
-    protected function _set_list_table()
1967
-    {
1968
-        // first is this a list_table view?
1969
-        if (! isset($this->_route_config['list_table'])) {
1970
-            return;
1971
-        } //not a list_table view so get out.
1972
-        // list table functions are per view specific (because some admin pages might have more than one list table!)
1973
-        $list_table_view = '_set_list_table_views_' . $this->_req_action;
1974
-        if (! method_exists($this, $list_table_view) || $this->{$list_table_view}() === false) {
1975
-            // user error msg
1976
-            $error_msg = esc_html__(
1977
-                'An error occurred. The requested list table views could not be found.',
1978
-                'event_espresso'
1979
-            );
1980
-            // developer error msg
1981
-            $error_msg .= '||'
1982
-                          . sprintf(
1983
-                              esc_html__(
1984
-                                  'List table views for "%s" route could not be setup. Check that you have the corresponding method, "%s" set up for defining list_table_views for this route.',
1985
-                                  'event_espresso'
1986
-                              ),
1987
-                              $this->_req_action,
1988
-                              $list_table_view
1989
-                          );
1990
-            throw new EE_Error($error_msg);
1991
-        }
1992
-        // let's provide the ability to filter the views per PAGE AND ROUTE, per PAGE, and globally
1993
-        $this->_views = apply_filters(
1994
-            'FHEE_list_table_views_' . $this->page_slug . '_' . $this->_req_action,
1995
-            $this->_views
1996
-        );
1997
-        $this->_views = apply_filters('FHEE_list_table_views_' . $this->page_slug, $this->_views);
1998
-        $this->_views = apply_filters('FHEE_list_table_views', $this->_views);
1999
-        $this->_set_list_table_view();
2000
-        $this->_set_list_table_object();
2001
-    }
2002
-
2003
-
2004
-    /**
2005
-     * set current view for List Table
2006
-     *
2007
-     * @return void
2008
-     */
2009
-    protected function _set_list_table_view()
2010
-    {
2011
-        $this->_view = isset($this->_views['in_use']) ? 'in_use' : 'all';
2012
-        $status      = $this->request->getRequestParam('status', null, DataType::KEY);
2013
-        $this->_view = $status && array_key_exists($status, $this->_views)
2014
-            ? $status
2015
-            : $this->_view;
2016
-    }
2017
-
2018
-
2019
-    /**
2020
-     * _set_list_table_object
2021
-     * WP_List_Table objects need to be loaded fairly early so automatic stuff WP does is taken care of.
2022
-     *
2023
-     * @throws InvalidInterfaceException
2024
-     * @throws InvalidArgumentException
2025
-     * @throws InvalidDataTypeException
2026
-     * @throws EE_Error
2027
-     * @throws InvalidInterfaceException
2028
-     */
2029
-    protected function _set_list_table_object()
2030
-    {
2031
-        if (isset($this->_route_config['list_table'])) {
2032
-            if (! class_exists($this->_route_config['list_table'])) {
2033
-                throw new EE_Error(
2034
-                    sprintf(
2035
-                        esc_html__(
2036
-                            'The %s class defined for the list table does not exist.  Please check the spelling of the class ref in the $_page_config property on %s.',
2037
-                            'event_espresso'
2038
-                        ),
2039
-                        $this->_route_config['list_table'],
2040
-                        $this->class_name
2041
-                    )
2042
-                );
2043
-            }
2044
-            $this->_list_table_object = $this->loader->getShared(
2045
-                $this->_route_config['list_table'],
2046
-                [
2047
-                    $this,
2048
-                    LoaderFactory::getShared(AdminListTableFilters::class),
2049
-                ]
2050
-            );
2051
-        }
2052
-    }
2053
-
2054
-
2055
-    /**
2056
-     * get_list_table_view_RLs - get it? View RL ?? VU-RL???  URL ??
2057
-     *
2058
-     * @param array $extra_query_args                     Optional. An array of extra query args to add to the generated
2059
-     *                                                    urls.  The array should be indexed by the view it is being
2060
-     *                                                    added to.
2061
-     * @return array
2062
-     */
2063
-    public function get_list_table_view_RLs(array $extra_query_args = []): array
2064
-    {
2065
-        $extra_query_args = apply_filters(
2066
-            'FHEE__EE_Admin_Page__get_list_table_view_RLs__extra_query_args',
2067
-            $extra_query_args,
2068
-            $this
2069
-        );
2070
-        // cycle thru views
2071
-        foreach ($this->_views as $key => $view) {
2072
-            $query_args = [];
2073
-            // check for current view
2074
-            $this->_views[ $key ]['class'] = $this->_view === $view['slug'] ? 'current' : '';
2075
-            $query_args['action']          = $this->_req_action;
2076
-            $action_nonce                  = "{$this->_req_action}_nonce";
2077
-            $query_args[ $action_nonce ]   = wp_create_nonce($action_nonce);
2078
-            $query_args['status']          = $view['slug'];
2079
-            // merge any other arguments sent in.
2080
-            if (isset($extra_query_args[ $view['slug'] ])) {
2081
-                $query_args = array_merge($query_args, $extra_query_args[ $view['slug'] ]);
2082
-            }
2083
-            $this->_views[ $key ]['url'] = EE_Admin_Page::add_query_args_and_nonce($query_args, $this->_admin_base_url);
2084
-        }
2085
-        return $this->_views;
2086
-    }
2087
-
2088
-
2089
-    /**
2090
-     * generates a dropdown box for selecting the number of visible rows in an admin page list table
2091
-     *
2092
-     * @param int $max_entries total number of rows in the table
2093
-     * @return string
2094
-     * @todo   : Note: ideally this should be added to the screen options dropdown as that would be consistent with how
2095
-     *                         WP does it.
2096
-     */
2097
-    protected function _entries_per_page_dropdown(int $max_entries = 0): string
2098
-    {
2099
-        $values   = [10, 25, 50, 100];
2100
-        $per_page = $this->request->getRequestParam('per_page', 10, DataType::INT);
2101
-        if ($max_entries) {
2102
-            $values[] = $max_entries;
2103
-            sort($values);
2104
-        }
2105
-        $entries_per_page_dropdown = '
1800
+		$content .= $help_content;
1801
+		if ($display) {
1802
+			echo wp_kses($content, AllowedTags::getWithFormTags());
1803
+			return '';
1804
+		}
1805
+		return $content;
1806
+	}
1807
+
1808
+
1809
+	/**
1810
+	 * _add_global_screen_options
1811
+	 * Add any extra wp_screen_options within this method using built-in WP functions/methods for doing so.
1812
+	 * This particular method will add_screen_options on ALL EE_Admin Pages
1813
+	 *
1814
+	 * @link   http://chrismarslender.com/wp-tutorials/wordpress-screen-options-tutorial/
1815
+	 *         see also WP_Screen object documents...
1816
+	 * @link   http://codex.wordpress.org/Class_Reference/WP_Screen
1817
+	 * @abstract
1818
+	 * @return void
1819
+	 */
1820
+	private function _add_global_screen_options()
1821
+	{
1822
+	}
1823
+
1824
+
1825
+	/**
1826
+	 * _add_global_feature_pointers
1827
+	 * This method is used for implementing any "feature pointers" (using built-in WP styling js).
1828
+	 * This particular method will implement feature pointers for ALL EE_Admin pages.
1829
+	 * Note: this is just a placeholder for now.  Implementation will come down the road
1830
+	 *
1831
+	 * @see    WP_Internal_Pointers class in wp-admin/includes/template.php for example (it's a final class so can't be
1832
+	 *         extended) also see:
1833
+	 * @link   http://eamann.com/tech/wordpress-portland/
1834
+	 * @abstract
1835
+	 * @return void
1836
+	 */
1837
+	private function _add_global_feature_pointers()
1838
+	{
1839
+	}
1840
+
1841
+
1842
+	/**
1843
+	 * load_global_scripts_styles
1844
+	 * The scripts and styles enqueued in here will be loaded on every EE Admin page
1845
+	 *
1846
+	 * @return void
1847
+	 */
1848
+	public function load_global_scripts_styles()
1849
+	{
1850
+		// add debugging styles
1851
+		if (WP_DEBUG) {
1852
+			add_action('admin_head', [$this, 'add_xdebug_style']);
1853
+		}
1854
+		// taking care of metaboxes
1855
+		if (
1856
+			empty($this->_cpt_route)
1857
+			&& (isset($this->_route_config['metaboxes']) || isset($this->_route_config['has_metaboxes']))
1858
+		) {
1859
+			wp_enqueue_script('dashboard');
1860
+		}
1861
+
1862
+		wp_enqueue_script(JqueryAssetManager::JS_HANDLE_JQUERY_UI_TOUCH_PUNCH);
1863
+		wp_enqueue_script(EspressoLegacyAdminAssetManager::JS_HANDLE_EE_ADMIN);
1864
+		// LOCALIZED DATA
1865
+		// localize script for ajax lazy loading
1866
+		wp_localize_script(
1867
+			EspressoLegacyAdminAssetManager::JS_HANDLE_EE_ADMIN,
1868
+			'eeLazyLoadingContainers',
1869
+			apply_filters(
1870
+				'FHEE__EE_Admin_Page_Core__load_global_scripts_styles__loader_containers',
1871
+				['espresso_news_post_box_content']
1872
+			)
1873
+		);
1874
+		StatusChangeNotice::loadAssets();
1875
+
1876
+		add_filter(
1877
+			'admin_body_class',
1878
+			function ($classes) {
1879
+				if (strpos($classes, 'espresso-admin') === false) {
1880
+					$classes .= ' espresso-admin';
1881
+				}
1882
+				return $classes;
1883
+			}
1884
+		);
1885
+	}
1886
+
1887
+
1888
+	/**
1889
+	 *        admin_footer_scripts_eei18n_js_strings
1890
+	 *
1891
+	 * @return        void
1892
+	 */
1893
+	public function admin_footer_scripts_eei18n_js_strings()
1894
+	{
1895
+		EE_Registry::$i18n_js_strings['ajax_url']       = WP_AJAX_URL;
1896
+		EE_Registry::$i18n_js_strings['confirm_delete'] = wp_strip_all_tags(
1897
+			__(
1898
+				'Are you absolutely sure you want to delete this item?\nThis action will delete ALL DATA associated with this item!!!\nThis can NOT be undone!!!',
1899
+				'event_espresso'
1900
+			)
1901
+		);
1902
+		EE_Registry::$i18n_js_strings['January']        = wp_strip_all_tags(__('January', 'event_espresso'));
1903
+		EE_Registry::$i18n_js_strings['February']       = wp_strip_all_tags(__('February', 'event_espresso'));
1904
+		EE_Registry::$i18n_js_strings['March']          = wp_strip_all_tags(__('March', 'event_espresso'));
1905
+		EE_Registry::$i18n_js_strings['April']          = wp_strip_all_tags(__('April', 'event_espresso'));
1906
+		EE_Registry::$i18n_js_strings['May']            = wp_strip_all_tags(__('May', 'event_espresso'));
1907
+		EE_Registry::$i18n_js_strings['June']           = wp_strip_all_tags(__('June', 'event_espresso'));
1908
+		EE_Registry::$i18n_js_strings['July']           = wp_strip_all_tags(__('July', 'event_espresso'));
1909
+		EE_Registry::$i18n_js_strings['August']         = wp_strip_all_tags(__('August', 'event_espresso'));
1910
+		EE_Registry::$i18n_js_strings['September']      = wp_strip_all_tags(__('September', 'event_espresso'));
1911
+		EE_Registry::$i18n_js_strings['October']        = wp_strip_all_tags(__('October', 'event_espresso'));
1912
+		EE_Registry::$i18n_js_strings['November']       = wp_strip_all_tags(__('November', 'event_espresso'));
1913
+		EE_Registry::$i18n_js_strings['December']       = wp_strip_all_tags(__('December', 'event_espresso'));
1914
+		EE_Registry::$i18n_js_strings['Jan']            = wp_strip_all_tags(__('Jan', 'event_espresso'));
1915
+		EE_Registry::$i18n_js_strings['Feb']            = wp_strip_all_tags(__('Feb', 'event_espresso'));
1916
+		EE_Registry::$i18n_js_strings['Mar']            = wp_strip_all_tags(__('Mar', 'event_espresso'));
1917
+		EE_Registry::$i18n_js_strings['Apr']            = wp_strip_all_tags(__('Apr', 'event_espresso'));
1918
+		EE_Registry::$i18n_js_strings['May']            = wp_strip_all_tags(__('May', 'event_espresso'));
1919
+		EE_Registry::$i18n_js_strings['Jun']            = wp_strip_all_tags(__('Jun', 'event_espresso'));
1920
+		EE_Registry::$i18n_js_strings['Jul']            = wp_strip_all_tags(__('Jul', 'event_espresso'));
1921
+		EE_Registry::$i18n_js_strings['Aug']            = wp_strip_all_tags(__('Aug', 'event_espresso'));
1922
+		EE_Registry::$i18n_js_strings['Sep']            = wp_strip_all_tags(__('Sep', 'event_espresso'));
1923
+		EE_Registry::$i18n_js_strings['Oct']            = wp_strip_all_tags(__('Oct', 'event_espresso'));
1924
+		EE_Registry::$i18n_js_strings['Nov']            = wp_strip_all_tags(__('Nov', 'event_espresso'));
1925
+		EE_Registry::$i18n_js_strings['Dec']            = wp_strip_all_tags(__('Dec', 'event_espresso'));
1926
+		EE_Registry::$i18n_js_strings['Sunday']         = wp_strip_all_tags(__('Sunday', 'event_espresso'));
1927
+		EE_Registry::$i18n_js_strings['Monday']         = wp_strip_all_tags(__('Monday', 'event_espresso'));
1928
+		EE_Registry::$i18n_js_strings['Tuesday']        = wp_strip_all_tags(__('Tuesday', 'event_espresso'));
1929
+		EE_Registry::$i18n_js_strings['Wednesday']      = wp_strip_all_tags(__('Wednesday', 'event_espresso'));
1930
+		EE_Registry::$i18n_js_strings['Thursday']       = wp_strip_all_tags(__('Thursday', 'event_espresso'));
1931
+		EE_Registry::$i18n_js_strings['Friday']         = wp_strip_all_tags(__('Friday', 'event_espresso'));
1932
+		EE_Registry::$i18n_js_strings['Saturday']       = wp_strip_all_tags(__('Saturday', 'event_espresso'));
1933
+		EE_Registry::$i18n_js_strings['Sun']            = wp_strip_all_tags(__('Sun', 'event_espresso'));
1934
+		EE_Registry::$i18n_js_strings['Mon']            = wp_strip_all_tags(__('Mon', 'event_espresso'));
1935
+		EE_Registry::$i18n_js_strings['Tue']            = wp_strip_all_tags(__('Tue', 'event_espresso'));
1936
+		EE_Registry::$i18n_js_strings['Wed']            = wp_strip_all_tags(__('Wed', 'event_espresso'));
1937
+		EE_Registry::$i18n_js_strings['Thu']            = wp_strip_all_tags(__('Thu', 'event_espresso'));
1938
+		EE_Registry::$i18n_js_strings['Fri']            = wp_strip_all_tags(__('Fri', 'event_espresso'));
1939
+		EE_Registry::$i18n_js_strings['Sat']            = wp_strip_all_tags(__('Sat', 'event_espresso'));
1940
+	}
1941
+
1942
+
1943
+	/**
1944
+	 *        load enhanced xdebug styles for ppl with failing eyesight
1945
+	 *
1946
+	 * @return        void
1947
+	 */
1948
+	public function add_xdebug_style()
1949
+	{
1950
+		echo '<style>.xdebug-error { font-size:1.5em; }</style>';
1951
+	}
1952
+
1953
+
1954
+	/************************/
1955
+	/** LIST TABLE METHODS **/
1956
+	/************************/
1957
+	/**
1958
+	 * this sets up the list table if the current view requires it.
1959
+	 *
1960
+	 * @return void
1961
+	 * @throws EE_Error
1962
+	 * @throws InvalidArgumentException
1963
+	 * @throws InvalidDataTypeException
1964
+	 * @throws InvalidInterfaceException
1965
+	 */
1966
+	protected function _set_list_table()
1967
+	{
1968
+		// first is this a list_table view?
1969
+		if (! isset($this->_route_config['list_table'])) {
1970
+			return;
1971
+		} //not a list_table view so get out.
1972
+		// list table functions are per view specific (because some admin pages might have more than one list table!)
1973
+		$list_table_view = '_set_list_table_views_' . $this->_req_action;
1974
+		if (! method_exists($this, $list_table_view) || $this->{$list_table_view}() === false) {
1975
+			// user error msg
1976
+			$error_msg = esc_html__(
1977
+				'An error occurred. The requested list table views could not be found.',
1978
+				'event_espresso'
1979
+			);
1980
+			// developer error msg
1981
+			$error_msg .= '||'
1982
+						  . sprintf(
1983
+							  esc_html__(
1984
+								  'List table views for "%s" route could not be setup. Check that you have the corresponding method, "%s" set up for defining list_table_views for this route.',
1985
+								  'event_espresso'
1986
+							  ),
1987
+							  $this->_req_action,
1988
+							  $list_table_view
1989
+						  );
1990
+			throw new EE_Error($error_msg);
1991
+		}
1992
+		// let's provide the ability to filter the views per PAGE AND ROUTE, per PAGE, and globally
1993
+		$this->_views = apply_filters(
1994
+			'FHEE_list_table_views_' . $this->page_slug . '_' . $this->_req_action,
1995
+			$this->_views
1996
+		);
1997
+		$this->_views = apply_filters('FHEE_list_table_views_' . $this->page_slug, $this->_views);
1998
+		$this->_views = apply_filters('FHEE_list_table_views', $this->_views);
1999
+		$this->_set_list_table_view();
2000
+		$this->_set_list_table_object();
2001
+	}
2002
+
2003
+
2004
+	/**
2005
+	 * set current view for List Table
2006
+	 *
2007
+	 * @return void
2008
+	 */
2009
+	protected function _set_list_table_view()
2010
+	{
2011
+		$this->_view = isset($this->_views['in_use']) ? 'in_use' : 'all';
2012
+		$status      = $this->request->getRequestParam('status', null, DataType::KEY);
2013
+		$this->_view = $status && array_key_exists($status, $this->_views)
2014
+			? $status
2015
+			: $this->_view;
2016
+	}
2017
+
2018
+
2019
+	/**
2020
+	 * _set_list_table_object
2021
+	 * WP_List_Table objects need to be loaded fairly early so automatic stuff WP does is taken care of.
2022
+	 *
2023
+	 * @throws InvalidInterfaceException
2024
+	 * @throws InvalidArgumentException
2025
+	 * @throws InvalidDataTypeException
2026
+	 * @throws EE_Error
2027
+	 * @throws InvalidInterfaceException
2028
+	 */
2029
+	protected function _set_list_table_object()
2030
+	{
2031
+		if (isset($this->_route_config['list_table'])) {
2032
+			if (! class_exists($this->_route_config['list_table'])) {
2033
+				throw new EE_Error(
2034
+					sprintf(
2035
+						esc_html__(
2036
+							'The %s class defined for the list table does not exist.  Please check the spelling of the class ref in the $_page_config property on %s.',
2037
+							'event_espresso'
2038
+						),
2039
+						$this->_route_config['list_table'],
2040
+						$this->class_name
2041
+					)
2042
+				);
2043
+			}
2044
+			$this->_list_table_object = $this->loader->getShared(
2045
+				$this->_route_config['list_table'],
2046
+				[
2047
+					$this,
2048
+					LoaderFactory::getShared(AdminListTableFilters::class),
2049
+				]
2050
+			);
2051
+		}
2052
+	}
2053
+
2054
+
2055
+	/**
2056
+	 * get_list_table_view_RLs - get it? View RL ?? VU-RL???  URL ??
2057
+	 *
2058
+	 * @param array $extra_query_args                     Optional. An array of extra query args to add to the generated
2059
+	 *                                                    urls.  The array should be indexed by the view it is being
2060
+	 *                                                    added to.
2061
+	 * @return array
2062
+	 */
2063
+	public function get_list_table_view_RLs(array $extra_query_args = []): array
2064
+	{
2065
+		$extra_query_args = apply_filters(
2066
+			'FHEE__EE_Admin_Page__get_list_table_view_RLs__extra_query_args',
2067
+			$extra_query_args,
2068
+			$this
2069
+		);
2070
+		// cycle thru views
2071
+		foreach ($this->_views as $key => $view) {
2072
+			$query_args = [];
2073
+			// check for current view
2074
+			$this->_views[ $key ]['class'] = $this->_view === $view['slug'] ? 'current' : '';
2075
+			$query_args['action']          = $this->_req_action;
2076
+			$action_nonce                  = "{$this->_req_action}_nonce";
2077
+			$query_args[ $action_nonce ]   = wp_create_nonce($action_nonce);
2078
+			$query_args['status']          = $view['slug'];
2079
+			// merge any other arguments sent in.
2080
+			if (isset($extra_query_args[ $view['slug'] ])) {
2081
+				$query_args = array_merge($query_args, $extra_query_args[ $view['slug'] ]);
2082
+			}
2083
+			$this->_views[ $key ]['url'] = EE_Admin_Page::add_query_args_and_nonce($query_args, $this->_admin_base_url);
2084
+		}
2085
+		return $this->_views;
2086
+	}
2087
+
2088
+
2089
+	/**
2090
+	 * generates a dropdown box for selecting the number of visible rows in an admin page list table
2091
+	 *
2092
+	 * @param int $max_entries total number of rows in the table
2093
+	 * @return string
2094
+	 * @todo   : Note: ideally this should be added to the screen options dropdown as that would be consistent with how
2095
+	 *                         WP does it.
2096
+	 */
2097
+	protected function _entries_per_page_dropdown(int $max_entries = 0): string
2098
+	{
2099
+		$values   = [10, 25, 50, 100];
2100
+		$per_page = $this->request->getRequestParam('per_page', 10, DataType::INT);
2101
+		if ($max_entries) {
2102
+			$values[] = $max_entries;
2103
+			sort($values);
2104
+		}
2105
+		$entries_per_page_dropdown = '
2106 2106
 			<div id="entries-per-page-dv" class="alignleft actions">
2107 2107
 				<label class="hide-if-no-js">
2108 2108
 					Show
2109 2109
 					<select id="entries-per-page-slct" name="entries-per-page-slct">';
2110
-        foreach ($values as $value) {
2111
-            if ($value < $max_entries) {
2112
-                $selected                  = $value === $per_page ? ' selected="' . $per_page . '"' : '';
2113
-                $entries_per_page_dropdown .= '
2110
+		foreach ($values as $value) {
2111
+			if ($value < $max_entries) {
2112
+				$selected                  = $value === $per_page ? ' selected="' . $per_page . '"' : '';
2113
+				$entries_per_page_dropdown .= '
2114 2114
 						<option value="' . $value . '"' . $selected . '>' . $value . '&nbsp;&nbsp;</option>';
2115
-            }
2116
-        }
2117
-        $selected                  = $max_entries === $per_page ? ' selected="' . $per_page . '"' : '';
2118
-        $entries_per_page_dropdown .= '
2115
+			}
2116
+		}
2117
+		$selected                  = $max_entries === $per_page ? ' selected="' . $per_page . '"' : '';
2118
+		$entries_per_page_dropdown .= '
2119 2119
 						<option value="' . $max_entries . '"' . $selected . '>All&nbsp;&nbsp;</option>';
2120
-        $entries_per_page_dropdown .= '
2120
+		$entries_per_page_dropdown .= '
2121 2121
 					</select>
2122 2122
 					entries
2123 2123
 				</label>
2124 2124
 				<input id="entries-per-page-btn" class="button button--secondary" type="submit" value="Go" >
2125 2125
 			</div>
2126 2126
 		';
2127
-        return $entries_per_page_dropdown;
2128
-    }
2129
-
2130
-
2131
-    /**
2132
-     *        _set_search_attributes
2133
-     *
2134
-     * @return        void
2135
-     */
2136
-    public function _set_search_attributes()
2137
-    {
2138
-        $this->_template_args['search']['btn_label'] = sprintf(
2139
-            esc_html__('Search %s', 'event_espresso'),
2140
-            empty($this->_search_btn_label) ? $this->page_label
2141
-                : $this->_search_btn_label
2142
-        );
2143
-        $this->_template_args['search']['callback']  = 'search_' . $this->page_slug;
2144
-    }
2145
-
2146
-
2147
-
2148
-    /*** END LIST TABLE METHODS **/
2149
-
2150
-    /**
2151
-     * @return void
2152
-     * @throws EE_Error
2153
-     */
2154
-    public function addRegisteredMetaBoxes()
2155
-    {
2156
-        remove_action('add_meta_boxes', [$this, 'addRegisteredMetaBoxes'], 99);
2157
-        $this->_add_registered_meta_boxes();
2158
-    }
2159
-
2160
-
2161
-    /**
2162
-     * _add_registered_metaboxes
2163
-     *  this loads any registered metaboxes via the 'metaboxes' index in the _page_config property array.
2164
-     *
2165
-     * @link   http://codex.wordpress.org/Function_Reference/add_meta_box
2166
-     * @return void
2167
-     * @throws EE_Error
2168
-     */
2169
-    private function _add_registered_meta_boxes()
2170
-    {
2171
-        // we only add meta boxes if the page_route calls for it
2172
-        if (isset($this->_route_config['metaboxes']) && is_array($this->_route_config['metaboxes'])) {
2173
-            // this simply loops through the callbacks provided
2174
-            // and checks if there is a corresponding callback registered by the child
2175
-            // if there is then we go ahead and process the metabox loader.
2176
-            foreach ($this->_route_config['metaboxes'] as $key => $metabox_callback) {
2177
-                // first check for Closures
2178
-                if ($metabox_callback instanceof Closure) {
2179
-                    $result = $metabox_callback();
2180
-                } elseif (is_callable($metabox_callback)) {
2181
-                    $result = call_user_func($metabox_callback);
2182
-                } elseif (method_exists($this, $metabox_callback)) {
2183
-                    $result = $this->{$metabox_callback}();
2184
-                } else {
2185
-                    $result = false;
2186
-                }
2187
-                if ($result === false) {
2188
-                    // user error msg
2189
-                    $error_msg = esc_html__(
2190
-                        'An error occurred. The  requested metabox could not be found.',
2191
-                        'event_espresso'
2192
-                    );
2193
-                    // developer error msg
2194
-                    $error_msg .= '||'
2195
-                                  . sprintf(
2196
-                                      esc_html__(
2197
-                                          'The metabox with the string "%s" could not be called. Check that the spelling for method names and actions in the "_page_config[\'metaboxes\']" array are all correct.',
2198
-                                          'event_espresso'
2199
-                                      ),
2200
-                                      $metabox_callback
2201
-                                  );
2202
-                    throw new EE_Error($error_msg);
2203
-                }
2204
-                unset($this->_route_config['metaboxes'][ $key ]);
2205
-            }
2206
-        }
2207
-    }
2208
-
2209
-
2210
-    /**
2211
-     * _add_screen_columns
2212
-     * This will check the _page_config array and if there is "columns" key index indicated, we'll set the template as
2213
-     * the dynamic column template and we'll set up the column options for the page.
2214
-     *
2215
-     * @return void
2216
-     */
2217
-    private function _add_screen_columns()
2218
-    {
2219
-        if (
2220
-            isset($this->_route_config['columns'])
2221
-            && is_array($this->_route_config['columns'])
2222
-            && count($this->_route_config['columns']) === 2
2223
-        ) {
2224
-            add_screen_option(
2225
-                'layout_columns',
2226
-                [
2227
-                    'max'     => (int) $this->_route_config['columns'][0],
2228
-                    'default' => (int) $this->_route_config['columns'][1],
2229
-                ]
2230
-            );
2231
-            $this->_template_args['num_columns']                 = $this->_route_config['columns'][0];
2232
-            $screen_id                                           = $this->_current_screen->id;
2233
-            $screen_columns                                      = (int) get_user_option("screen_layout_$screen_id");
2234
-            $total_columns                                       = ! empty($screen_columns)
2235
-                ? $screen_columns
2236
-                : $this->_route_config['columns'][1];
2237
-            $this->_template_args['current_screen_widget_class'] = 'columns-' . $total_columns;
2238
-            $this->_template_args['current_page']                = $this->_wp_page_slug;
2239
-            $this->_template_args['screen']                      = $this->_current_screen;
2240
-            $this->_column_template_path                         = EE_ADMIN_TEMPLATE
2241
-                                                                   . 'admin_details_metabox_column_wrapper.template.php';
2242
-            // finally if we don't have has_metaboxes set in the route config
2243
-            // let's make sure it IS set otherwise the necessary hidden fields for this won't be loaded.
2244
-            $this->_route_config['has_metaboxes'] = true;
2245
-        }
2246
-    }
2247
-
2248
-
2249
-
2250
-    /** GLOBALLY AVAILABLE METABOXES **/
2251
-
2252
-
2253
-    /**
2254
-     * In this section we put any globally available EE metaboxes for all EE Admin pages.  They are called by simply
2255
-     * referencing the callback in the _page_config array property.  This way you can be very specific about what pages
2256
-     * these get loaded on.
2257
-     */
2258
-    private function _espresso_news_post_box()
2259
-    {
2260
-        $news_box_title = apply_filters(
2261
-            'FHEE__EE_Admin_Page___espresso_news_post_box__news_box_title',
2262
-            esc_html__('New @ Event Espresso', 'event_espresso')
2263
-        );
2264
-        $this->addMetaBox(
2265
-            'espresso_news_post_box',
2266
-            $news_box_title,
2267
-            [
2268
-                $this,
2269
-                'espresso_news_post_box',
2270
-            ],
2271
-            $this->_wp_page_slug,
2272
-            'side',
2273
-            'low'
2274
-        );
2275
-    }
2276
-
2277
-
2278
-    /**
2279
-     * Code for setting up espresso ratings request metabox.
2280
-     */
2281
-    protected function _espresso_ratings_request()
2282
-    {
2283
-        if (! apply_filters('FHEE_show_ratings_request_meta_box', true)) {
2284
-            return;
2285
-        }
2286
-        $ratings_box_title = apply_filters(
2287
-            'FHEE__EE_Admin_Page___espresso_news_post_box__news_box_title',
2288
-            esc_html__('Keep Event Espresso Decaf Free', 'event_espresso')
2289
-        );
2290
-        $this->addMetaBox(
2291
-            'espresso_ratings_request',
2292
-            $ratings_box_title,
2293
-            [
2294
-                $this,
2295
-                'espresso_ratings_request',
2296
-            ],
2297
-            $this->_wp_page_slug,
2298
-            'side'
2299
-        );
2300
-    }
2301
-
2302
-
2303
-    /**
2304
-     * Code for setting up espresso ratings request metabox content.
2305
-     *
2306
-     * @throws DomainException
2307
-     */
2308
-    public function espresso_ratings_request()
2309
-    {
2310
-        EEH_Template::display_template(EE_ADMIN_TEMPLATE . 'espresso_ratings_request_content.template.php');
2311
-    }
2312
-
2313
-
2314
-    public static function cached_rss_display(string $rss_id, string $url): bool
2315
-    {
2316
-        $loading   = '<p class="widget-loading hide-if-no-js">'
2317
-                     . esc_html__('Loading&#8230;', 'event_espresso')
2318
-                     . '</p><p class="hide-if-js">'
2319
-                     . esc_html__('This widget requires JavaScript.', 'event_espresso')
2320
-                     . '</p>';
2321
-        $pre       = '<div class="espresso-rss-display">' . "\n\t";
2322
-        $pre       .= '<span id="' . esc_attr($rss_id) . '_url" class="hidden">' . esc_url_raw($url) . '</span>';
2323
-        $post      = '</div>' . "\n";
2324
-        $cache_key = 'ee_rss_' . md5($rss_id);
2325
-        $output    = get_transient($cache_key);
2326
-        if ($output !== false) {
2327
-            echo wp_kses($pre . $output . $post, AllowedTags::getWithFormTags());
2328
-            return true;
2329
-        }
2330
-        if (! (defined('DOING_AJAX') && DOING_AJAX)) {
2331
-            echo wp_kses($pre . $loading . $post, AllowedTags::getWithFormTags());
2332
-            return false;
2333
-        }
2334
-        ob_start();
2335
-        wp_widget_rss_output($url, ['show_date' => 0, 'items' => 5]);
2336
-        set_transient($cache_key, ob_get_flush(), 12 * HOUR_IN_SECONDS);
2337
-        return true;
2338
-    }
2339
-
2340
-
2341
-    public function espresso_news_post_box()
2342
-    {
2343
-        ?>
2127
+		return $entries_per_page_dropdown;
2128
+	}
2129
+
2130
+
2131
+	/**
2132
+	 *        _set_search_attributes
2133
+	 *
2134
+	 * @return        void
2135
+	 */
2136
+	public function _set_search_attributes()
2137
+	{
2138
+		$this->_template_args['search']['btn_label'] = sprintf(
2139
+			esc_html__('Search %s', 'event_espresso'),
2140
+			empty($this->_search_btn_label) ? $this->page_label
2141
+				: $this->_search_btn_label
2142
+		);
2143
+		$this->_template_args['search']['callback']  = 'search_' . $this->page_slug;
2144
+	}
2145
+
2146
+
2147
+
2148
+	/*** END LIST TABLE METHODS **/
2149
+
2150
+	/**
2151
+	 * @return void
2152
+	 * @throws EE_Error
2153
+	 */
2154
+	public function addRegisteredMetaBoxes()
2155
+	{
2156
+		remove_action('add_meta_boxes', [$this, 'addRegisteredMetaBoxes'], 99);
2157
+		$this->_add_registered_meta_boxes();
2158
+	}
2159
+
2160
+
2161
+	/**
2162
+	 * _add_registered_metaboxes
2163
+	 *  this loads any registered metaboxes via the 'metaboxes' index in the _page_config property array.
2164
+	 *
2165
+	 * @link   http://codex.wordpress.org/Function_Reference/add_meta_box
2166
+	 * @return void
2167
+	 * @throws EE_Error
2168
+	 */
2169
+	private function _add_registered_meta_boxes()
2170
+	{
2171
+		// we only add meta boxes if the page_route calls for it
2172
+		if (isset($this->_route_config['metaboxes']) && is_array($this->_route_config['metaboxes'])) {
2173
+			// this simply loops through the callbacks provided
2174
+			// and checks if there is a corresponding callback registered by the child
2175
+			// if there is then we go ahead and process the metabox loader.
2176
+			foreach ($this->_route_config['metaboxes'] as $key => $metabox_callback) {
2177
+				// first check for Closures
2178
+				if ($metabox_callback instanceof Closure) {
2179
+					$result = $metabox_callback();
2180
+				} elseif (is_callable($metabox_callback)) {
2181
+					$result = call_user_func($metabox_callback);
2182
+				} elseif (method_exists($this, $metabox_callback)) {
2183
+					$result = $this->{$metabox_callback}();
2184
+				} else {
2185
+					$result = false;
2186
+				}
2187
+				if ($result === false) {
2188
+					// user error msg
2189
+					$error_msg = esc_html__(
2190
+						'An error occurred. The  requested metabox could not be found.',
2191
+						'event_espresso'
2192
+					);
2193
+					// developer error msg
2194
+					$error_msg .= '||'
2195
+								  . sprintf(
2196
+									  esc_html__(
2197
+										  'The metabox with the string "%s" could not be called. Check that the spelling for method names and actions in the "_page_config[\'metaboxes\']" array are all correct.',
2198
+										  'event_espresso'
2199
+									  ),
2200
+									  $metabox_callback
2201
+								  );
2202
+					throw new EE_Error($error_msg);
2203
+				}
2204
+				unset($this->_route_config['metaboxes'][ $key ]);
2205
+			}
2206
+		}
2207
+	}
2208
+
2209
+
2210
+	/**
2211
+	 * _add_screen_columns
2212
+	 * This will check the _page_config array and if there is "columns" key index indicated, we'll set the template as
2213
+	 * the dynamic column template and we'll set up the column options for the page.
2214
+	 *
2215
+	 * @return void
2216
+	 */
2217
+	private function _add_screen_columns()
2218
+	{
2219
+		if (
2220
+			isset($this->_route_config['columns'])
2221
+			&& is_array($this->_route_config['columns'])
2222
+			&& count($this->_route_config['columns']) === 2
2223
+		) {
2224
+			add_screen_option(
2225
+				'layout_columns',
2226
+				[
2227
+					'max'     => (int) $this->_route_config['columns'][0],
2228
+					'default' => (int) $this->_route_config['columns'][1],
2229
+				]
2230
+			);
2231
+			$this->_template_args['num_columns']                 = $this->_route_config['columns'][0];
2232
+			$screen_id                                           = $this->_current_screen->id;
2233
+			$screen_columns                                      = (int) get_user_option("screen_layout_$screen_id");
2234
+			$total_columns                                       = ! empty($screen_columns)
2235
+				? $screen_columns
2236
+				: $this->_route_config['columns'][1];
2237
+			$this->_template_args['current_screen_widget_class'] = 'columns-' . $total_columns;
2238
+			$this->_template_args['current_page']                = $this->_wp_page_slug;
2239
+			$this->_template_args['screen']                      = $this->_current_screen;
2240
+			$this->_column_template_path                         = EE_ADMIN_TEMPLATE
2241
+																   . 'admin_details_metabox_column_wrapper.template.php';
2242
+			// finally if we don't have has_metaboxes set in the route config
2243
+			// let's make sure it IS set otherwise the necessary hidden fields for this won't be loaded.
2244
+			$this->_route_config['has_metaboxes'] = true;
2245
+		}
2246
+	}
2247
+
2248
+
2249
+
2250
+	/** GLOBALLY AVAILABLE METABOXES **/
2251
+
2252
+
2253
+	/**
2254
+	 * In this section we put any globally available EE metaboxes for all EE Admin pages.  They are called by simply
2255
+	 * referencing the callback in the _page_config array property.  This way you can be very specific about what pages
2256
+	 * these get loaded on.
2257
+	 */
2258
+	private function _espresso_news_post_box()
2259
+	{
2260
+		$news_box_title = apply_filters(
2261
+			'FHEE__EE_Admin_Page___espresso_news_post_box__news_box_title',
2262
+			esc_html__('New @ Event Espresso', 'event_espresso')
2263
+		);
2264
+		$this->addMetaBox(
2265
+			'espresso_news_post_box',
2266
+			$news_box_title,
2267
+			[
2268
+				$this,
2269
+				'espresso_news_post_box',
2270
+			],
2271
+			$this->_wp_page_slug,
2272
+			'side',
2273
+			'low'
2274
+		);
2275
+	}
2276
+
2277
+
2278
+	/**
2279
+	 * Code for setting up espresso ratings request metabox.
2280
+	 */
2281
+	protected function _espresso_ratings_request()
2282
+	{
2283
+		if (! apply_filters('FHEE_show_ratings_request_meta_box', true)) {
2284
+			return;
2285
+		}
2286
+		$ratings_box_title = apply_filters(
2287
+			'FHEE__EE_Admin_Page___espresso_news_post_box__news_box_title',
2288
+			esc_html__('Keep Event Espresso Decaf Free', 'event_espresso')
2289
+		);
2290
+		$this->addMetaBox(
2291
+			'espresso_ratings_request',
2292
+			$ratings_box_title,
2293
+			[
2294
+				$this,
2295
+				'espresso_ratings_request',
2296
+			],
2297
+			$this->_wp_page_slug,
2298
+			'side'
2299
+		);
2300
+	}
2301
+
2302
+
2303
+	/**
2304
+	 * Code for setting up espresso ratings request metabox content.
2305
+	 *
2306
+	 * @throws DomainException
2307
+	 */
2308
+	public function espresso_ratings_request()
2309
+	{
2310
+		EEH_Template::display_template(EE_ADMIN_TEMPLATE . 'espresso_ratings_request_content.template.php');
2311
+	}
2312
+
2313
+
2314
+	public static function cached_rss_display(string $rss_id, string $url): bool
2315
+	{
2316
+		$loading   = '<p class="widget-loading hide-if-no-js">'
2317
+					 . esc_html__('Loading&#8230;', 'event_espresso')
2318
+					 . '</p><p class="hide-if-js">'
2319
+					 . esc_html__('This widget requires JavaScript.', 'event_espresso')
2320
+					 . '</p>';
2321
+		$pre       = '<div class="espresso-rss-display">' . "\n\t";
2322
+		$pre       .= '<span id="' . esc_attr($rss_id) . '_url" class="hidden">' . esc_url_raw($url) . '</span>';
2323
+		$post      = '</div>' . "\n";
2324
+		$cache_key = 'ee_rss_' . md5($rss_id);
2325
+		$output    = get_transient($cache_key);
2326
+		if ($output !== false) {
2327
+			echo wp_kses($pre . $output . $post, AllowedTags::getWithFormTags());
2328
+			return true;
2329
+		}
2330
+		if (! (defined('DOING_AJAX') && DOING_AJAX)) {
2331
+			echo wp_kses($pre . $loading . $post, AllowedTags::getWithFormTags());
2332
+			return false;
2333
+		}
2334
+		ob_start();
2335
+		wp_widget_rss_output($url, ['show_date' => 0, 'items' => 5]);
2336
+		set_transient($cache_key, ob_get_flush(), 12 * HOUR_IN_SECONDS);
2337
+		return true;
2338
+	}
2339
+
2340
+
2341
+	public function espresso_news_post_box()
2342
+	{
2343
+		?>
2344 2344
 <div class="padding">
2345 2345
 	<div id="espresso_news_post_box_content" class="infolinks">
2346 2346
 		<?php
2347
-                // Get RSS Feed(s)
2348
-                EE_Admin_Page::cached_rss_display(
2349
-                    'espresso_news_post_box_content',
2350
-                    esc_url_raw(
2351
-                        apply_filters(
2352
-                            'FHEE__EE_Admin_Page__espresso_news_post_box__feed_url',
2353
-                            'https://eventespresso.com/feed/'
2354
-                        )
2355
-                    )
2356
-                );
2357
-                ?>
2347
+				// Get RSS Feed(s)
2348
+				EE_Admin_Page::cached_rss_display(
2349
+					'espresso_news_post_box_content',
2350
+					esc_url_raw(
2351
+						apply_filters(
2352
+							'FHEE__EE_Admin_Page__espresso_news_post_box__feed_url',
2353
+							'https://eventespresso.com/feed/'
2354
+						)
2355
+					)
2356
+				);
2357
+				?>
2358 2358
 	</div>
2359 2359
 	<?php do_action('AHEE__EE_Admin_Page__espresso_news_post_box__after_content'); ?>
2360 2360
 </div>
2361 2361
 <?php
2362
-    }
2363
-
2364
-
2365
-    private function _espresso_links_post_box()
2366
-    {
2367
-        // Hiding until we actually have content to put in here...
2368
-        // $this->addMetaBox('espresso_links_post_box', esc_html__('Helpful Plugin Links', 'event_espresso'), array( $this, 'espresso_links_post_box'), $this->_wp_page_slug, 'side');
2369
-    }
2370
-
2371
-
2372
-    public function espresso_links_post_box()
2373
-    {
2374
-        // Hiding until we actually have content to put in here...
2375
-        // EEH_Template::display_template(
2376
-        //     EE_ADMIN_TEMPLATE . 'admin_general_metabox_contents_espresso_links.template.php'
2377
-        // );
2378
-    }
2379
-
2380
-
2381
-    protected function _espresso_sponsors_post_box()
2382
-    {
2383
-        if (apply_filters('FHEE_show_sponsors_meta_box', true)) {
2384
-            $this->addMetaBox(
2385
-                'espresso_sponsors_post_box',
2386
-                esc_html__('Event Espresso Highlights', 'event_espresso'),
2387
-                [$this, 'espresso_sponsors_post_box'],
2388
-                $this->_wp_page_slug,
2389
-                'side'
2390
-            );
2391
-        }
2392
-    }
2393
-
2394
-
2395
-    public function espresso_sponsors_post_box()
2396
-    {
2397
-        EEH_Template::display_template(
2398
-            EE_ADMIN_TEMPLATE . 'admin_general_metabox_contents_espresso_sponsors.template.php'
2399
-        );
2400
-    }
2401
-
2402
-
2403
-    /**
2404
-     * if there is [ 'label' => [ 'publishbox' => 'some title' ]]
2405
-     * present in the _page_config array, then we'll use that for the metabox label.
2406
-     * Otherwise we'll just use publish
2407
-     * (publishbox itself could be an array of labels indexed by routes)
2408
-     *
2409
-     * @return string
2410
-     * @since   5.0.0.p
2411
-     */
2412
-    protected function getPublishBoxTitle(): string
2413
-    {
2414
-        $publish_box_title = esc_html__('Publish', 'event_espresso');
2415
-        if (! empty($this->_labels['publishbox'])) {
2416
-            if (is_array($this->_labels['publishbox'])) {
2417
-                $publish_box_title = $this->_labels['publishbox'][ $this->_req_action ] ?? $publish_box_title;
2418
-            } else {
2419
-                $publish_box_title = $this->_labels['publishbox'];
2420
-            }
2421
-        }
2422
-        return apply_filters(
2423
-            'FHEE__EE_Admin_Page___publish_post_box__box_label',
2424
-            $publish_box_title,
2425
-            $this->_req_action,
2426
-            $this
2427
-        );
2428
-    }
2429
-
2430
-
2431
-    /**
2432
-     * @throws EE_Error
2433
-     */
2434
-    private function _publish_post_box()
2435
-    {
2436
-        $title = $this->getPublishBoxTitle();
2437
-        if (empty($this->_template_args['save_buttons'])) {
2438
-            $this->_set_publish_post_box_vars(sanitize_key($title), "espresso_{$this->page_slug}_editor_overview");
2439
-        } else {
2440
-            $this->addPublishPostMetaBoxHiddenFields(
2441
-                sanitize_key($title),
2442
-                ['type' => 'hidden', 'value' => "espresso_{$this->page_slug}_editor_overview"]
2443
-            );
2444
-        }
2445
-        $this->addMetaBox(
2446
-            "espresso_{$this->page_slug}_editor_overview",
2447
-            $title,
2448
-            [$this, 'editor_overview'],
2449
-            $this->_current_screen->id,
2450
-            'side',
2451
-            'high'
2452
-        );
2453
-    }
2454
-
2455
-
2456
-    public function editor_overview()
2457
-    {
2458
-        /**
2459
-         * @var string $publish_box_extra_content
2460
-         * @var string $publish_hidden_fields
2461
-         * @var string $publish_delete_link
2462
-         * @var string $save_buttons
2463
-         */
2464
-        // if we have extra content set let's add it in if not make sure its empty
2465
-        $this->_template_args['publish_box_extra_content'] = $this->_template_args['publish_box_extra_content'] ?? '';
2466
-        echo EEH_Template::display_template(
2467
-            EE_ADMIN_TEMPLATE . 'admin_details_publish_metabox.template.php',
2468
-            $this->_template_args,
2469
-            true
2470
-        );
2471
-    }
2472
-
2473
-
2474
-    /** end of globally available metaboxes section **/
2475
-
2476
-
2477
-    /**
2478
-     * Sets the _template_args arguments used by the _publish_post_box shortcut
2479
-     * Note: currently there is no validation for this.  However, if you want the delete button, the
2480
-     * save, and save and close buttons to work properly, then you will want to include a
2481
-     * values for the name and id arguments.
2482
-     *
2483
-     * @param string|null $name                     key used for the action ID (i.e. event_id)
2484
-     * @param int|string  $id                       id attached to the item published
2485
-     * @param string|null $delete                   page route callback for the delete action
2486
-     * @param string|null $save_close_redirect_URL  custom URL to redirect to after Save & Close has been completed
2487
-     * @param bool        $both_btns                whether to display BOTH the "Save & Close" and "Save" buttons
2488
-     *                                              or just the "Save" button
2489
-     * @throws EE_Error
2490
-     * @throws InvalidArgumentException
2491
-     * @throws InvalidDataTypeException
2492
-     * @throws InvalidInterfaceException
2493
-     * @todo  Add in validation for name/id arguments.
2494
-     */
2495
-    protected function _set_publish_post_box_vars(
2496
-        ?string $name = '',
2497
-        $id = 0,
2498
-        ?string $delete = '',
2499
-        ?string $save_close_redirect_URL = '',
2500
-        bool $both_btns = true
2501
-    ) {
2502
-        // if Save & Close, use a custom redirect URL or default to the main page?
2503
-        $save_close_redirect_URL = ! empty($save_close_redirect_URL)
2504
-            ? $save_close_redirect_URL
2505
-            : $this->_admin_base_url;
2506
-        // create the Save & Close and Save buttons
2507
-        $this->_set_save_buttons($both_btns, [], [], $save_close_redirect_URL);
2508
-        // if we have extra content set let's add it in if not make sure its empty
2509
-        $this->_template_args['publish_box_extra_content'] = $this->_template_args['publish_box_extra_content'] ?? '';
2510
-        if ($delete && ! empty($id) && empty($this->_template_args['publish_delete_link'])) {
2511
-            // make sure we have a default if just true is sent.
2512
-            $delete                                      = ! empty($delete) ? $delete : 'delete';
2513
-            $this->_template_args['publish_delete_link'] = $this->get_action_link_or_button(
2514
-                $delete,
2515
-                $delete,
2516
-                [$name => $id],
2517
-                'submitdelete deletion button button--outline button--caution'
2518
-            );
2519
-        }
2520
-        if (! isset($this->_template_args['publish_delete_link'])) {
2521
-            $this->_template_args['publish_delete_link'] = '';
2522
-        }
2523
-        if (! empty($name) && ! empty($id)) {
2524
-            $this->addPublishPostMetaBoxHiddenFields($name, ['type' => 'hidden', 'value' => $id]);
2525
-        }
2526
-        $hidden_fields = $this->_generate_admin_form_fields($this->publish_post_meta_box_hidden_fields, 'array');
2527
-        // add hidden fields
2528
-        $this->_template_args['publish_hidden_fields'] = $this->_template_args['publish_hidden_fields'] ?? '';
2529
-        foreach ($hidden_fields as $hidden_field) {
2530
-            $this->_template_args['publish_hidden_fields'] .= $hidden_field['field'] ?? '';
2531
-        }
2532
-    }
2533
-
2534
-
2535
-    /**
2536
-     * @param string|null $name
2537
-     * @param int|string  $id
2538
-     * @param string|null $delete
2539
-     * @param string|null $save_close_redirect_URL
2540
-     * @param bool        $both_btns
2541
-     * @throws EE_Error
2542
-     */
2543
-    public function set_publish_post_box_vars(
2544
-        ?string $name = '',
2545
-        $id = 0,
2546
-        ?string $delete = '',
2547
-        ?string $save_close_redirect_URL = '',
2548
-        bool $both_btns = false
2549
-    ) {
2550
-        $this->_set_publish_post_box_vars($name, $id, $delete, $save_close_redirect_URL, $both_btns);
2551
-    }
2552
-
2553
-
2554
-    protected function addPublishPostMetaBoxHiddenFields(string $field_name, array $field_attributes)
2555
-    {
2556
-        $this->publish_post_meta_box_hidden_fields[ $field_name ] = $field_attributes;
2557
-    }
2558
-
2559
-
2560
-    /**
2561
-     * displays an error message to ppl who have javascript disabled
2562
-     *
2563
-     * @return void
2564
-     */
2565
-    private function _display_no_javascript_warning()
2566
-    {
2567
-        ?>
2362
+	}
2363
+
2364
+
2365
+	private function _espresso_links_post_box()
2366
+	{
2367
+		// Hiding until we actually have content to put in here...
2368
+		// $this->addMetaBox('espresso_links_post_box', esc_html__('Helpful Plugin Links', 'event_espresso'), array( $this, 'espresso_links_post_box'), $this->_wp_page_slug, 'side');
2369
+	}
2370
+
2371
+
2372
+	public function espresso_links_post_box()
2373
+	{
2374
+		// Hiding until we actually have content to put in here...
2375
+		// EEH_Template::display_template(
2376
+		//     EE_ADMIN_TEMPLATE . 'admin_general_metabox_contents_espresso_links.template.php'
2377
+		// );
2378
+	}
2379
+
2380
+
2381
+	protected function _espresso_sponsors_post_box()
2382
+	{
2383
+		if (apply_filters('FHEE_show_sponsors_meta_box', true)) {
2384
+			$this->addMetaBox(
2385
+				'espresso_sponsors_post_box',
2386
+				esc_html__('Event Espresso Highlights', 'event_espresso'),
2387
+				[$this, 'espresso_sponsors_post_box'],
2388
+				$this->_wp_page_slug,
2389
+				'side'
2390
+			);
2391
+		}
2392
+	}
2393
+
2394
+
2395
+	public function espresso_sponsors_post_box()
2396
+	{
2397
+		EEH_Template::display_template(
2398
+			EE_ADMIN_TEMPLATE . 'admin_general_metabox_contents_espresso_sponsors.template.php'
2399
+		);
2400
+	}
2401
+
2402
+
2403
+	/**
2404
+	 * if there is [ 'label' => [ 'publishbox' => 'some title' ]]
2405
+	 * present in the _page_config array, then we'll use that for the metabox label.
2406
+	 * Otherwise we'll just use publish
2407
+	 * (publishbox itself could be an array of labels indexed by routes)
2408
+	 *
2409
+	 * @return string
2410
+	 * @since   5.0.0.p
2411
+	 */
2412
+	protected function getPublishBoxTitle(): string
2413
+	{
2414
+		$publish_box_title = esc_html__('Publish', 'event_espresso');
2415
+		if (! empty($this->_labels['publishbox'])) {
2416
+			if (is_array($this->_labels['publishbox'])) {
2417
+				$publish_box_title = $this->_labels['publishbox'][ $this->_req_action ] ?? $publish_box_title;
2418
+			} else {
2419
+				$publish_box_title = $this->_labels['publishbox'];
2420
+			}
2421
+		}
2422
+		return apply_filters(
2423
+			'FHEE__EE_Admin_Page___publish_post_box__box_label',
2424
+			$publish_box_title,
2425
+			$this->_req_action,
2426
+			$this
2427
+		);
2428
+	}
2429
+
2430
+
2431
+	/**
2432
+	 * @throws EE_Error
2433
+	 */
2434
+	private function _publish_post_box()
2435
+	{
2436
+		$title = $this->getPublishBoxTitle();
2437
+		if (empty($this->_template_args['save_buttons'])) {
2438
+			$this->_set_publish_post_box_vars(sanitize_key($title), "espresso_{$this->page_slug}_editor_overview");
2439
+		} else {
2440
+			$this->addPublishPostMetaBoxHiddenFields(
2441
+				sanitize_key($title),
2442
+				['type' => 'hidden', 'value' => "espresso_{$this->page_slug}_editor_overview"]
2443
+			);
2444
+		}
2445
+		$this->addMetaBox(
2446
+			"espresso_{$this->page_slug}_editor_overview",
2447
+			$title,
2448
+			[$this, 'editor_overview'],
2449
+			$this->_current_screen->id,
2450
+			'side',
2451
+			'high'
2452
+		);
2453
+	}
2454
+
2455
+
2456
+	public function editor_overview()
2457
+	{
2458
+		/**
2459
+		 * @var string $publish_box_extra_content
2460
+		 * @var string $publish_hidden_fields
2461
+		 * @var string $publish_delete_link
2462
+		 * @var string $save_buttons
2463
+		 */
2464
+		// if we have extra content set let's add it in if not make sure its empty
2465
+		$this->_template_args['publish_box_extra_content'] = $this->_template_args['publish_box_extra_content'] ?? '';
2466
+		echo EEH_Template::display_template(
2467
+			EE_ADMIN_TEMPLATE . 'admin_details_publish_metabox.template.php',
2468
+			$this->_template_args,
2469
+			true
2470
+		);
2471
+	}
2472
+
2473
+
2474
+	/** end of globally available metaboxes section **/
2475
+
2476
+
2477
+	/**
2478
+	 * Sets the _template_args arguments used by the _publish_post_box shortcut
2479
+	 * Note: currently there is no validation for this.  However, if you want the delete button, the
2480
+	 * save, and save and close buttons to work properly, then you will want to include a
2481
+	 * values for the name and id arguments.
2482
+	 *
2483
+	 * @param string|null $name                     key used for the action ID (i.e. event_id)
2484
+	 * @param int|string  $id                       id attached to the item published
2485
+	 * @param string|null $delete                   page route callback for the delete action
2486
+	 * @param string|null $save_close_redirect_URL  custom URL to redirect to after Save & Close has been completed
2487
+	 * @param bool        $both_btns                whether to display BOTH the "Save & Close" and "Save" buttons
2488
+	 *                                              or just the "Save" button
2489
+	 * @throws EE_Error
2490
+	 * @throws InvalidArgumentException
2491
+	 * @throws InvalidDataTypeException
2492
+	 * @throws InvalidInterfaceException
2493
+	 * @todo  Add in validation for name/id arguments.
2494
+	 */
2495
+	protected function _set_publish_post_box_vars(
2496
+		?string $name = '',
2497
+		$id = 0,
2498
+		?string $delete = '',
2499
+		?string $save_close_redirect_URL = '',
2500
+		bool $both_btns = true
2501
+	) {
2502
+		// if Save & Close, use a custom redirect URL or default to the main page?
2503
+		$save_close_redirect_URL = ! empty($save_close_redirect_URL)
2504
+			? $save_close_redirect_URL
2505
+			: $this->_admin_base_url;
2506
+		// create the Save & Close and Save buttons
2507
+		$this->_set_save_buttons($both_btns, [], [], $save_close_redirect_URL);
2508
+		// if we have extra content set let's add it in if not make sure its empty
2509
+		$this->_template_args['publish_box_extra_content'] = $this->_template_args['publish_box_extra_content'] ?? '';
2510
+		if ($delete && ! empty($id) && empty($this->_template_args['publish_delete_link'])) {
2511
+			// make sure we have a default if just true is sent.
2512
+			$delete                                      = ! empty($delete) ? $delete : 'delete';
2513
+			$this->_template_args['publish_delete_link'] = $this->get_action_link_or_button(
2514
+				$delete,
2515
+				$delete,
2516
+				[$name => $id],
2517
+				'submitdelete deletion button button--outline button--caution'
2518
+			);
2519
+		}
2520
+		if (! isset($this->_template_args['publish_delete_link'])) {
2521
+			$this->_template_args['publish_delete_link'] = '';
2522
+		}
2523
+		if (! empty($name) && ! empty($id)) {
2524
+			$this->addPublishPostMetaBoxHiddenFields($name, ['type' => 'hidden', 'value' => $id]);
2525
+		}
2526
+		$hidden_fields = $this->_generate_admin_form_fields($this->publish_post_meta_box_hidden_fields, 'array');
2527
+		// add hidden fields
2528
+		$this->_template_args['publish_hidden_fields'] = $this->_template_args['publish_hidden_fields'] ?? '';
2529
+		foreach ($hidden_fields as $hidden_field) {
2530
+			$this->_template_args['publish_hidden_fields'] .= $hidden_field['field'] ?? '';
2531
+		}
2532
+	}
2533
+
2534
+
2535
+	/**
2536
+	 * @param string|null $name
2537
+	 * @param int|string  $id
2538
+	 * @param string|null $delete
2539
+	 * @param string|null $save_close_redirect_URL
2540
+	 * @param bool        $both_btns
2541
+	 * @throws EE_Error
2542
+	 */
2543
+	public function set_publish_post_box_vars(
2544
+		?string $name = '',
2545
+		$id = 0,
2546
+		?string $delete = '',
2547
+		?string $save_close_redirect_URL = '',
2548
+		bool $both_btns = false
2549
+	) {
2550
+		$this->_set_publish_post_box_vars($name, $id, $delete, $save_close_redirect_URL, $both_btns);
2551
+	}
2552
+
2553
+
2554
+	protected function addPublishPostMetaBoxHiddenFields(string $field_name, array $field_attributes)
2555
+	{
2556
+		$this->publish_post_meta_box_hidden_fields[ $field_name ] = $field_attributes;
2557
+	}
2558
+
2559
+
2560
+	/**
2561
+	 * displays an error message to ppl who have javascript disabled
2562
+	 *
2563
+	 * @return void
2564
+	 */
2565
+	private function _display_no_javascript_warning()
2566
+	{
2567
+		?>
2568 2568
 <noscript>
2569 2569
 	<div id="no-js-message" class="error">
2570 2570
 		<p style="font-size:1.3em;">
2571 2571
 			<span style="color:red;"><?php esc_html_e('Warning!', 'event_espresso'); ?></span>
2572 2572
 			<?php esc_html_e(
2573
-                        'Javascript is currently turned off for your browser. Javascript must be enabled in order for all of the features on this page to function properly. Please turn your javascript back on.',
2574
-                        'event_espresso'
2575
-                    ); ?>
2573
+						'Javascript is currently turned off for your browser. Javascript must be enabled in order for all of the features on this page to function properly. Please turn your javascript back on.',
2574
+						'event_espresso'
2575
+					); ?>
2576 2576
 		</p>
2577 2577
 	</div>
2578 2578
 </noscript>
2579 2579
 <?php
2580
-    }
2581
-
2582
-
2583
-    /**
2584
-     * displays espresso success and/or error notices
2585
-     *
2586
-     * @return void
2587
-     */
2588
-    protected function _display_espresso_notices()
2589
-    {
2590
-        $notices = (array) $this->_get_transient(true);
2591
-        foreach ($notices as $notice) {
2592
-            echo $notice ? stripslashes($notice) : '';
2593
-        }
2594
-    }
2595
-
2596
-
2597
-    /**
2598
-     * spinny things pacify the masses
2599
-     *
2600
-     * @return void
2601
-     */
2602
-    protected function _add_admin_page_ajax_loading_img()
2603
-    {
2604
-        ?>
2580
+	}
2581
+
2582
+
2583
+	/**
2584
+	 * displays espresso success and/or error notices
2585
+	 *
2586
+	 * @return void
2587
+	 */
2588
+	protected function _display_espresso_notices()
2589
+	{
2590
+		$notices = (array) $this->_get_transient(true);
2591
+		foreach ($notices as $notice) {
2592
+			echo $notice ? stripslashes($notice) : '';
2593
+		}
2594
+	}
2595
+
2596
+
2597
+	/**
2598
+	 * spinny things pacify the masses
2599
+	 *
2600
+	 * @return void
2601
+	 */
2602
+	protected function _add_admin_page_ajax_loading_img()
2603
+	{
2604
+		?>
2605 2605
 <div id="espresso-ajax-loading" class="ajax-loading-grey">
2606 2606
 	<span class="ee-spinner ee-spin"></span><span class="hidden"><?php
2607
-                esc_html_e('loading...', 'event_espresso'); ?></span>
2607
+				esc_html_e('loading...', 'event_espresso'); ?></span>
2608 2608
 </div>
2609 2609
 <?php
2610
-    }
2610
+	}
2611 2611
 
2612 2612
 
2613
-    /**
2614
-     * add admin page overlay for modal boxes
2615
-     *
2616
-     * @return void
2617
-     */
2618
-    protected function _add_admin_page_overlay()
2619
-    {
2620
-        ?>
2613
+	/**
2614
+	 * add admin page overlay for modal boxes
2615
+	 *
2616
+	 * @return void
2617
+	 */
2618
+	protected function _add_admin_page_overlay()
2619
+	{
2620
+		?>
2621 2621
 <div id="espresso-admin-page-overlay-dv" class=""></div>
2622 2622
 <?php
2623
-    }
2624
-
2625
-
2626
-    /**
2627
-     * facade for $this->addMetaBox()
2628
-     *
2629
-     * @param string   $action        where the metabox gets displayed
2630
-     * @param string   $title         Title of Metabox (output in metabox header)
2631
-     * @param callable $callback      If not empty and $create_fun is set to false then we'll use a custom callback
2632
-     *                                instead of the one created in here.
2633
-     * @param array    $callback_args an array of args supplied for the metabox
2634
-     * @param string   $column        what metabox column
2635
-     * @param string   $priority      give this metabox a priority (using accepted priorities for wp meta boxes)
2636
-     * @param bool     $create_func   default is true.  Basically we can say we don't WANT to have the runtime function
2637
-     *                                created but just set our own callback for wp's add_meta_box.
2638
-     * @throws DomainException
2639
-     */
2640
-    public function _add_admin_page_meta_box(
2641
-        string $action,
2642
-        string $title,
2643
-        callable $callback,
2644
-        array $callback_args,
2645
-        string $column = 'normal',
2646
-        string $priority = 'high',
2647
-        bool $create_func = true
2648
-    ) {
2649
-        // if we have empty callback args and we want to automatically create the metabox callback then we need to make sure the callback args are generated.
2650
-        if (empty($callback_args) && $create_func) {
2651
-            $callback_args = [
2652
-                'template_path' => $this->_template_path,
2653
-                'template_args' => $this->_template_args,
2654
-            ];
2655
-        }
2656
-        // if $create_func is true (default) then we automatically create the function for displaying the actual meta box.  If false then we take the $callback reference passed through and use it instead (so callers can define their own callback function/method if they wish)
2657
-        $call_back_func = $create_func
2658
-            ? static function ($post, $metabox) {
2659
-                echo EEH_Template::display_template(
2660
-                    $metabox['args']['template_path'],
2661
-                    $metabox['args']['template_args'],
2662
-                    true
2663
-                );
2664
-            }
2665
-            : $callback;
2666
-        $this->addMetaBox(
2667
-            str_replace('_', '-', $action) . '-mbox',
2668
-            $title,
2669
-            $call_back_func,
2670
-            $this->_wp_page_slug,
2671
-            $column,
2672
-            $priority,
2673
-            $callback_args
2674
-        );
2675
-    }
2676
-
2677
-
2678
-    /**
2679
-     * generates HTML wrapper for and admin details page that contains metaboxes in columns
2680
-     *
2681
-     * @throws DomainException
2682
-     * @throws EE_Error
2683
-     * @throws InvalidArgumentException
2684
-     * @throws InvalidDataTypeException
2685
-     * @throws InvalidInterfaceException
2686
-     */
2687
-    public function display_admin_page_with_metabox_columns()
2688
-    {
2689
-        $this->_template_args['post_body_content']  = $this->_template_args['admin_page_content'];
2690
-        $this->_template_args['admin_page_content'] = EEH_Template::display_template(
2691
-            $this->_column_template_path,
2692
-            $this->_template_args,
2693
-            true
2694
-        );
2695
-        // the final wrapper
2696
-        $this->admin_page_wrapper();
2697
-    }
2698
-
2699
-
2700
-    /**
2701
-     * generates  HTML wrapper for an admin details page
2702
-     *
2703
-     * @return void
2704
-     * @throws DomainException
2705
-     * @throws EE_Error
2706
-     * @throws InvalidArgumentException
2707
-     * @throws InvalidDataTypeException
2708
-     * @throws InvalidInterfaceException
2709
-     */
2710
-    public function display_admin_page_with_sidebar()
2711
-    {
2712
-        $this->_display_admin_page(true);
2713
-    }
2714
-
2715
-
2716
-    /**
2717
-     * generates  HTML wrapper for an admin details page (except no sidebar)
2718
-     *
2719
-     * @return void
2720
-     * @throws DomainException
2721
-     * @throws EE_Error
2722
-     * @throws InvalidArgumentException
2723
-     * @throws InvalidDataTypeException
2724
-     * @throws InvalidInterfaceException
2725
-     */
2726
-    public function display_admin_page_with_no_sidebar()
2727
-    {
2728
-        $this->_display_admin_page();
2729
-    }
2730
-
2731
-
2732
-    /**
2733
-     * generates HTML wrapper for an EE about admin page (no sidebar)
2734
-     *
2735
-     * @return void
2736
-     * @throws DomainException
2737
-     * @throws EE_Error
2738
-     * @throws InvalidArgumentException
2739
-     * @throws InvalidDataTypeException
2740
-     * @throws InvalidInterfaceException
2741
-     */
2742
-    public function display_about_admin_page()
2743
-    {
2744
-        $this->_display_admin_page(false, true);
2745
-    }
2746
-
2747
-
2748
-    /**
2749
-     * display_admin_page
2750
-     * contains the code for actually displaying an admin page
2751
-     *
2752
-     * @param bool $sidebar true with sidebar, false without
2753
-     * @param bool $about   use the About admin wrapper instead of the default.
2754
-     * @return void
2755
-     * @throws DomainException
2756
-     * @throws EE_Error
2757
-     * @throws InvalidArgumentException
2758
-     * @throws InvalidDataTypeException
2759
-     * @throws InvalidInterfaceException
2760
-     */
2761
-    private function _display_admin_page(bool $sidebar = false, bool $about = false): void
2762
-    {
2763
-        // custom remove metaboxes hook to add or remove any metaboxes to/from Admin pages.
2764
-        do_action('AHEE__EE_Admin_Page___display_admin_page__modify_metaboxes');
2765
-
2766
-        // set current wp page slug - looks like: event-espresso_page_event_categories
2767
-        // keep in mind "event-espresso" COULD be something else if the top level menu label has been translated.
2768
-        $post_body_content = $this->_template_args['before_admin_page_content'] ?? '';
2769
-
2770
-        $this->_template_args['add_page_frame'] = $this->_req_action !== 'system_status'
2771
-                                                  && $this->_req_action !== 'data_reset'
2772
-                                                  && $this->_wp_page_slug !== 'event-espresso_page_espresso_packages'
2773
-                                                  && strpos($post_body_content, 'wp-list-table') === false;
2774
-
2775
-        $this->_template_args['current_page']                 = $this->_wp_page_slug;
2776
-        $this->_template_args['admin_page_wrapper_div_id']    = $this->_cpt_route
2777
-            ? 'poststuff'
2778
-            : 'espresso-default-admin';
2779
-        $this->_template_args['admin_page_wrapper_div_class'] = str_replace(
2780
-                                                                    'event-espresso_page_espresso_',
2781
-                                                                    '',
2782
-                                                                    $this->_wp_page_slug
2783
-                                                                ) . ' ' . $this->_req_action . '-route';
2784
-
2785
-        $template_path = $sidebar
2786
-            ? EE_ADMIN_TEMPLATE . 'admin_details_wrapper.template.php'
2787
-            : EE_ADMIN_TEMPLATE . 'admin_details_wrapper_no_sidebar.template.php';
2788
-
2789
-        $this->_template_args['is_ajax'] = $this->request->isAjax();
2790
-        if ($this->request->isAjax()) {
2791
-            $template_path = EE_ADMIN_TEMPLATE . 'admin_details_wrapper_no_sidebar_ajax.template.php';
2792
-        }
2793
-        $template_path = ! empty($this->_column_template_path) ? $this->_column_template_path : $template_path;
2794
-
2795
-        $this->_template_args['post_body_content']         = $this->_template_args['admin_page_content'] ?? '';
2796
-        $this->_template_args['before_admin_page_content'] = $post_body_content;
2797
-        $this->_template_args['after_admin_page_content']  = $this->_template_args['after_admin_page_content'] ?? '';
2798
-
2799
-        // ensure $post_type and $post are set
2800
-        // to prevent WooCommerce from blowing things up if not using CPT
2801
-        global $post_type, $post;
2802
-        $this->_template_args['post_type'] = $post_type ?? '';
2803
-        $this->_template_args['post']  = $post ?? new WP_Post( (object) [ 'ID' => 0, 'filter' => 'raw' ] );
2804
-
2805
-        $this->_template_args['post_body_content'] = EEH_Template::display_template(
2806
-            EE_ADMIN_TEMPLATE . 'admin_details_wrapper_post_body_content.template.php',
2807
-            $this->_template_args,
2808
-            true
2809
-        );
2810
-
2811
-        $this->_template_args['admin_page_content'] = EEH_Template::display_template(
2812
-            $template_path,
2813
-            $this->_template_args,
2814
-            true
2815
-        );
2816
-        // the final template wrapper
2817
-        $this->admin_page_wrapper($about);
2818
-    }
2819
-
2820
-
2821
-    /**
2822
-     * This is used to display caf preview pages.
2823
-     *
2824
-     * @param string $utm_campaign_source what is the key used for Google Analytics link
2825
-     * @param bool   $display_sidebar     whether to use the sidebar template or the full template for the page.  TRUE
2826
-     *                                    = SHOW sidebar, FALSE = no sidebar. Default no sidebar.
2827
-     * @return void
2828
-     * @throws DomainException
2829
-     * @throws EE_Error
2830
-     * @throws InvalidArgumentException
2831
-     * @throws InvalidDataTypeException
2832
-     * @throws InvalidInterfaceException
2833
-     * @since 4.3.2
2834
-     */
2835
-    public function display_admin_caf_preview_page(string $utm_campaign_source = '', bool $display_sidebar = true)
2836
-    {
2837
-        // let's generate a default preview action button if there isn't one already present.
2838
-        $this->_labels['buttons']['buy_now']           = esc_html__(
2839
-            'Upgrade to Event Espresso 4 Right Now',
2840
-            'event_espresso'
2841
-        );
2842
-        $buy_now_url                                   = add_query_arg(
2843
-            [
2844
-                'ee_ver'       => 'ee4',
2845
-                'utm_source'   => 'ee4_plugin_admin',
2846
-                'utm_medium'   => 'link',
2847
-                'utm_campaign' => $utm_campaign_source,
2848
-                'utm_content'  => 'buy_now_button',
2849
-            ],
2850
-            'https://eventespresso.com/pricing/'
2851
-        );
2852
-        $this->_template_args['preview_action_button'] = ! isset($this->_template_args['preview_action_button'])
2853
-            ? $this->get_action_link_or_button(
2854
-                '',
2855
-                'buy_now',
2856
-                [],
2857
-                'button button--primary button--big',
2858
-                esc_url_raw($buy_now_url),
2859
-                true
2860
-            )
2861
-            : $this->_template_args['preview_action_button'];
2862
-        $this->_template_args['admin_page_content']    = EEH_Template::display_template(
2863
-            EE_ADMIN_TEMPLATE . 'admin_caf_full_page_preview.template.php',
2864
-            $this->_template_args,
2865
-            true
2866
-        );
2867
-        $this->_display_admin_page($display_sidebar);
2868
-    }
2869
-
2870
-
2871
-    /**
2872
-     * display_admin_list_table_page_with_sidebar
2873
-     * generates HTML wrapper for an admin_page with list_table
2874
-     *
2875
-     * @return void
2876
-     * @throws DomainException
2877
-     * @throws EE_Error
2878
-     * @throws InvalidArgumentException
2879
-     * @throws InvalidDataTypeException
2880
-     * @throws InvalidInterfaceException
2881
-     */
2882
-    public function display_admin_list_table_page_with_sidebar()
2883
-    {
2884
-        $this->_display_admin_list_table_page(true);
2885
-    }
2886
-
2887
-
2888
-    /**
2889
-     * display_admin_list_table_page_with_no_sidebar
2890
-     * generates HTML wrapper for an admin_page with list_table (but with no sidebar)
2891
-     *
2892
-     * @return void
2893
-     * @throws DomainException
2894
-     * @throws EE_Error
2895
-     * @throws InvalidArgumentException
2896
-     * @throws InvalidDataTypeException
2897
-     * @throws InvalidInterfaceException
2898
-     */
2899
-    public function display_admin_list_table_page_with_no_sidebar()
2900
-    {
2901
-        $this->_display_admin_list_table_page();
2902
-    }
2903
-
2904
-
2905
-    /**
2906
-     * generates html wrapper for an admin_list_table page
2907
-     *
2908
-     * @param bool $sidebar whether to display with sidebar or not.
2909
-     * @return void
2910
-     * @throws DomainException
2911
-     * @throws EE_Error
2912
-     * @throws InvalidArgumentException
2913
-     * @throws InvalidDataTypeException
2914
-     * @throws InvalidInterfaceException
2915
-     */
2916
-    private function _display_admin_list_table_page(bool $sidebar = false)
2917
-    {
2918
-        // setup search attributes
2919
-        $this->_set_search_attributes();
2920
-        $this->_template_args['current_page']     = $this->_wp_page_slug;
2921
-        $template_path                            = EE_ADMIN_TEMPLATE . 'admin_list_wrapper.template.php';
2922
-        $this->_template_args['table_url']        = $this->request->isAjax()
2923
-            ? add_query_arg(['noheader' => 'true', 'route' => $this->_req_action], $this->_admin_base_url)
2924
-            : add_query_arg(['route' => $this->_req_action], $this->_admin_base_url);
2925
-        $this->_template_args['list_table']       = $this->_list_table_object;
2926
-        $this->_template_args['current_route']    = $this->_req_action;
2927
-        $this->_template_args['list_table_class'] = get_class($this->_list_table_object);
2928
-        $ajax_sorting_callback                    = $this->_list_table_object->get_ajax_sorting_callback();
2929
-        if (! empty($ajax_sorting_callback)) {
2930
-            $sortable_list_table_form_fields = wp_nonce_field(
2931
-                $ajax_sorting_callback . '_nonce',
2932
-                $ajax_sorting_callback . '_nonce',
2933
-                false,
2934
-                false
2935
-            );
2936
-            $sortable_list_table_form_fields .= '<input type="hidden" id="ajax_table_sort_page" name="ajax_table_sort_page" value="'
2937
-                                                . $this->page_slug
2938
-                                                . '" />';
2939
-            $sortable_list_table_form_fields .= '<input type="hidden" id="ajax_table_sort_action" name="ajax_table_sort_action" value="'
2940
-                                                . $ajax_sorting_callback
2941
-                                                . '" />';
2942
-        } else {
2943
-            $sortable_list_table_form_fields = '';
2944
-        }
2945
-        $this->_template_args['sortable_list_table_form_fields'] = $sortable_list_table_form_fields;
2946
-
2947
-        $hidden_form_fields = $this->_template_args['list_table_hidden_fields'] ?? '';
2948
-
2949
-        $nonce_ref          = $this->_req_action . '_nonce';
2950
-        $hidden_form_fields .= '
2623
+	}
2624
+
2625
+
2626
+	/**
2627
+	 * facade for $this->addMetaBox()
2628
+	 *
2629
+	 * @param string   $action        where the metabox gets displayed
2630
+	 * @param string   $title         Title of Metabox (output in metabox header)
2631
+	 * @param callable $callback      If not empty and $create_fun is set to false then we'll use a custom callback
2632
+	 *                                instead of the one created in here.
2633
+	 * @param array    $callback_args an array of args supplied for the metabox
2634
+	 * @param string   $column        what metabox column
2635
+	 * @param string   $priority      give this metabox a priority (using accepted priorities for wp meta boxes)
2636
+	 * @param bool     $create_func   default is true.  Basically we can say we don't WANT to have the runtime function
2637
+	 *                                created but just set our own callback for wp's add_meta_box.
2638
+	 * @throws DomainException
2639
+	 */
2640
+	public function _add_admin_page_meta_box(
2641
+		string $action,
2642
+		string $title,
2643
+		callable $callback,
2644
+		array $callback_args,
2645
+		string $column = 'normal',
2646
+		string $priority = 'high',
2647
+		bool $create_func = true
2648
+	) {
2649
+		// if we have empty callback args and we want to automatically create the metabox callback then we need to make sure the callback args are generated.
2650
+		if (empty($callback_args) && $create_func) {
2651
+			$callback_args = [
2652
+				'template_path' => $this->_template_path,
2653
+				'template_args' => $this->_template_args,
2654
+			];
2655
+		}
2656
+		// if $create_func is true (default) then we automatically create the function for displaying the actual meta box.  If false then we take the $callback reference passed through and use it instead (so callers can define their own callback function/method if they wish)
2657
+		$call_back_func = $create_func
2658
+			? static function ($post, $metabox) {
2659
+				echo EEH_Template::display_template(
2660
+					$metabox['args']['template_path'],
2661
+					$metabox['args']['template_args'],
2662
+					true
2663
+				);
2664
+			}
2665
+			: $callback;
2666
+		$this->addMetaBox(
2667
+			str_replace('_', '-', $action) . '-mbox',
2668
+			$title,
2669
+			$call_back_func,
2670
+			$this->_wp_page_slug,
2671
+			$column,
2672
+			$priority,
2673
+			$callback_args
2674
+		);
2675
+	}
2676
+
2677
+
2678
+	/**
2679
+	 * generates HTML wrapper for and admin details page that contains metaboxes in columns
2680
+	 *
2681
+	 * @throws DomainException
2682
+	 * @throws EE_Error
2683
+	 * @throws InvalidArgumentException
2684
+	 * @throws InvalidDataTypeException
2685
+	 * @throws InvalidInterfaceException
2686
+	 */
2687
+	public function display_admin_page_with_metabox_columns()
2688
+	{
2689
+		$this->_template_args['post_body_content']  = $this->_template_args['admin_page_content'];
2690
+		$this->_template_args['admin_page_content'] = EEH_Template::display_template(
2691
+			$this->_column_template_path,
2692
+			$this->_template_args,
2693
+			true
2694
+		);
2695
+		// the final wrapper
2696
+		$this->admin_page_wrapper();
2697
+	}
2698
+
2699
+
2700
+	/**
2701
+	 * generates  HTML wrapper for an admin details page
2702
+	 *
2703
+	 * @return void
2704
+	 * @throws DomainException
2705
+	 * @throws EE_Error
2706
+	 * @throws InvalidArgumentException
2707
+	 * @throws InvalidDataTypeException
2708
+	 * @throws InvalidInterfaceException
2709
+	 */
2710
+	public function display_admin_page_with_sidebar()
2711
+	{
2712
+		$this->_display_admin_page(true);
2713
+	}
2714
+
2715
+
2716
+	/**
2717
+	 * generates  HTML wrapper for an admin details page (except no sidebar)
2718
+	 *
2719
+	 * @return void
2720
+	 * @throws DomainException
2721
+	 * @throws EE_Error
2722
+	 * @throws InvalidArgumentException
2723
+	 * @throws InvalidDataTypeException
2724
+	 * @throws InvalidInterfaceException
2725
+	 */
2726
+	public function display_admin_page_with_no_sidebar()
2727
+	{
2728
+		$this->_display_admin_page();
2729
+	}
2730
+
2731
+
2732
+	/**
2733
+	 * generates HTML wrapper for an EE about admin page (no sidebar)
2734
+	 *
2735
+	 * @return void
2736
+	 * @throws DomainException
2737
+	 * @throws EE_Error
2738
+	 * @throws InvalidArgumentException
2739
+	 * @throws InvalidDataTypeException
2740
+	 * @throws InvalidInterfaceException
2741
+	 */
2742
+	public function display_about_admin_page()
2743
+	{
2744
+		$this->_display_admin_page(false, true);
2745
+	}
2746
+
2747
+
2748
+	/**
2749
+	 * display_admin_page
2750
+	 * contains the code for actually displaying an admin page
2751
+	 *
2752
+	 * @param bool $sidebar true with sidebar, false without
2753
+	 * @param bool $about   use the About admin wrapper instead of the default.
2754
+	 * @return void
2755
+	 * @throws DomainException
2756
+	 * @throws EE_Error
2757
+	 * @throws InvalidArgumentException
2758
+	 * @throws InvalidDataTypeException
2759
+	 * @throws InvalidInterfaceException
2760
+	 */
2761
+	private function _display_admin_page(bool $sidebar = false, bool $about = false): void
2762
+	{
2763
+		// custom remove metaboxes hook to add or remove any metaboxes to/from Admin pages.
2764
+		do_action('AHEE__EE_Admin_Page___display_admin_page__modify_metaboxes');
2765
+
2766
+		// set current wp page slug - looks like: event-espresso_page_event_categories
2767
+		// keep in mind "event-espresso" COULD be something else if the top level menu label has been translated.
2768
+		$post_body_content = $this->_template_args['before_admin_page_content'] ?? '';
2769
+
2770
+		$this->_template_args['add_page_frame'] = $this->_req_action !== 'system_status'
2771
+												  && $this->_req_action !== 'data_reset'
2772
+												  && $this->_wp_page_slug !== 'event-espresso_page_espresso_packages'
2773
+												  && strpos($post_body_content, 'wp-list-table') === false;
2774
+
2775
+		$this->_template_args['current_page']                 = $this->_wp_page_slug;
2776
+		$this->_template_args['admin_page_wrapper_div_id']    = $this->_cpt_route
2777
+			? 'poststuff'
2778
+			: 'espresso-default-admin';
2779
+		$this->_template_args['admin_page_wrapper_div_class'] = str_replace(
2780
+																	'event-espresso_page_espresso_',
2781
+																	'',
2782
+																	$this->_wp_page_slug
2783
+																) . ' ' . $this->_req_action . '-route';
2784
+
2785
+		$template_path = $sidebar
2786
+			? EE_ADMIN_TEMPLATE . 'admin_details_wrapper.template.php'
2787
+			: EE_ADMIN_TEMPLATE . 'admin_details_wrapper_no_sidebar.template.php';
2788
+
2789
+		$this->_template_args['is_ajax'] = $this->request->isAjax();
2790
+		if ($this->request->isAjax()) {
2791
+			$template_path = EE_ADMIN_TEMPLATE . 'admin_details_wrapper_no_sidebar_ajax.template.php';
2792
+		}
2793
+		$template_path = ! empty($this->_column_template_path) ? $this->_column_template_path : $template_path;
2794
+
2795
+		$this->_template_args['post_body_content']         = $this->_template_args['admin_page_content'] ?? '';
2796
+		$this->_template_args['before_admin_page_content'] = $post_body_content;
2797
+		$this->_template_args['after_admin_page_content']  = $this->_template_args['after_admin_page_content'] ?? '';
2798
+
2799
+		// ensure $post_type and $post are set
2800
+		// to prevent WooCommerce from blowing things up if not using CPT
2801
+		global $post_type, $post;
2802
+		$this->_template_args['post_type'] = $post_type ?? '';
2803
+		$this->_template_args['post']  = $post ?? new WP_Post( (object) [ 'ID' => 0, 'filter' => 'raw' ] );
2804
+
2805
+		$this->_template_args['post_body_content'] = EEH_Template::display_template(
2806
+			EE_ADMIN_TEMPLATE . 'admin_details_wrapper_post_body_content.template.php',
2807
+			$this->_template_args,
2808
+			true
2809
+		);
2810
+
2811
+		$this->_template_args['admin_page_content'] = EEH_Template::display_template(
2812
+			$template_path,
2813
+			$this->_template_args,
2814
+			true
2815
+		);
2816
+		// the final template wrapper
2817
+		$this->admin_page_wrapper($about);
2818
+	}
2819
+
2820
+
2821
+	/**
2822
+	 * This is used to display caf preview pages.
2823
+	 *
2824
+	 * @param string $utm_campaign_source what is the key used for Google Analytics link
2825
+	 * @param bool   $display_sidebar     whether to use the sidebar template or the full template for the page.  TRUE
2826
+	 *                                    = SHOW sidebar, FALSE = no sidebar. Default no sidebar.
2827
+	 * @return void
2828
+	 * @throws DomainException
2829
+	 * @throws EE_Error
2830
+	 * @throws InvalidArgumentException
2831
+	 * @throws InvalidDataTypeException
2832
+	 * @throws InvalidInterfaceException
2833
+	 * @since 4.3.2
2834
+	 */
2835
+	public function display_admin_caf_preview_page(string $utm_campaign_source = '', bool $display_sidebar = true)
2836
+	{
2837
+		// let's generate a default preview action button if there isn't one already present.
2838
+		$this->_labels['buttons']['buy_now']           = esc_html__(
2839
+			'Upgrade to Event Espresso 4 Right Now',
2840
+			'event_espresso'
2841
+		);
2842
+		$buy_now_url                                   = add_query_arg(
2843
+			[
2844
+				'ee_ver'       => 'ee4',
2845
+				'utm_source'   => 'ee4_plugin_admin',
2846
+				'utm_medium'   => 'link',
2847
+				'utm_campaign' => $utm_campaign_source,
2848
+				'utm_content'  => 'buy_now_button',
2849
+			],
2850
+			'https://eventespresso.com/pricing/'
2851
+		);
2852
+		$this->_template_args['preview_action_button'] = ! isset($this->_template_args['preview_action_button'])
2853
+			? $this->get_action_link_or_button(
2854
+				'',
2855
+				'buy_now',
2856
+				[],
2857
+				'button button--primary button--big',
2858
+				esc_url_raw($buy_now_url),
2859
+				true
2860
+			)
2861
+			: $this->_template_args['preview_action_button'];
2862
+		$this->_template_args['admin_page_content']    = EEH_Template::display_template(
2863
+			EE_ADMIN_TEMPLATE . 'admin_caf_full_page_preview.template.php',
2864
+			$this->_template_args,
2865
+			true
2866
+		);
2867
+		$this->_display_admin_page($display_sidebar);
2868
+	}
2869
+
2870
+
2871
+	/**
2872
+	 * display_admin_list_table_page_with_sidebar
2873
+	 * generates HTML wrapper for an admin_page with list_table
2874
+	 *
2875
+	 * @return void
2876
+	 * @throws DomainException
2877
+	 * @throws EE_Error
2878
+	 * @throws InvalidArgumentException
2879
+	 * @throws InvalidDataTypeException
2880
+	 * @throws InvalidInterfaceException
2881
+	 */
2882
+	public function display_admin_list_table_page_with_sidebar()
2883
+	{
2884
+		$this->_display_admin_list_table_page(true);
2885
+	}
2886
+
2887
+
2888
+	/**
2889
+	 * display_admin_list_table_page_with_no_sidebar
2890
+	 * generates HTML wrapper for an admin_page with list_table (but with no sidebar)
2891
+	 *
2892
+	 * @return void
2893
+	 * @throws DomainException
2894
+	 * @throws EE_Error
2895
+	 * @throws InvalidArgumentException
2896
+	 * @throws InvalidDataTypeException
2897
+	 * @throws InvalidInterfaceException
2898
+	 */
2899
+	public function display_admin_list_table_page_with_no_sidebar()
2900
+	{
2901
+		$this->_display_admin_list_table_page();
2902
+	}
2903
+
2904
+
2905
+	/**
2906
+	 * generates html wrapper for an admin_list_table page
2907
+	 *
2908
+	 * @param bool $sidebar whether to display with sidebar or not.
2909
+	 * @return void
2910
+	 * @throws DomainException
2911
+	 * @throws EE_Error
2912
+	 * @throws InvalidArgumentException
2913
+	 * @throws InvalidDataTypeException
2914
+	 * @throws InvalidInterfaceException
2915
+	 */
2916
+	private function _display_admin_list_table_page(bool $sidebar = false)
2917
+	{
2918
+		// setup search attributes
2919
+		$this->_set_search_attributes();
2920
+		$this->_template_args['current_page']     = $this->_wp_page_slug;
2921
+		$template_path                            = EE_ADMIN_TEMPLATE . 'admin_list_wrapper.template.php';
2922
+		$this->_template_args['table_url']        = $this->request->isAjax()
2923
+			? add_query_arg(['noheader' => 'true', 'route' => $this->_req_action], $this->_admin_base_url)
2924
+			: add_query_arg(['route' => $this->_req_action], $this->_admin_base_url);
2925
+		$this->_template_args['list_table']       = $this->_list_table_object;
2926
+		$this->_template_args['current_route']    = $this->_req_action;
2927
+		$this->_template_args['list_table_class'] = get_class($this->_list_table_object);
2928
+		$ajax_sorting_callback                    = $this->_list_table_object->get_ajax_sorting_callback();
2929
+		if (! empty($ajax_sorting_callback)) {
2930
+			$sortable_list_table_form_fields = wp_nonce_field(
2931
+				$ajax_sorting_callback . '_nonce',
2932
+				$ajax_sorting_callback . '_nonce',
2933
+				false,
2934
+				false
2935
+			);
2936
+			$sortable_list_table_form_fields .= '<input type="hidden" id="ajax_table_sort_page" name="ajax_table_sort_page" value="'
2937
+												. $this->page_slug
2938
+												. '" />';
2939
+			$sortable_list_table_form_fields .= '<input type="hidden" id="ajax_table_sort_action" name="ajax_table_sort_action" value="'
2940
+												. $ajax_sorting_callback
2941
+												. '" />';
2942
+		} else {
2943
+			$sortable_list_table_form_fields = '';
2944
+		}
2945
+		$this->_template_args['sortable_list_table_form_fields'] = $sortable_list_table_form_fields;
2946
+
2947
+		$hidden_form_fields = $this->_template_args['list_table_hidden_fields'] ?? '';
2948
+
2949
+		$nonce_ref          = $this->_req_action . '_nonce';
2950
+		$hidden_form_fields .= '
2951 2951
             <input type="hidden" name="' . $nonce_ref . '" value="' . wp_create_nonce($nonce_ref) . '">';
2952 2952
 
2953
-        $this->_template_args['list_table_hidden_fields'] = $hidden_form_fields;
2954
-        // display message about search results?
2955
-        $search                                    = $this->request->getRequestParam('s');
2956
-        $this->_template_args['before_list_table'] .= ! empty($search)
2957
-            ? '<p class="ee-search-results">' . sprintf(
2958
-                esc_html__('Displaying search results for the search string: %1$s', 'event_espresso'),
2959
-                trim($search, '%')
2960
-            ) . '</p>'
2961
-            : '';
2962
-        // filter before_list_table template arg
2963
-        $this->_template_args['before_list_table'] = apply_filters(
2964
-            'FHEE__EE_Admin_Page___display_admin_list_table_page__before_list_table__template_arg',
2965
-            $this->_template_args['before_list_table'],
2966
-            $this->page_slug,
2967
-            $this->request->requestParams(),
2968
-            $this->_req_action
2969
-        );
2970
-        // convert to array and filter again
2971
-        // arrays are easier to inject new items in a specific location,
2972
-        // but would not be backwards compatible, so we have to add a new filter
2973
-        $this->_template_args['before_list_table'] = implode(
2974
-            " \n",
2975
-            (array) apply_filters(
2976
-                'FHEE__EE_Admin_Page___display_admin_list_table_page__before_list_table__template_args_array',
2977
-                (array) $this->_template_args['before_list_table'],
2978
-                $this->page_slug,
2979
-                $this->request->requestParams(),
2980
-                $this->_req_action
2981
-            )
2982
-        );
2983
-        // filter after_list_table template arg
2984
-        $this->_template_args['after_list_table'] = apply_filters(
2985
-            'FHEE__EE_Admin_Page___display_admin_list_table_page__after_list_table__template_arg',
2986
-            $this->_template_args['after_list_table'],
2987
-            $this->page_slug,
2988
-            $this->request->requestParams(),
2989
-            $this->_req_action
2990
-        );
2991
-        // convert to array and filter again
2992
-        // arrays are easier to inject new items in a specific location,
2993
-        // but would not be backwards compatible, so we have to add a new filter
2994
-        $this->_template_args['after_list_table']   = implode(
2995
-            " \n",
2996
-            (array) apply_filters(
2997
-                'FHEE__EE_Admin_Page___display_admin_list_table_page__after_list_table__template_args_array',
2998
-                (array) $this->_template_args['after_list_table'],
2999
-                $this->page_slug,
3000
-                $this->request->requestParams(),
3001
-                $this->_req_action
3002
-            )
3003
-        );
3004
-        $this->_template_args['admin_page_content'] = EEH_Template::display_template(
3005
-            $template_path,
3006
-            $this->_template_args,
3007
-            true
3008
-        );
3009
-        // the final template wrapper
3010
-        if ($sidebar) {
3011
-            $this->display_admin_page_with_sidebar();
3012
-        } else {
3013
-            $this->display_admin_page_with_no_sidebar();
3014
-        }
3015
-    }
3016
-
3017
-
3018
-    /**
3019
-     * This just prepares a legend using the given items and the admin_details_legend.template.php file and returns the
3020
-     * html string for the legend.
3021
-     * $items are expected in an array in the following format:
3022
-     * $legend_items = array(
3023
-     *        'item_id' => array(
3024
-     *            'icon' => 'http://url_to_icon_being_described.png',
3025
-     *            'desc' => esc_html__('localized description of item');
3026
-     *        )
3027
-     * );
3028
-     *
3029
-     * @param array $items see above for format of array
3030
-     * @return string html string of legend
3031
-     * @throws DomainException
3032
-     */
3033
-    protected function _display_legend(array $items): string
3034
-    {
3035
-        $this->_template_args['items'] = (array) apply_filters(
3036
-            'FHEE__EE_Admin_Page___display_legend__items',
3037
-            $items,
3038
-            $this
3039
-        );
3040
-        /** @var StatusChangeNotice $status_change_notice */
3041
-        $status_change_notice                         = $this->loader->getShared(
3042
-            'EventEspresso\core\domain\services\admin\notices\status_change\StatusChangeNotice'
3043
-        );
3044
-        $this->_template_args['status_change_notice'] = $status_change_notice->display(
3045
-            '__admin-legend',
3046
-            $this->page_slug
3047
-        );
3048
-        return EEH_Template::display_template(
3049
-            EE_ADMIN_TEMPLATE . 'admin_details_legend.template.php',
3050
-            $this->_template_args,
3051
-            true
3052
-        );
3053
-    }
3054
-
3055
-
3056
-    /**
3057
-     * This is used whenever we're DOING_AJAX to return a formatted json array that our calling javascript can expect
3058
-     * The returned json object is created from an array in the following format:
3059
-     * array(
3060
-     *  'error' => FALSE, //(default FALSE), contains any errors and/or exceptions (exceptions return json early),
3061
-     *  'success' => FALSE, //(default FALSE) - contains any special success message.
3062
-     *  'notices' => '', // - contains any EE_Error formatted notices
3063
-     *  'content' => 'string can be html', //this is a string of formatted content (can be html)
3064
-     *  'data' => array() //this can be any key/value pairs that a method returns for later json parsing by the js.
3065
-     *  We're also going to include the template args with every package (so js can pick out any specific template args
3066
-     *  that might be included in here)
3067
-     * )
3068
-     * The json object is populated by whatever is set in the $_template_args property.
3069
-     *
3070
-     * @param bool  $sticky_notices    Used to indicate whether you want to ensure notices are added to a transient
3071
-     *                                 instead of displayed.
3072
-     * @param array $notices_arguments Use this to pass any additional args on to the _process_notices.
3073
-     * @return void
3074
-     * @throws EE_Error
3075
-     * @throws InvalidArgumentException
3076
-     * @throws InvalidDataTypeException
3077
-     * @throws InvalidInterfaceException
3078
-     */
3079
-    protected function _return_json(bool $sticky_notices = false, array $notices_arguments = [])
3080
-    {
3081
-        // make sure any EE_Error notices have been handled.
3082
-        $this->_process_notices($notices_arguments, true, $sticky_notices);
3083
-        $data = $this->_template_args['data'] ?? [];
3084
-        unset($this->_template_args['data']);
3085
-        $json = [
3086
-            'error'     => $this->_template_args['error'] ?? false,
3087
-            'success'   => $this->_template_args['success'] ?? false,
3088
-            'errors'    => $this->_template_args['errors'] ?? false,
3089
-            'attention' => $this->_template_args['attention'] ?? false,
3090
-            'notices'   => EE_Error::get_notices(),
3091
-            'content'   => $this->_template_args['admin_page_content'] ?? '',
3092
-            'data'      => array_merge($data, ['template_args' => $this->_template_args]),
3093
-            'isEEajax'  => true,
3094
-            // special flag so any ajax.Success methods in js can identify this return package as a EEajax package.
3095
-        ];
3096
-        // make sure there are no php errors or headers_sent.  Then we can set correct json header.
3097
-        if (null === error_get_last() || ! headers_sent()) {
3098
-            header('Content-Type: application/json; charset=UTF-8');
3099
-        }
3100
-        echo wp_json_encode($json);
3101
-        exit();
3102
-    }
3103
-
3104
-
3105
-    /**
3106
-     * Simply a wrapper for the protected method so we can call this outside the class (ONLY when doing ajax)
3107
-     *
3108
-     * @return void
3109
-     * @throws EE_Error
3110
-     * @throws InvalidArgumentException
3111
-     * @throws InvalidDataTypeException
3112
-     * @throws InvalidInterfaceException
3113
-     */
3114
-    public function return_json()
3115
-    {
3116
-        if ($this->request->isAjax()) {
3117
-            $this->_return_json();
3118
-        } else {
3119
-            throw new EE_Error(
3120
-                sprintf(
3121
-                    esc_html__('The public %s method can only be called when DOING_AJAX = TRUE', 'event_espresso'),
3122
-                    __FUNCTION__
3123
-                )
3124
-            );
3125
-        }
3126
-    }
3127
-
3128
-
3129
-    /**
3130
-     * This provides a way for child hook classes to send along themselves by reference so methods/properties within
3131
-     * them can be accessed by EE_Admin_child pages. This is assigned to the $_hook_obj property.
3132
-     *
3133
-     * @param EE_Admin_Hooks $hook_obj This will be the object for the EE_Admin_Hooks child
3134
-     * @deprecated  5.0.8.p
3135
-     */
3136
-    public function set_hook_object(EE_Admin_Hooks $hook_obj)
3137
-    {
3138
-        $this->_hook_obj = $hook_obj;
3139
-    }
3140
-
3141
-
3142
-    /**
3143
-     *        generates  HTML wrapper with Tabbed nav for an admin page
3144
-     *
3145
-     * @param bool $about whether to use the special about page wrapper or default.
3146
-     * @return void
3147
-     * @throws DomainException
3148
-     * @throws EE_Error
3149
-     * @throws InvalidArgumentException
3150
-     * @throws InvalidDataTypeException
3151
-     * @throws InvalidInterfaceException
3152
-     */
3153
-    public function admin_page_wrapper(bool $about = false)
3154
-    {
3155
-        $this->_template_args['nav_tabs']         = $this->_get_main_nav_tabs();
3156
-        $this->_template_args['admin_page_title'] = $this->_admin_page_title;
3157
-
3158
-        $this->_template_args['before_admin_page_content'] = apply_filters(
3159
-            "FHEE_before_admin_page_content$this->_current_page$this->_current_view",
3160
-            $this->_template_args['before_admin_page_content'] ?? ''
3161
-        );
3162
-
3163
-        $this->_template_args['after_admin_page_content'] = apply_filters(
3164
-            "FHEE_after_admin_page_content$this->_current_page$this->_current_view",
3165
-            $this->_template_args['after_admin_page_content'] ?? ''
3166
-        );
3167
-        $this->_template_args['after_admin_page_content'] .= $this->_set_help_popup_content();
3168
-
3169
-        if ($this->request->isAjax()) {
3170
-            $this->_template_args['admin_page_content'] = EEH_Template::display_template(
3171
-            // $template_path,
3172
-                EE_ADMIN_TEMPLATE . 'admin_wrapper_ajax.template.php',
3173
-                $this->_template_args,
3174
-                true
3175
-            );
3176
-            $this->_return_json();
3177
-        }
3178
-        // load settings page wrapper template
3179
-        $template_path = $about
3180
-            ? EE_ADMIN_TEMPLATE . 'about_admin_wrapper.template.php'
3181
-            : EE_ADMIN_TEMPLATE . 'admin_wrapper.template.php';
3182
-
3183
-        EEH_Template::display_template($template_path, $this->_template_args);
3184
-    }
3185
-
3186
-
3187
-    /**
3188
-     * This returns the admin_nav tabs html using the configuration in the _nav_tabs property
3189
-     *
3190
-     * @return string html
3191
-     * @throws EE_Error
3192
-     */
3193
-    protected function _get_main_nav_tabs(): string
3194
-    {
3195
-        // let's generate the html using the EEH_Tabbed_Content helper.
3196
-        // We do this here so that it's possible for child classes to add in nav tabs dynamically at the last minute
3197
-        // (rather than setting in the page_routes array)
3198
-        return EEH_Tabbed_Content::display_admin_nav_tabs($this->_nav_tabs, $this->page_slug);
3199
-    }
3200
-
3201
-
3202
-    /**
3203
-     *        sort nav tabs
3204
-     *
3205
-     * @param array $a
3206
-     * @param array $b
3207
-     * @return int
3208
-     */
3209
-    private function _sort_nav_tabs(array $a, array $b): int
3210
-    {
3211
-        if ($a['order'] === $b['order']) {
3212
-            return 0;
3213
-        }
3214
-        return ($a['order'] < $b['order']) ? -1 : 1;
3215
-    }
3216
-
3217
-
3218
-    /**
3219
-     * generates HTML for the forms used on admin pages
3220
-     *
3221
-     * @param array  $input_vars - array of input field details
3222
-     * @param string $generator  indicates which generator to use: options are 'string' or 'array'
3223
-     * @param bool   $id
3224
-     * @return array|string
3225
-     * @uses   EEH_Form_Fields::get_form_fields (/helper/EEH_Form_Fields.helper.php)
3226
-     * @uses   EEH_Form_Fields::get_form_fields_array (/helper/EEH_Form_Fields.helper.php)
3227
-     */
3228
-    protected function _generate_admin_form_fields(
3229
-        array $input_vars = [],
3230
-        string $generator = 'string',
3231
-        bool $id = false
3232
-    ) {
3233
-        return $generator === 'string'
3234
-            ? EEH_Form_Fields::get_form_fields($input_vars, $id)
3235
-            : EEH_Form_Fields::get_form_fields_array($input_vars);
3236
-    }
3237
-
3238
-
3239
-    /**
3240
-     * generates the "Save" and "Save & Close" buttons for edit forms
3241
-     *
3242
-     * @param bool             $both     if true then both buttons will be generated.  If false then just the "Save &
3243
-     *                                   Close" button.
3244
-     * @param array            $text     if included, generator will use the given text for the buttons ( array([0] =>
3245
-     *                                   'Save', [1] => 'save & close')
3246
-     * @param array            $actions  if included allows us to set the actions that each button will carry out (i.e.
3247
-     *                                   via the "name" value in the button).  We can also use this to just dump
3248
-     *                                   default actions by submitting some other value.
3249
-     * @param bool|string|null $referrer if false then we just do the default action on save and close.  Otherwise it
3250
-     *                                   will use the $referrer string. IF null, then we don't do ANYTHING on save and
3251
-     *                                   close (normal form handling).
3252
-     */
3253
-    protected function _set_save_buttons(bool $both = true, array $text = [], array $actions = [], $referrer = null)
3254
-    {
3255
-        $referrer_url  = ! empty($referrer) ? $referrer : $this->request->getServerParam('REQUEST_URI');
3256
-        $button_text   = ! empty($text)
3257
-            ? $text
3258
-            : [
3259
-                esc_html__('Save', 'event_espresso'),
3260
-                esc_html__('Save and Close', 'event_espresso'),
3261
-            ];
3262
-        $default_names = ['save', 'save_and_close'];
3263
-        $buttons       = '';
3264
-        foreach ($button_text as $key => $button) {
3265
-            $ref     = $default_names[ $key ];
3266
-            $name    = ! empty($actions) ? $actions[ $key ] : $ref;
3267
-            $buttons .= '<input type="submit" class="button button--primary ' . $ref . '" '
3268
-                        . 'value="' . $button . '" name="' . $name . '" '
3269
-                        . 'id="' . $this->_current_view . '_' . $ref . '" />';
3270
-            if (! $both) {
3271
-                break;
3272
-            }
3273
-        }
3274
-        // add in a hidden index for the current page (so save and close redirects properly)
3275
-        $buttons .= '<input type="hidden" id="save_and_close_referrer" name="save_and_close_referrer" value="'
3276
-                    . $referrer_url
3277
-                    . '" />';
3278
-
3279
-        $this->_template_args['save_buttons'] = $buttons;
3280
-    }
3281
-
3282
-
3283
-    /**
3284
-     * Wrapper for the protected function.  Allows plugins/addons to call this to set the form tags.
3285
-     *
3286
-     * @param string $route
3287
-     * @param array  $additional_hidden_fields
3288
-     * @see   $this->_set_add_edit_form_tags() for details on params
3289
-     * @since 4.6.0
3290
-     */
3291
-    public function set_add_edit_form_tags(string $route = '', array $additional_hidden_fields = [])
3292
-    {
3293
-        $this->_set_add_edit_form_tags($route, $additional_hidden_fields);
3294
-    }
3295
-
3296
-
3297
-    /**
3298
-     * set form open and close tags on add/edit pages.
3299
-     *
3300
-     * @param string $route                    the route you want the form to direct to
3301
-     * @param array  $additional_hidden_fields any additional hidden fields required in the form header
3302
-     * @return void
3303
-     */
3304
-    protected function _set_add_edit_form_tags(string $route = '', array $additional_hidden_fields = [])
3305
-    {
3306
-        if (empty($route)) {
3307
-            $user_msg = esc_html__(
3308
-                'An error occurred. No action was set for this page\'s form.',
3309
-                'event_espresso'
3310
-            );
3311
-            $dev_msg  = $user_msg . "\n"
3312
-                        . sprintf(
3313
-                            esc_html__('The $route argument is required for the %s->%s method.', 'event_espresso'),
3314
-                            __FUNCTION__,
3315
-                            __CLASS__
3316
-                        );
3317
-            EE_Error::add_error($user_msg . '||' . $dev_msg, __FILE__, __FUNCTION__, __LINE__);
3318
-        }
3319
-        // open form
3320
-        $action                                            = $this->_admin_base_url;
3321
-        $this->_template_args['before_admin_page_content'] = "
2953
+		$this->_template_args['list_table_hidden_fields'] = $hidden_form_fields;
2954
+		// display message about search results?
2955
+		$search                                    = $this->request->getRequestParam('s');
2956
+		$this->_template_args['before_list_table'] .= ! empty($search)
2957
+			? '<p class="ee-search-results">' . sprintf(
2958
+				esc_html__('Displaying search results for the search string: %1$s', 'event_espresso'),
2959
+				trim($search, '%')
2960
+			) . '</p>'
2961
+			: '';
2962
+		// filter before_list_table template arg
2963
+		$this->_template_args['before_list_table'] = apply_filters(
2964
+			'FHEE__EE_Admin_Page___display_admin_list_table_page__before_list_table__template_arg',
2965
+			$this->_template_args['before_list_table'],
2966
+			$this->page_slug,
2967
+			$this->request->requestParams(),
2968
+			$this->_req_action
2969
+		);
2970
+		// convert to array and filter again
2971
+		// arrays are easier to inject new items in a specific location,
2972
+		// but would not be backwards compatible, so we have to add a new filter
2973
+		$this->_template_args['before_list_table'] = implode(
2974
+			" \n",
2975
+			(array) apply_filters(
2976
+				'FHEE__EE_Admin_Page___display_admin_list_table_page__before_list_table__template_args_array',
2977
+				(array) $this->_template_args['before_list_table'],
2978
+				$this->page_slug,
2979
+				$this->request->requestParams(),
2980
+				$this->_req_action
2981
+			)
2982
+		);
2983
+		// filter after_list_table template arg
2984
+		$this->_template_args['after_list_table'] = apply_filters(
2985
+			'FHEE__EE_Admin_Page___display_admin_list_table_page__after_list_table__template_arg',
2986
+			$this->_template_args['after_list_table'],
2987
+			$this->page_slug,
2988
+			$this->request->requestParams(),
2989
+			$this->_req_action
2990
+		);
2991
+		// convert to array and filter again
2992
+		// arrays are easier to inject new items in a specific location,
2993
+		// but would not be backwards compatible, so we have to add a new filter
2994
+		$this->_template_args['after_list_table']   = implode(
2995
+			" \n",
2996
+			(array) apply_filters(
2997
+				'FHEE__EE_Admin_Page___display_admin_list_table_page__after_list_table__template_args_array',
2998
+				(array) $this->_template_args['after_list_table'],
2999
+				$this->page_slug,
3000
+				$this->request->requestParams(),
3001
+				$this->_req_action
3002
+			)
3003
+		);
3004
+		$this->_template_args['admin_page_content'] = EEH_Template::display_template(
3005
+			$template_path,
3006
+			$this->_template_args,
3007
+			true
3008
+		);
3009
+		// the final template wrapper
3010
+		if ($sidebar) {
3011
+			$this->display_admin_page_with_sidebar();
3012
+		} else {
3013
+			$this->display_admin_page_with_no_sidebar();
3014
+		}
3015
+	}
3016
+
3017
+
3018
+	/**
3019
+	 * This just prepares a legend using the given items and the admin_details_legend.template.php file and returns the
3020
+	 * html string for the legend.
3021
+	 * $items are expected in an array in the following format:
3022
+	 * $legend_items = array(
3023
+	 *        'item_id' => array(
3024
+	 *            'icon' => 'http://url_to_icon_being_described.png',
3025
+	 *            'desc' => esc_html__('localized description of item');
3026
+	 *        )
3027
+	 * );
3028
+	 *
3029
+	 * @param array $items see above for format of array
3030
+	 * @return string html string of legend
3031
+	 * @throws DomainException
3032
+	 */
3033
+	protected function _display_legend(array $items): string
3034
+	{
3035
+		$this->_template_args['items'] = (array) apply_filters(
3036
+			'FHEE__EE_Admin_Page___display_legend__items',
3037
+			$items,
3038
+			$this
3039
+		);
3040
+		/** @var StatusChangeNotice $status_change_notice */
3041
+		$status_change_notice                         = $this->loader->getShared(
3042
+			'EventEspresso\core\domain\services\admin\notices\status_change\StatusChangeNotice'
3043
+		);
3044
+		$this->_template_args['status_change_notice'] = $status_change_notice->display(
3045
+			'__admin-legend',
3046
+			$this->page_slug
3047
+		);
3048
+		return EEH_Template::display_template(
3049
+			EE_ADMIN_TEMPLATE . 'admin_details_legend.template.php',
3050
+			$this->_template_args,
3051
+			true
3052
+		);
3053
+	}
3054
+
3055
+
3056
+	/**
3057
+	 * This is used whenever we're DOING_AJAX to return a formatted json array that our calling javascript can expect
3058
+	 * The returned json object is created from an array in the following format:
3059
+	 * array(
3060
+	 *  'error' => FALSE, //(default FALSE), contains any errors and/or exceptions (exceptions return json early),
3061
+	 *  'success' => FALSE, //(default FALSE) - contains any special success message.
3062
+	 *  'notices' => '', // - contains any EE_Error formatted notices
3063
+	 *  'content' => 'string can be html', //this is a string of formatted content (can be html)
3064
+	 *  'data' => array() //this can be any key/value pairs that a method returns for later json parsing by the js.
3065
+	 *  We're also going to include the template args with every package (so js can pick out any specific template args
3066
+	 *  that might be included in here)
3067
+	 * )
3068
+	 * The json object is populated by whatever is set in the $_template_args property.
3069
+	 *
3070
+	 * @param bool  $sticky_notices    Used to indicate whether you want to ensure notices are added to a transient
3071
+	 *                                 instead of displayed.
3072
+	 * @param array $notices_arguments Use this to pass any additional args on to the _process_notices.
3073
+	 * @return void
3074
+	 * @throws EE_Error
3075
+	 * @throws InvalidArgumentException
3076
+	 * @throws InvalidDataTypeException
3077
+	 * @throws InvalidInterfaceException
3078
+	 */
3079
+	protected function _return_json(bool $sticky_notices = false, array $notices_arguments = [])
3080
+	{
3081
+		// make sure any EE_Error notices have been handled.
3082
+		$this->_process_notices($notices_arguments, true, $sticky_notices);
3083
+		$data = $this->_template_args['data'] ?? [];
3084
+		unset($this->_template_args['data']);
3085
+		$json = [
3086
+			'error'     => $this->_template_args['error'] ?? false,
3087
+			'success'   => $this->_template_args['success'] ?? false,
3088
+			'errors'    => $this->_template_args['errors'] ?? false,
3089
+			'attention' => $this->_template_args['attention'] ?? false,
3090
+			'notices'   => EE_Error::get_notices(),
3091
+			'content'   => $this->_template_args['admin_page_content'] ?? '',
3092
+			'data'      => array_merge($data, ['template_args' => $this->_template_args]),
3093
+			'isEEajax'  => true,
3094
+			// special flag so any ajax.Success methods in js can identify this return package as a EEajax package.
3095
+		];
3096
+		// make sure there are no php errors or headers_sent.  Then we can set correct json header.
3097
+		if (null === error_get_last() || ! headers_sent()) {
3098
+			header('Content-Type: application/json; charset=UTF-8');
3099
+		}
3100
+		echo wp_json_encode($json);
3101
+		exit();
3102
+	}
3103
+
3104
+
3105
+	/**
3106
+	 * Simply a wrapper for the protected method so we can call this outside the class (ONLY when doing ajax)
3107
+	 *
3108
+	 * @return void
3109
+	 * @throws EE_Error
3110
+	 * @throws InvalidArgumentException
3111
+	 * @throws InvalidDataTypeException
3112
+	 * @throws InvalidInterfaceException
3113
+	 */
3114
+	public function return_json()
3115
+	{
3116
+		if ($this->request->isAjax()) {
3117
+			$this->_return_json();
3118
+		} else {
3119
+			throw new EE_Error(
3120
+				sprintf(
3121
+					esc_html__('The public %s method can only be called when DOING_AJAX = TRUE', 'event_espresso'),
3122
+					__FUNCTION__
3123
+				)
3124
+			);
3125
+		}
3126
+	}
3127
+
3128
+
3129
+	/**
3130
+	 * This provides a way for child hook classes to send along themselves by reference so methods/properties within
3131
+	 * them can be accessed by EE_Admin_child pages. This is assigned to the $_hook_obj property.
3132
+	 *
3133
+	 * @param EE_Admin_Hooks $hook_obj This will be the object for the EE_Admin_Hooks child
3134
+	 * @deprecated  5.0.8.p
3135
+	 */
3136
+	public function set_hook_object(EE_Admin_Hooks $hook_obj)
3137
+	{
3138
+		$this->_hook_obj = $hook_obj;
3139
+	}
3140
+
3141
+
3142
+	/**
3143
+	 *        generates  HTML wrapper with Tabbed nav for an admin page
3144
+	 *
3145
+	 * @param bool $about whether to use the special about page wrapper or default.
3146
+	 * @return void
3147
+	 * @throws DomainException
3148
+	 * @throws EE_Error
3149
+	 * @throws InvalidArgumentException
3150
+	 * @throws InvalidDataTypeException
3151
+	 * @throws InvalidInterfaceException
3152
+	 */
3153
+	public function admin_page_wrapper(bool $about = false)
3154
+	{
3155
+		$this->_template_args['nav_tabs']         = $this->_get_main_nav_tabs();
3156
+		$this->_template_args['admin_page_title'] = $this->_admin_page_title;
3157
+
3158
+		$this->_template_args['before_admin_page_content'] = apply_filters(
3159
+			"FHEE_before_admin_page_content$this->_current_page$this->_current_view",
3160
+			$this->_template_args['before_admin_page_content'] ?? ''
3161
+		);
3162
+
3163
+		$this->_template_args['after_admin_page_content'] = apply_filters(
3164
+			"FHEE_after_admin_page_content$this->_current_page$this->_current_view",
3165
+			$this->_template_args['after_admin_page_content'] ?? ''
3166
+		);
3167
+		$this->_template_args['after_admin_page_content'] .= $this->_set_help_popup_content();
3168
+
3169
+		if ($this->request->isAjax()) {
3170
+			$this->_template_args['admin_page_content'] = EEH_Template::display_template(
3171
+			// $template_path,
3172
+				EE_ADMIN_TEMPLATE . 'admin_wrapper_ajax.template.php',
3173
+				$this->_template_args,
3174
+				true
3175
+			);
3176
+			$this->_return_json();
3177
+		}
3178
+		// load settings page wrapper template
3179
+		$template_path = $about
3180
+			? EE_ADMIN_TEMPLATE . 'about_admin_wrapper.template.php'
3181
+			: EE_ADMIN_TEMPLATE . 'admin_wrapper.template.php';
3182
+
3183
+		EEH_Template::display_template($template_path, $this->_template_args);
3184
+	}
3185
+
3186
+
3187
+	/**
3188
+	 * This returns the admin_nav tabs html using the configuration in the _nav_tabs property
3189
+	 *
3190
+	 * @return string html
3191
+	 * @throws EE_Error
3192
+	 */
3193
+	protected function _get_main_nav_tabs(): string
3194
+	{
3195
+		// let's generate the html using the EEH_Tabbed_Content helper.
3196
+		// We do this here so that it's possible for child classes to add in nav tabs dynamically at the last minute
3197
+		// (rather than setting in the page_routes array)
3198
+		return EEH_Tabbed_Content::display_admin_nav_tabs($this->_nav_tabs, $this->page_slug);
3199
+	}
3200
+
3201
+
3202
+	/**
3203
+	 *        sort nav tabs
3204
+	 *
3205
+	 * @param array $a
3206
+	 * @param array $b
3207
+	 * @return int
3208
+	 */
3209
+	private function _sort_nav_tabs(array $a, array $b): int
3210
+	{
3211
+		if ($a['order'] === $b['order']) {
3212
+			return 0;
3213
+		}
3214
+		return ($a['order'] < $b['order']) ? -1 : 1;
3215
+	}
3216
+
3217
+
3218
+	/**
3219
+	 * generates HTML for the forms used on admin pages
3220
+	 *
3221
+	 * @param array  $input_vars - array of input field details
3222
+	 * @param string $generator  indicates which generator to use: options are 'string' or 'array'
3223
+	 * @param bool   $id
3224
+	 * @return array|string
3225
+	 * @uses   EEH_Form_Fields::get_form_fields (/helper/EEH_Form_Fields.helper.php)
3226
+	 * @uses   EEH_Form_Fields::get_form_fields_array (/helper/EEH_Form_Fields.helper.php)
3227
+	 */
3228
+	protected function _generate_admin_form_fields(
3229
+		array $input_vars = [],
3230
+		string $generator = 'string',
3231
+		bool $id = false
3232
+	) {
3233
+		return $generator === 'string'
3234
+			? EEH_Form_Fields::get_form_fields($input_vars, $id)
3235
+			: EEH_Form_Fields::get_form_fields_array($input_vars);
3236
+	}
3237
+
3238
+
3239
+	/**
3240
+	 * generates the "Save" and "Save & Close" buttons for edit forms
3241
+	 *
3242
+	 * @param bool             $both     if true then both buttons will be generated.  If false then just the "Save &
3243
+	 *                                   Close" button.
3244
+	 * @param array            $text     if included, generator will use the given text for the buttons ( array([0] =>
3245
+	 *                                   'Save', [1] => 'save & close')
3246
+	 * @param array            $actions  if included allows us to set the actions that each button will carry out (i.e.
3247
+	 *                                   via the "name" value in the button).  We can also use this to just dump
3248
+	 *                                   default actions by submitting some other value.
3249
+	 * @param bool|string|null $referrer if false then we just do the default action on save and close.  Otherwise it
3250
+	 *                                   will use the $referrer string. IF null, then we don't do ANYTHING on save and
3251
+	 *                                   close (normal form handling).
3252
+	 */
3253
+	protected function _set_save_buttons(bool $both = true, array $text = [], array $actions = [], $referrer = null)
3254
+	{
3255
+		$referrer_url  = ! empty($referrer) ? $referrer : $this->request->getServerParam('REQUEST_URI');
3256
+		$button_text   = ! empty($text)
3257
+			? $text
3258
+			: [
3259
+				esc_html__('Save', 'event_espresso'),
3260
+				esc_html__('Save and Close', 'event_espresso'),
3261
+			];
3262
+		$default_names = ['save', 'save_and_close'];
3263
+		$buttons       = '';
3264
+		foreach ($button_text as $key => $button) {
3265
+			$ref     = $default_names[ $key ];
3266
+			$name    = ! empty($actions) ? $actions[ $key ] : $ref;
3267
+			$buttons .= '<input type="submit" class="button button--primary ' . $ref . '" '
3268
+						. 'value="' . $button . '" name="' . $name . '" '
3269
+						. 'id="' . $this->_current_view . '_' . $ref . '" />';
3270
+			if (! $both) {
3271
+				break;
3272
+			}
3273
+		}
3274
+		// add in a hidden index for the current page (so save and close redirects properly)
3275
+		$buttons .= '<input type="hidden" id="save_and_close_referrer" name="save_and_close_referrer" value="'
3276
+					. $referrer_url
3277
+					. '" />';
3278
+
3279
+		$this->_template_args['save_buttons'] = $buttons;
3280
+	}
3281
+
3282
+
3283
+	/**
3284
+	 * Wrapper for the protected function.  Allows plugins/addons to call this to set the form tags.
3285
+	 *
3286
+	 * @param string $route
3287
+	 * @param array  $additional_hidden_fields
3288
+	 * @see   $this->_set_add_edit_form_tags() for details on params
3289
+	 * @since 4.6.0
3290
+	 */
3291
+	public function set_add_edit_form_tags(string $route = '', array $additional_hidden_fields = [])
3292
+	{
3293
+		$this->_set_add_edit_form_tags($route, $additional_hidden_fields);
3294
+	}
3295
+
3296
+
3297
+	/**
3298
+	 * set form open and close tags on add/edit pages.
3299
+	 *
3300
+	 * @param string $route                    the route you want the form to direct to
3301
+	 * @param array  $additional_hidden_fields any additional hidden fields required in the form header
3302
+	 * @return void
3303
+	 */
3304
+	protected function _set_add_edit_form_tags(string $route = '', array $additional_hidden_fields = [])
3305
+	{
3306
+		if (empty($route)) {
3307
+			$user_msg = esc_html__(
3308
+				'An error occurred. No action was set for this page\'s form.',
3309
+				'event_espresso'
3310
+			);
3311
+			$dev_msg  = $user_msg . "\n"
3312
+						. sprintf(
3313
+							esc_html__('The $route argument is required for the %s->%s method.', 'event_espresso'),
3314
+							__FUNCTION__,
3315
+							__CLASS__
3316
+						);
3317
+			EE_Error::add_error($user_msg . '||' . $dev_msg, __FILE__, __FUNCTION__, __LINE__);
3318
+		}
3319
+		// open form
3320
+		$action                                            = $this->_admin_base_url;
3321
+		$this->_template_args['before_admin_page_content'] = "
3322 3322
             <form name='form' method='post' action='$action' id='{$route}_event_form' class='ee-admin-page-form' >
3323 3323
             ";
3324
-        // add nonce
3325
-        $nonce                                             =
3326
-            wp_nonce_field($route . '_nonce', $route . '_nonce', false, false);
3327
-        $this->_template_args['before_admin_page_content'] .= "\n\t" . $nonce;
3328
-        // add REQUIRED form action
3329
-        $hidden_fields = [
3330
-            'action' => ['type' => 'hidden', 'value' => $route],
3331
-        ];
3332
-        // merge arrays
3333
-        $hidden_fields = is_array($additional_hidden_fields)
3334
-            ? array_merge($hidden_fields, $additional_hidden_fields)
3335
-            : $hidden_fields;
3336
-        // generate form fields
3337
-        $form_fields = $this->_generate_admin_form_fields($hidden_fields, 'array');
3338
-        // add fields to form
3339
-        foreach ((array) $form_fields as $form_field) {
3340
-            $this->_template_args['before_admin_page_content'] .= "\n\t" . $form_field['field'];
3341
-        }
3342
-        // close form
3343
-        $this->_template_args['after_admin_page_content'] = '</form>';
3344
-    }
3345
-
3346
-
3347
-    /**
3348
-     * Public Wrapper for _redirect_after_action() method since its
3349
-     * discovered it would be useful for external code to have access.
3350
-     *
3351
-     * @param bool|int $success
3352
-     * @param string   $what
3353
-     * @param string   $action_desc
3354
-     * @param array    $query_args
3355
-     * @param bool     $override_overwrite
3356
-     * @throws EE_Error
3357
-     * @see   EE_Admin_Page::_redirect_after_action() for params.
3358
-     * @since 4.5.0
3359
-     */
3360
-    public function redirect_after_action(
3361
-        $success = false,
3362
-        string $what = 'item',
3363
-        string $action_desc = 'processed',
3364
-        array $query_args = [],
3365
-        bool $override_overwrite = false
3366
-    ) {
3367
-        $this->_redirect_after_action(
3368
-            $success,
3369
-            $what,
3370
-            $action_desc,
3371
-            $query_args,
3372
-            $override_overwrite
3373
-        );
3374
-    }
3375
-
3376
-
3377
-    /**
3378
-     * Helper method for merging existing request data with the returned redirect url.
3379
-     * This is typically used for redirects after an action so that if the original view was a filtered view those
3380
-     * filters are still applied.
3381
-     *
3382
-     * @param array $new_route_data
3383
-     * @return array
3384
-     */
3385
-    protected function mergeExistingRequestParamsWithRedirectArgs(array $new_route_data): array
3386
-    {
3387
-        foreach ($this->request->requestParams() as $ref => $value) {
3388
-            // unset nonces
3389
-            if (strpos($ref, 'nonce') !== false) {
3390
-                $this->request->unSetRequestParam($ref);
3391
-                continue;
3392
-            }
3393
-            // urlencode values.
3394
-            $value = is_array($value) ? array_map('urlencode', $value) : urlencode($value);
3395
-            $this->request->setRequestParam($ref, $value);
3396
-        }
3397
-        return array_merge($this->request->requestParams(), $new_route_data);
3398
-    }
3399
-
3400
-
3401
-    /**
3402
-     * @param int|float|string $success            - whether success was for two or more records, or just one, or none
3403
-     * @param string           $what               - what the action was performed on
3404
-     * @param string           $action_desc        - what was done ie: updated, deleted, etc
3405
-     * @param array            $query_args         - an array of query_args to be added to the URL to redirect to
3406
-     * @param BOOL             $override_overwrite - by default all EE_Error::success messages are overwritten,
3407
-     *                                             this allows you to override this so that they show.
3408
-     * @return void
3409
-     * @throws EE_Error
3410
-     * @throws InvalidArgumentException
3411
-     * @throws InvalidDataTypeException
3412
-     * @throws InvalidInterfaceException
3413
-     */
3414
-    protected function _redirect_after_action(
3415
-        $success = 0,
3416
-        string $what = 'item',
3417
-        string $action_desc = 'processed',
3418
-        array $query_args = [],
3419
-        bool $override_overwrite = false
3420
-    ) {
3421
-        $notices = EE_Error::get_notices(false);
3422
-        // overwrite default success messages //BUT ONLY if overwrite not overridden
3423
-        if (! $override_overwrite || ! empty($notices['errors'])) {
3424
-            EE_Error::overwrite_success();
3425
-        }
3426
-        if (! $override_overwrite && ! empty($what) && ! empty($action_desc) && empty($notices['errors'])) {
3427
-            // how many records affected ? more than one record ? or just one ?
3428
-            EE_Error::add_success(
3429
-                sprintf(
3430
-                    esc_html(
3431
-                        _n(
3432
-                            'The "%1$s" has been successfully %2$s.',
3433
-                            'The "%1$s" have been successfully %2$s.',
3434
-                            $success,
3435
-                            'event_espresso'
3436
-                        )
3437
-                    ),
3438
-                    $what,
3439
-                    $action_desc
3440
-                ),
3441
-                __FILE__,
3442
-                __FUNCTION__,
3443
-                __LINE__
3444
-            );
3445
-        }
3446
-        // check that $query_args isn't something crazy
3447
-        $query_args = is_array($query_args) ? $query_args : [];
3448
-        /**
3449
-         * Allow injecting actions before the query_args are modified for possible different
3450
-         * redirections on save and close actions
3451
-         *
3452
-         * @param array $query_args       The original query_args array coming into the
3453
-         *                                method.
3454
-         * @since 4.2.0
3455
-         */
3456
-        do_action(
3457
-            "AHEE__{$this->class_name}___redirect_after_action__before_redirect_modification_$this->_req_action",
3458
-            $query_args
3459
-        );
3460
-        // set redirect url.
3461
-        // Note if there is a "page" index in the $query_args then we go with vanilla admin.php route,
3462
-        // otherwise we go with whatever is set as the _admin_base_url
3463
-        $redirect_url = isset($query_args['page']) ? admin_url('admin.php') : $this->_admin_base_url;
3464
-        // calculate where we're going (if we have a "save and close" button pushed)
3465
-        if (
3466
-            $this->request->requestParamIsSet('save_and_close')
3467
-            && $this->request->requestParamIsSet('save_and_close_referrer')
3468
-        ) {
3469
-            // even though we have the save_and_close referrer, we need to parse the url for the action in order to generate a nonce
3470
-            $parsed_url = parse_url($this->request->getRequestParam('save_and_close_referrer', '', DataType::URL));
3471
-            // regenerate query args array from referrer URL
3472
-            parse_str($parsed_url['query'], $query_args);
3473
-            // correct page and action will be in the query args now
3474
-            $redirect_url = admin_url('admin.php');
3475
-        }
3476
-        // merge any default query_args set in _default_route_query_args property
3477
-        if (! empty($this->_default_route_query_args) && ! $this->_is_UI_request) {
3478
-            $args_to_merge = [];
3479
-            foreach ($this->_default_route_query_args as $query_param => $query_value) {
3480
-                // is there a wp_referer array in our _default_route_query_args property?
3481
-                if ($query_param === 'wp_referer') {
3482
-                    $query_value = (array) $query_value;
3483
-                    foreach ($query_value as $reference => $value) {
3484
-                        if (strpos($reference, 'nonce') !== false) {
3485
-                            continue;
3486
-                        }
3487
-                        // finally we will override any arguments in the referer with
3488
-                        // what might be set on the _default_route_query_args array.
3489
-                        if (isset($this->_default_route_query_args[ $reference ])) {
3490
-                            $args_to_merge[ $reference ] = urlencode($this->_default_route_query_args[ $reference ]);
3491
-                        } else {
3492
-                            $args_to_merge[ $reference ] = urlencode($value);
3493
-                        }
3494
-                    }
3495
-                    continue;
3496
-                }
3497
-                $args_to_merge[ $query_param ] = $query_value;
3498
-            }
3499
-            // now let's merge these arguments but override with what was specifically sent in to the
3500
-            // redirect.
3501
-            $query_args = array_merge($args_to_merge, $query_args);
3502
-        }
3503
-        $this->_process_notices($query_args);
3504
-        // generate redirect url
3505
-        // if redirecting to anything other than the main page, add a nonce
3506
-        if (isset($query_args['action'])) {
3507
-            // manually generate wp_nonce and merge that with the query vars
3508
-            // becuz the wp_nonce_url function wrecks havoc on some vars
3509
-            $query_args['_wpnonce'] = wp_create_nonce($query_args['action'] . '_nonce');
3510
-        }
3511
-        // we're adding some hooks and filters in here for processing any things just before redirects
3512
-        // (example: an admin page has done an insert or update and we want to run something after that).
3513
-        do_action('AHEE_redirect_' . $this->class_name . $this->_req_action, $query_args);
3514
-        $redirect_url = apply_filters(
3515
-            'FHEE_redirect_' . $this->class_name . $this->_req_action,
3516
-            EE_Admin_Page::add_query_args_and_nonce($query_args, $redirect_url),
3517
-            $query_args
3518
-        );
3519
-        // check if we're doing ajax.  If we are then lets just return the results and js can handle how it wants.
3520
-        if ($this->request->isAjax()) {
3521
-            $default_data                    = [
3522
-                'close'        => true,
3523
-                'redirect_url' => $redirect_url,
3524
-                'where'        => 'main',
3525
-                'what'         => 'append',
3526
-            ];
3527
-            $this->_template_args['success'] = $success;
3528
-            $this->_template_args['data']    = ! empty($this->_template_args['data']) ? array_merge(
3529
-                $default_data,
3530
-                $this->_template_args['data']
3531
-            ) : $default_data;
3532
-            $this->_return_json();
3533
-        }
3534
-        wp_safe_redirect($redirect_url);
3535
-        exit();
3536
-    }
3537
-
3538
-
3539
-    /**
3540
-     * process any notices before redirecting (or returning ajax request)
3541
-     * This method sets the $this->_template_args['notices'] attribute;
3542
-     *
3543
-     * @param array $query_args         any query args that need to be used for notice transient ('action')
3544
-     * @param bool  $skip_route_verify  This is typically used when we are processing notices REALLY early and
3545
-     *                                  page_routes haven't been defined yet.
3546
-     * @param bool  $sticky_notices     This is used to flag that regardless of whether this is doing_ajax or not, we
3547
-     *                                  still save a transient for the notice.
3548
-     * @return void
3549
-     * @throws EE_Error
3550
-     * @throws InvalidArgumentException
3551
-     * @throws InvalidDataTypeException
3552
-     * @throws InvalidInterfaceException
3553
-     */
3554
-    protected function _process_notices(
3555
-        array $query_args = [],
3556
-        bool $skip_route_verify = false,
3557
-        bool $sticky_notices = true
3558
-    ) {
3559
-        // first let's set individual error properties if doing_ajax and the properties aren't already set.
3560
-        if ($this->request->isAjax()) {
3561
-            $notices = EE_Error::get_notices(false);
3562
-            if (empty($this->_template_args['success'])) {
3563
-                $this->_template_args['success'] = $notices['success'] ?? false;
3564
-            }
3565
-            if (empty($this->_template_args['errors'])) {
3566
-                $this->_template_args['errors'] = $notices['errors'] ?? false;
3567
-            }
3568
-            if (empty($this->_template_args['attention'])) {
3569
-                $this->_template_args['attention'] = $notices['attention'] ?? false;
3570
-            }
3571
-        }
3572
-        $this->_template_args['notices'] = EE_Error::get_notices();
3573
-        // IF this isn't ajax we need to create a transient for the notices using the route (however, overridden if $sticky_notices == true)
3574
-        if (! $this->request->isAjax() || $sticky_notices) {
3575
-            $route = $query_args['action'] ?? 'default';
3576
-            $this->_add_transient(
3577
-                $route,
3578
-                (array) $this->_template_args['notices'],
3579
-                true,
3580
-                $skip_route_verify
3581
-            );
3582
-        }
3583
-    }
3584
-
3585
-
3586
-    /**
3587
-     * get_action_link_or_button
3588
-     * returns the button html for adding, editing, or deleting an item (depending on given type)
3589
-     *
3590
-     * @param string $action        use this to indicate which action the url is generated with.
3591
-     * @param string $type          accepted strings must be defined in the $_labels['button'] array(as the key)
3592
-     *                              property.
3593
-     * @param array  $extra_request if the button requires extra params you can include them in $key=>$value pairs.
3594
-     * @param string $class         Use this to give the class for the button. Defaults to 'button--primary'
3595
-     * @param string $base_url      If this is not provided
3596
-     *                              the _admin_base_url will be used as the default for the button base_url.
3597
-     *                              Otherwise this value will be used.
3598
-     * @param bool   $exclude_nonce If true then no nonce will be in the generated button link.
3599
-     * @return string
3600
-     * @throws InvalidArgumentException
3601
-     * @throws InvalidInterfaceException
3602
-     * @throws InvalidDataTypeException
3603
-     * @throws EE_Error
3604
-     */
3605
-    public function get_action_link_or_button(
3606
-        string $action,
3607
-        string $type = 'add',
3608
-        array $extra_request = [],
3609
-        string $class = 'button button--primary',
3610
-        string $base_url = '',
3611
-        bool $exclude_nonce = false
3612
-    ): string {
3613
-        // first let's validate the action (if $base_url is FALSE otherwise validation will happen further along)
3614
-        if (empty($base_url) && ! isset($this->_page_routes[ $action ])) {
3615
-            throw new EE_Error(
3616
-                sprintf(
3617
-                    esc_html__(
3618
-                        'There is no page route for given action for the button.  This action was given: %s',
3619
-                        'event_espresso'
3620
-                    ),
3621
-                    $action
3622
-                )
3623
-            );
3624
-        }
3625
-        if (! isset($this->_labels['buttons'][ $type ])) {
3626
-            throw new EE_Error(
3627
-                sprintf(
3628
-                    esc_html__(
3629
-                        'There is no label for the given button type (%s). Labels are set in the <code>_page_config</code> property.',
3630
-                        'event_espresso'
3631
-                    ),
3632
-                    $type
3633
-                )
3634
-            );
3635
-        }
3636
-        // finally check user access for this button.
3637
-        $has_access = $this->check_user_access($action, true);
3638
-        if (! $has_access) {
3639
-            return '';
3640
-        }
3641
-        $_base_url  = ! $base_url ? $this->_admin_base_url : $base_url;
3642
-        $query_args = [
3643
-            'action' => $action,
3644
-        ];
3645
-        // merge extra_request args but make sure our original action takes precedence and doesn't get overwritten.
3646
-        if (! empty($extra_request)) {
3647
-            $query_args = array_merge($extra_request, $query_args);
3648
-        }
3649
-        $url = EE_Admin_Page::add_query_args_and_nonce($query_args, $_base_url, false, $exclude_nonce);
3650
-        return EEH_Template::get_button_or_link($url, $this->_labels['buttons'][ $type ], $class);
3651
-    }
3652
-
3653
-
3654
-    /**
3655
-     * _per_page_screen_option
3656
-     * Utility function for adding in a per_page_option in the screen_options_dropdown.
3657
-     *
3658
-     * @return void
3659
-     * @throws InvalidArgumentException
3660
-     * @throws InvalidInterfaceException
3661
-     * @throws InvalidDataTypeException
3662
-     */
3663
-    protected function _per_page_screen_option()
3664
-    {
3665
-        $option = 'per_page';
3666
-        $args   = [
3667
-            'label'   => apply_filters(
3668
-                'FHEE__EE_Admin_Page___per_page_screen_options___label',
3669
-                $this->_admin_page_title,
3670
-                $this
3671
-            ),
3672
-            'default' => (int) apply_filters(
3673
-                'FHEE__EE_Admin_Page___per_page_screen_options__default',
3674
-                20
3675
-            ),
3676
-            'option'  => $this->_current_page . '_' . $this->_current_view . '_per_page',
3677
-        ];
3678
-        // ONLY add the screen option if the user has access to it.
3679
-        if ($this->check_user_access($this->_current_view, true)) {
3680
-            add_screen_option($option, $args);
3681
-        }
3682
-    }
3683
-
3684
-
3685
-    /**
3686
-     * set_per_page_screen_option
3687
-     * All this does is make sure that WordPress saves any per_page screen options (if set) for the current page.
3688
-     * we have to do this rather than running inside the 'set-screen-options' hook because it runs earlier than
3689
-     * admin_menu.
3690
-     *
3691
-     * @return void
3692
-     */
3693
-    private function _set_per_page_screen_options()
3694
-    {
3695
-        if ($this->request->requestParamIsSet('wp_screen_options')) {
3696
-            check_admin_referer('screen-options-nonce', 'screenoptionnonce');
3697
-            if (! $user = wp_get_current_user()) {
3698
-                return;
3699
-            }
3700
-            $option = $this->request->getRequestParam('wp_screen_options[option]', '', DataType::KEY);
3701
-            if (! $option) {
3702
-                return;
3703
-            }
3704
-            $value      = $this->request->getRequestParam('wp_screen_options[value]', 0, DataType::INT);
3705
-            $map_option = $option;
3706
-            $option     = str_replace('-', '_', $option);
3707
-            switch ($map_option) {
3708
-                case $this->_current_page . '_' . $this->_current_view . '_per_page':
3709
-                    $max_value = apply_filters(
3710
-                        'FHEE__EE_Admin_Page___set_per_page_screen_options__max_value',
3711
-                        999,
3712
-                        $this->_current_page,
3713
-                        $this->_current_view
3714
-                    );
3715
-                    if ($value < 1) {
3716
-                        return;
3717
-                    }
3718
-                    $value = min($value, $max_value);
3719
-                    break;
3720
-                default:
3721
-                    $value = apply_filters(
3722
-                        'FHEE__EE_Admin_Page___set_per_page_screen_options__value',
3723
-                        false,
3724
-                        $option,
3725
-                        $value
3726
-                    );
3727
-                    if (false === $value) {
3728
-                        return;
3729
-                    }
3730
-                    break;
3731
-            }
3732
-            update_user_meta($user->ID, $option, $value);
3733
-            wp_safe_redirect(remove_query_arg(['pagenum', 'apage', 'paged'], wp_get_referer()));
3734
-            exit;
3735
-        }
3736
-    }
3737
-
3738
-
3739
-    /**
3740
-     * This just allows for setting the $_template_args property if it needs to be set outside the object
3741
-     *
3742
-     * @param array $data array that will be assigned to template args.
3743
-     */
3744
-    public function set_template_args(array $data)
3745
-    {
3746
-        $this->_template_args = array_merge($this->_template_args, $data);
3747
-    }
3748
-
3749
-
3750
-    public function setAdminPageTitle(string $title)
3751
-    {
3752
-        $this->_admin_page_title = sanitize_text_field($title);
3753
-    }
3754
-
3755
-
3756
-    /**
3757
-     * This makes available the WP transient system for temporarily moving data between routes
3758
-     *
3759
-     * @param string $route             the route that should receive the transient
3760
-     * @param array  $data              the data that gets sent
3761
-     * @param bool   $notices           If this is for notices then we use this to indicate so, otherwise it's just a
3762
-     *                                  normal route transient.
3763
-     * @param bool   $skip_route_verify Used to indicate we want to skip route verification.  This is usually ONLY used
3764
-     *                                  when we are adding a transient before page_routes have been defined.
3765
-     * @return void
3766
-     * @throws EE_Error
3767
-     */
3768
-    protected function _add_transient(
3769
-        string $route,
3770
-        array $data,
3771
-        bool $notices = false,
3772
-        bool $skip_route_verify = false
3773
-    ) {
3774
-        $user_id = get_current_user_id();
3775
-        if (! $skip_route_verify) {
3776
-            $this->_verify_route($route);
3777
-        }
3778
-        // now let's set the string for what kind of transient we're setting
3779
-        $transient = $notices ? "ee_rte_n_tx_{$route}_$user_id" : "rte_tx_{$route}_$user_id";
3780
-        $data      = $notices ? ['notices' => $data] : $data;
3781
-        // is there already a transient for this route?  If there is then let's ADD to that transient
3782
-        $existing = is_multisite() && is_network_admin()
3783
-            ? get_site_transient($transient)
3784
-            : get_transient($transient);
3785
-        if ($existing) {
3786
-            $data = array_merge($data, (array) $existing);
3787
-        }
3788
-        if (is_multisite() && is_network_admin()) {
3789
-            set_site_transient($transient, $data, 8);
3790
-        } else {
3791
-            set_transient($transient, $data, 8);
3792
-        }
3793
-    }
3794
-
3795
-
3796
-    /**
3797
-     * this retrieves the temporary transient that has been set for moving data between routes.
3798
-     *
3799
-     * @param bool   $notices true we get notices transient. False we just return normal route transient
3800
-     * @param string $route
3801
-     * @return mixed data
3802
-     */
3803
-    protected function _get_transient(bool $notices = false, string $route = '')
3804
-    {
3805
-        $user_id   = get_current_user_id();
3806
-        $route     = ! $route ? $this->_req_action : $route;
3807
-        $transient = $notices
3808
-            ? 'ee_rte_n_tx_' . $route . '_' . $user_id
3809
-            : 'rte_tx_' . $route . '_' . $user_id;
3810
-        $data      = is_multisite() && is_network_admin()
3811
-            ? get_site_transient($transient)
3812
-            : get_transient($transient);
3813
-        // delete transient after retrieval (just in case it hasn't expired);
3814
-        if (is_multisite() && is_network_admin()) {
3815
-            delete_site_transient($transient);
3816
-        } else {
3817
-            delete_transient($transient);
3818
-        }
3819
-        return $notices && isset($data['notices']) ? $data['notices'] : $data;
3820
-    }
3821
-
3822
-
3823
-    /**
3824
-     * The purpose of this method is just to run garbage collection on any EE transients that might have expired but
3825
-     * would not be called later. This will be assigned to run on a specific EE Admin page. (place the method in the
3826
-     * default route callback on the EE_Admin page you want it run.)
3827
-     *
3828
-     * @return void
3829
-     */
3830
-    protected function _transient_garbage_collection()
3831
-    {
3832
-        global $wpdb;
3833
-        // retrieve all existing transients
3834
-        $query =
3835
-            "SELECT option_name FROM $wpdb->options WHERE option_name LIKE '%rte_tx_%' OR option_name LIKE '%rte_n_tx_%'";
3836
-        if ($results = $wpdb->get_results($query)) {
3837
-            foreach ($results as $result) {
3838
-                $transient = str_replace('_transient_', '', $result->option_name);
3839
-                get_transient($transient);
3840
-                if (is_multisite() && is_network_admin()) {
3841
-                    get_site_transient($transient);
3842
-                }
3843
-            }
3844
-        }
3845
-    }
3846
-
3847
-
3848
-    /**
3849
-     * get_view
3850
-     *
3851
-     * @return string content of _view property
3852
-     */
3853
-    public function get_view(): string
3854
-    {
3855
-        return $this->_view;
3856
-    }
3857
-
3858
-
3859
-    /**
3860
-     * getter for the protected $_views property
3861
-     *
3862
-     * @return array
3863
-     */
3864
-    public function get_views(): array
3865
-    {
3866
-        return $this->_views;
3867
-    }
3868
-
3869
-
3870
-    /**
3871
-     * @param array $views
3872
-     * @return void
3873
-     * @since 5.0.13.p
3874
-     */
3875
-    public function updateViews(array $views)
3876
-    {
3877
-        $this->_views = array_merge($this->_views, $views);
3878
-    }
3879
-
3880
-
3881
-    /**
3882
-    /**
3883
-     * get_current_page
3884
-     *
3885
-     * @return string _current_page property value
3886
-     */
3887
-    public function get_current_page(): string
3888
-    {
3889
-        return $this->_current_page;
3890
-    }
3891
-
3892
-
3893
-    /**
3894
-     * get_current_view
3895
-     *
3896
-     * @return string _current_view property value
3897
-     */
3898
-    public function get_current_view(): string
3899
-    {
3900
-        return $this->_current_view;
3901
-    }
3902
-
3903
-
3904
-    /**
3905
-     * get_current_screen
3906
-     *
3907
-     * @return object The current WP_Screen object
3908
-     */
3909
-    public function get_current_screen()
3910
-    {
3911
-        return $this->_current_screen;
3912
-    }
3913
-
3914
-
3915
-    /**
3916
-     * get_current_page_view_url
3917
-     *
3918
-     * @return string This returns the url for the current_page_view.
3919
-     */
3920
-    public function get_current_page_view_url(): string
3921
-    {
3922
-        return $this->_current_page_view_url;
3923
-    }
3924
-
3925
-
3926
-    /**
3927
-     * just returns the Request
3928
-     *
3929
-     * @return RequestInterface
3930
-     */
3931
-    public function get_request(): ?RequestInterface
3932
-    {
3933
-        return $this->request;
3934
-    }
3935
-
3936
-
3937
-    /**
3938
-     * just returns the _req_data property
3939
-     *
3940
-     * @return array
3941
-     */
3942
-    public function get_request_data(): array
3943
-    {
3944
-        return $this->request->requestParams();
3945
-    }
3946
-
3947
-
3948
-    /**
3949
-     * returns the _req_data protected property
3950
-     *
3951
-     * @return string
3952
-     */
3953
-    public function get_req_action(): string
3954
-    {
3955
-        return $this->_req_action;
3956
-    }
3957
-
3958
-
3959
-    /**
3960
-     * @return bool  value of $_is_caf property
3961
-     */
3962
-    public function is_caf(): bool
3963
-    {
3964
-        return $this->_is_caf;
3965
-    }
3966
-
3967
-
3968
-    /**
3969
-     * @return array
3970
-     */
3971
-    public function default_espresso_metaboxes(): array
3972
-    {
3973
-        return $this->_default_espresso_metaboxes;
3974
-    }
3975
-
3976
-
3977
-    /**
3978
-     * @return string
3979
-     */
3980
-    public function admin_base_url(): string
3981
-    {
3982
-        return $this->_admin_base_url;
3983
-    }
3984
-
3985
-
3986
-    /**
3987
-     * @return string
3988
-     */
3989
-    public function wp_page_slug(): string
3990
-    {
3991
-        return $this->_wp_page_slug;
3992
-    }
3993
-
3994
-
3995
-    /**
3996
-     * updates  espresso configuration settings
3997
-     *
3998
-     * @param string                   $tab
3999
-     * @param EE_Config_Base|EE_Config $config
4000
-     * @param string                   $file file where error occurred
4001
-     * @param string                   $func function  where error occurred
4002
-     * @param string                   $line line no where error occurred
4003
-     * @return bool
4004
-     */
4005
-    protected function _update_espresso_configuration(
4006
-        string $tab,
4007
-        $config,
4008
-        string $file = '',
4009
-        string $func = '',
4010
-        string $line = ''
4011
-    ): bool {
4012
-        // remove any options that are NOT going to be saved with the config settings.
4013
-        if (isset($config->core->ee_ueip_optin)) {
4014
-            // TODO: remove the following two lines and make sure values are migrated from 3.1
4015
-            update_option('ee_ueip_optin', $config->core->ee_ueip_optin);
4016
-            update_option('ee_ueip_has_notified', true);
4017
-        }
4018
-        // and save it (note we're also doing the network save here)
4019
-        $net_saved    = ! is_main_site() || EE_Network_Config::instance()->update_config(false, false);
4020
-        $config_saved = EE_Config::instance()->update_espresso_config(false, false);
4021
-        if ($config_saved && $net_saved) {
4022
-            EE_Error::add_success(sprintf(esc_html__('"%s" have been successfully updated.', 'event_espresso'), $tab));
4023
-            return true;
4024
-        }
4025
-        EE_Error::add_error(
4026
-            sprintf(esc_html__('The "%s" were not updated.', 'event_espresso'), $tab),
4027
-            $file,
4028
-            $func,
4029
-            $line
4030
-        );
4031
-        return false;
4032
-    }
4033
-
4034
-
4035
-    /**
4036
-     * Returns an array to be used for EE_FOrm_Fields.helper.php's select_input as the $values argument.
4037
-     *
4038
-     * @return array
4039
-     */
4040
-    public function get_yes_no_values(): array
4041
-    {
4042
-        return $this->_yes_no_values;
4043
-    }
4044
-
4045
-
4046
-    /**
4047
-     * @return string
4048
-     * @throws ReflectionException
4049
-     * @since 5.0.0.p
4050
-     */
4051
-    protected function _get_dir(): string
4052
-    {
4053
-        $reflector = new ReflectionClass($this->class_name);
4054
-        return dirname($reflector->getFileName());
4055
-    }
4056
-
4057
-
4058
-    /**
4059
-     * A helper for getting a "next link".
4060
-     *
4061
-     * @param string $url   The url to link to
4062
-     * @param string $class The class to use.
4063
-     * @return string
4064
-     */
4065
-    protected function _next_link(string $url, string $class = 'dashicons dashicons-arrow-right'): string
4066
-    {
4067
-        return '<a class="' . $class . '" href="' . $url . '"></a>';
4068
-    }
4069
-
4070
-
4071
-    /**
4072
-     * A helper for getting a "previous link".
4073
-     *
4074
-     * @param string $url   The url to link to
4075
-     * @param string $class The class to use.
4076
-     * @return string
4077
-     */
4078
-    protected function _previous_link(string $url, string $class = 'dashicons dashicons-arrow-left'): string
4079
-    {
4080
-        return '<a class="' . $class . '" href="' . $url . '"></a>';
4081
-    }
4082
-
4083
-
4084
-
4085
-
4086
-
4087
-
4088
-
4089
-    // below are some messages related methods that should be available across the EE_Admin system.  Note, these methods are NOT page specific
4090
-
4091
-
4092
-    /**
4093
-     * This processes a request to resend a registration and assumes we have a _REG_ID for doing so. So if the caller
4094
-     * knows that the _REG_ID isn't in the req_data array but CAN obtain it, the caller should ADD the _REG_ID to the
4095
-     * _req_data array.
4096
-     *
4097
-     * @return bool success/fail
4098
-     * @throws EE_Error
4099
-     * @throws InvalidArgumentException
4100
-     * @throws ReflectionException
4101
-     * @throws InvalidDataTypeException
4102
-     * @throws InvalidInterfaceException
4103
-     */
4104
-    protected function _process_resend_registration(): bool
4105
-    {
4106
-        $this->_template_args['success'] = EED_Messages::process_resend($this->request->requestParams());
4107
-        do_action(
4108
-            'AHEE__EE_Admin_Page___process_resend_registration',
4109
-            $this->_template_args['success'],
4110
-            $this->request->requestParams()
4111
-        );
4112
-        return $this->_template_args['success'];
4113
-    }
4114
-
4115
-
4116
-    /**
4117
-     * This automatically processes any payment message notifications when manual payment has been applied.
4118
-     *
4119
-     * @param EE_Payment $payment
4120
-     * @return bool success/fail
4121
-     */
4122
-    protected function _process_payment_notification(EE_Payment $payment): bool
4123
-    {
4124
-        add_filter('FHEE__EE_Payment_Processor__process_registration_payments__display_notifications', '__return_true');
4125
-        do_action('AHEE__EE_Admin_Page___process_admin_payment_notification', $payment);
4126
-        $this->_template_args['success'] = apply_filters(
4127
-            'FHEE__EE_Admin_Page___process_admin_payment_notification__success',
4128
-            false,
4129
-            $payment
4130
-        );
4131
-        return $this->_template_args['success'];
4132
-    }
4133
-
4134
-
4135
-    /**
4136
-     * @param EEM_Base      $entity_model
4137
-     * @param string        $entity_PK_name name of the primary key field used as a request param, ie: id, ID, etc
4138
-     * @param string        $action         one of the EE_Admin_List_Table::ACTION_* constants: delete, restore, trash
4139
-     * @param string        $delete_column  name of the field that denotes whether entity is trashed
4140
-     * @param callable|null $callback       called after entity is trashed, restored, or deleted
4141
-     * @return int|float
4142
-     * @throws EE_Error
4143
-     */
4144
-    protected function trashRestoreDeleteEntities(
4145
-        EEM_Base $entity_model,
4146
-        string $entity_PK_name,
4147
-        string $action = EE_Admin_List_Table::ACTION_DELETE,
4148
-        string $delete_column = '',
4149
-        ?callable $callback = null
4150
-    ) {
4151
-        $entity_PK      = $entity_model->get_primary_key_field();
4152
-        $entity_PK_name = $entity_PK_name ?: $entity_PK->get_name();
4153
-        $entity_PK_type = $this->resolveEntityFieldDataType($entity_PK);
4154
-        // grab ID if deleting a single entity
4155
-        if ($this->request->requestParamIsSet($entity_PK_name)) {
4156
-            $ID = $this->request->getRequestParam($entity_PK_name, 0, $entity_PK_type);
4157
-            return $this->trashRestoreDeleteEntity($entity_model, $ID, $action, $delete_column, $callback) ? 1 : 0;
4158
-        }
4159
-        // or grab checkbox array if bulk deleting
4160
-        $checkboxes = $this->request->getRequestParam('checkbox', [], $entity_PK_type, true);
4161
-        if (empty($checkboxes)) {
4162
-            return 0;
4163
-        }
4164
-        $success = 0;
4165
-        $IDs     = array_keys($checkboxes);
4166
-        // cycle thru bulk action checkboxes
4167
-        foreach ($IDs as $ID) {
4168
-            // increment $success
4169
-            if ($this->trashRestoreDeleteEntity($entity_model, $ID, $action, $delete_column, $callback)) {
4170
-                $success++;
4171
-            }
4172
-        }
4173
-        $count = (int) count($checkboxes);
4174
-        // if multiple entities were deleted successfully, then $deleted will be full count of deletions,
4175
-        // otherwise it will be a fraction of ( actual deletions / total entities to be deleted )
4176
-        return $success === $count ? $count : $success / $count;
4177
-    }
4178
-
4179
-
4180
-    /**
4181
-     * @param EE_Primary_Key_Field_Base $entity_PK
4182
-     * @return string
4183
-     * @throws EE_Error
4184
-     * @since   4.10.30.p
4185
-     */
4186
-    private function resolveEntityFieldDataType(EE_Primary_Key_Field_Base $entity_PK): string
4187
-    {
4188
-        $entity_PK_type = $entity_PK->getSchemaType();
4189
-        switch ($entity_PK_type) {
4190
-            case 'boolean':
4191
-                return DataType::BOOL;
4192
-            case 'integer':
4193
-                return DataType::INT;
4194
-            case 'number':
4195
-                return DataType::FLOAT;
4196
-            case 'string':
4197
-                return DataType::STRING;
4198
-        }
4199
-        throw new RuntimeException(
4200
-            sprintf(
4201
-                esc_html__(
4202
-                    '"%1$s" is an invalid schema type for the %2$s primary key.',
4203
-                    'event_espresso'
4204
-                ),
4205
-                $entity_PK_type,
4206
-                $entity_PK->get_name()
4207
-            )
4208
-        );
4209
-    }
4210
-
4211
-
4212
-    /**
4213
-     * @param EEM_Base      $entity_model
4214
-     * @param int|string    $entity_ID
4215
-     * @param string        $action        one of the EE_Admin_List_Table::ACTION_* constants: delete, restore, trash
4216
-     * @param string        $delete_column name of the field that denotes whether entity is trashed
4217
-     * @param callable|null $callback      called after entity is trashed, restored, or deleted
4218
-     * @return bool
4219
-     */
4220
-    protected function trashRestoreDeleteEntity(
4221
-        EEM_Base $entity_model,
4222
-        $entity_ID,
4223
-        string $action,
4224
-        string $delete_column,
4225
-        ?callable $callback = null
4226
-    ): bool {
4227
-        $entity_ID = absint($entity_ID);
4228
-        if (! $entity_ID) {
4229
-            $this->trashRestoreDeleteError($action, $entity_model);
4230
-        }
4231
-        $result = 0;
4232
-        try {
4233
-            $entity = $entity_model->get_one_by_ID($entity_ID);
4234
-            if (! $entity instanceof EE_Base_Class) {
4235
-                throw new DomainException(
4236
-                    sprintf(
4237
-                        esc_html__(
4238
-                            'Missing or invalid %1$s entity with ID of "%2$s" returned from db.',
4239
-                            'event_espresso'
4240
-                        ),
4241
-                        str_replace('EEM_', '', $entity_model->get_this_model_name()),
4242
-                        $entity_ID
4243
-                    )
4244
-                );
4245
-            }
4246
-            switch ($action) {
4247
-                case EE_Admin_List_Table::ACTION_DELETE:
4248
-                    $result = (bool) $entity->delete_permanently();
4249
-                    break;
4250
-                case EE_Admin_List_Table::ACTION_RESTORE:
4251
-                    $result = $entity->delete_or_restore(false);
4252
-                    break;
4253
-                case EE_Admin_List_Table::ACTION_TRASH:
4254
-                    $result = $entity->delete_or_restore();
4255
-                    break;
4256
-            }
4257
-        } catch (Exception $exception) {
4258
-            $this->trashRestoreDeleteError($action, $entity_model, $exception);
4259
-        }
4260
-        if (is_callable($callback)) {
4261
-            call_user_func_array($callback, [$entity_model, $entity_ID, $action, $result, $delete_column]);
4262
-        }
4263
-        return $result;
4264
-    }
4265
-
4266
-
4267
-    /**
4268
-     * @param EEM_Base $entity_model
4269
-     * @param string   $delete_column
4270
-     * @since 4.10.30.p
4271
-     */
4272
-    private function validateDeleteColumn(EEM_Base $entity_model, string $delete_column)
4273
-    {
4274
-        if (empty($delete_column)) {
4275
-            throw new DomainException(
4276
-                sprintf(
4277
-                    esc_html__(
4278
-                        'You need to specify the name of the "delete column" on the %2$s model, in order to trash or restore an entity.',
4279
-                        'event_espresso'
4280
-                    ),
4281
-                    $entity_model->get_this_model_name()
4282
-                )
4283
-            );
4284
-        }
4285
-        if (! $entity_model->has_field($delete_column)) {
4286
-            throw new DomainException(
4287
-                sprintf(
4288
-                    esc_html__(
4289
-                        'The %1$s field does not exist on the %2$s model.',
4290
-                        'event_espresso'
4291
-                    ),
4292
-                    $delete_column,
4293
-                    $entity_model->get_this_model_name()
4294
-                )
4295
-            );
4296
-        }
4297
-    }
4298
-
4299
-
4300
-    /**
4301
-     * @param EEM_Base       $entity_model
4302
-     * @param Exception|null $exception
4303
-     * @param string         $action
4304
-     * @since 4.10.30.p
4305
-     */
4306
-    private function trashRestoreDeleteError(string $action, EEM_Base $entity_model, Exception $exception = null)
4307
-    {
4308
-        if ($exception instanceof Exception) {
4309
-            throw new RuntimeException(
4310
-                sprintf(
4311
-                    esc_html__(
4312
-                        'Could not %1$s the %2$s because the following error occurred: %3$s',
4313
-                        'event_espresso'
4314
-                    ),
4315
-                    $action,
4316
-                    $entity_model->get_this_model_name(),
4317
-                    $exception->getMessage()
4318
-                )
4319
-            );
4320
-        }
4321
-        throw new RuntimeException(
4322
-            sprintf(
4323
-                esc_html__(
4324
-                    'Could not %1$s the %2$s because an invalid ID was received.',
4325
-                    'event_espresso'
4326
-                ),
4327
-                $action,
4328
-                $entity_model->get_this_model_name()
4329
-            )
4330
-        );
4331
-    }
3324
+		// add nonce
3325
+		$nonce                                             =
3326
+			wp_nonce_field($route . '_nonce', $route . '_nonce', false, false);
3327
+		$this->_template_args['before_admin_page_content'] .= "\n\t" . $nonce;
3328
+		// add REQUIRED form action
3329
+		$hidden_fields = [
3330
+			'action' => ['type' => 'hidden', 'value' => $route],
3331
+		];
3332
+		// merge arrays
3333
+		$hidden_fields = is_array($additional_hidden_fields)
3334
+			? array_merge($hidden_fields, $additional_hidden_fields)
3335
+			: $hidden_fields;
3336
+		// generate form fields
3337
+		$form_fields = $this->_generate_admin_form_fields($hidden_fields, 'array');
3338
+		// add fields to form
3339
+		foreach ((array) $form_fields as $form_field) {
3340
+			$this->_template_args['before_admin_page_content'] .= "\n\t" . $form_field['field'];
3341
+		}
3342
+		// close form
3343
+		$this->_template_args['after_admin_page_content'] = '</form>';
3344
+	}
3345
+
3346
+
3347
+	/**
3348
+	 * Public Wrapper for _redirect_after_action() method since its
3349
+	 * discovered it would be useful for external code to have access.
3350
+	 *
3351
+	 * @param bool|int $success
3352
+	 * @param string   $what
3353
+	 * @param string   $action_desc
3354
+	 * @param array    $query_args
3355
+	 * @param bool     $override_overwrite
3356
+	 * @throws EE_Error
3357
+	 * @see   EE_Admin_Page::_redirect_after_action() for params.
3358
+	 * @since 4.5.0
3359
+	 */
3360
+	public function redirect_after_action(
3361
+		$success = false,
3362
+		string $what = 'item',
3363
+		string $action_desc = 'processed',
3364
+		array $query_args = [],
3365
+		bool $override_overwrite = false
3366
+	) {
3367
+		$this->_redirect_after_action(
3368
+			$success,
3369
+			$what,
3370
+			$action_desc,
3371
+			$query_args,
3372
+			$override_overwrite
3373
+		);
3374
+	}
3375
+
3376
+
3377
+	/**
3378
+	 * Helper method for merging existing request data with the returned redirect url.
3379
+	 * This is typically used for redirects after an action so that if the original view was a filtered view those
3380
+	 * filters are still applied.
3381
+	 *
3382
+	 * @param array $new_route_data
3383
+	 * @return array
3384
+	 */
3385
+	protected function mergeExistingRequestParamsWithRedirectArgs(array $new_route_data): array
3386
+	{
3387
+		foreach ($this->request->requestParams() as $ref => $value) {
3388
+			// unset nonces
3389
+			if (strpos($ref, 'nonce') !== false) {
3390
+				$this->request->unSetRequestParam($ref);
3391
+				continue;
3392
+			}
3393
+			// urlencode values.
3394
+			$value = is_array($value) ? array_map('urlencode', $value) : urlencode($value);
3395
+			$this->request->setRequestParam($ref, $value);
3396
+		}
3397
+		return array_merge($this->request->requestParams(), $new_route_data);
3398
+	}
3399
+
3400
+
3401
+	/**
3402
+	 * @param int|float|string $success            - whether success was for two or more records, or just one, or none
3403
+	 * @param string           $what               - what the action was performed on
3404
+	 * @param string           $action_desc        - what was done ie: updated, deleted, etc
3405
+	 * @param array            $query_args         - an array of query_args to be added to the URL to redirect to
3406
+	 * @param BOOL             $override_overwrite - by default all EE_Error::success messages are overwritten,
3407
+	 *                                             this allows you to override this so that they show.
3408
+	 * @return void
3409
+	 * @throws EE_Error
3410
+	 * @throws InvalidArgumentException
3411
+	 * @throws InvalidDataTypeException
3412
+	 * @throws InvalidInterfaceException
3413
+	 */
3414
+	protected function _redirect_after_action(
3415
+		$success = 0,
3416
+		string $what = 'item',
3417
+		string $action_desc = 'processed',
3418
+		array $query_args = [],
3419
+		bool $override_overwrite = false
3420
+	) {
3421
+		$notices = EE_Error::get_notices(false);
3422
+		// overwrite default success messages //BUT ONLY if overwrite not overridden
3423
+		if (! $override_overwrite || ! empty($notices['errors'])) {
3424
+			EE_Error::overwrite_success();
3425
+		}
3426
+		if (! $override_overwrite && ! empty($what) && ! empty($action_desc) && empty($notices['errors'])) {
3427
+			// how many records affected ? more than one record ? or just one ?
3428
+			EE_Error::add_success(
3429
+				sprintf(
3430
+					esc_html(
3431
+						_n(
3432
+							'The "%1$s" has been successfully %2$s.',
3433
+							'The "%1$s" have been successfully %2$s.',
3434
+							$success,
3435
+							'event_espresso'
3436
+						)
3437
+					),
3438
+					$what,
3439
+					$action_desc
3440
+				),
3441
+				__FILE__,
3442
+				__FUNCTION__,
3443
+				__LINE__
3444
+			);
3445
+		}
3446
+		// check that $query_args isn't something crazy
3447
+		$query_args = is_array($query_args) ? $query_args : [];
3448
+		/**
3449
+		 * Allow injecting actions before the query_args are modified for possible different
3450
+		 * redirections on save and close actions
3451
+		 *
3452
+		 * @param array $query_args       The original query_args array coming into the
3453
+		 *                                method.
3454
+		 * @since 4.2.0
3455
+		 */
3456
+		do_action(
3457
+			"AHEE__{$this->class_name}___redirect_after_action__before_redirect_modification_$this->_req_action",
3458
+			$query_args
3459
+		);
3460
+		// set redirect url.
3461
+		// Note if there is a "page" index in the $query_args then we go with vanilla admin.php route,
3462
+		// otherwise we go with whatever is set as the _admin_base_url
3463
+		$redirect_url = isset($query_args['page']) ? admin_url('admin.php') : $this->_admin_base_url;
3464
+		// calculate where we're going (if we have a "save and close" button pushed)
3465
+		if (
3466
+			$this->request->requestParamIsSet('save_and_close')
3467
+			&& $this->request->requestParamIsSet('save_and_close_referrer')
3468
+		) {
3469
+			// even though we have the save_and_close referrer, we need to parse the url for the action in order to generate a nonce
3470
+			$parsed_url = parse_url($this->request->getRequestParam('save_and_close_referrer', '', DataType::URL));
3471
+			// regenerate query args array from referrer URL
3472
+			parse_str($parsed_url['query'], $query_args);
3473
+			// correct page and action will be in the query args now
3474
+			$redirect_url = admin_url('admin.php');
3475
+		}
3476
+		// merge any default query_args set in _default_route_query_args property
3477
+		if (! empty($this->_default_route_query_args) && ! $this->_is_UI_request) {
3478
+			$args_to_merge = [];
3479
+			foreach ($this->_default_route_query_args as $query_param => $query_value) {
3480
+				// is there a wp_referer array in our _default_route_query_args property?
3481
+				if ($query_param === 'wp_referer') {
3482
+					$query_value = (array) $query_value;
3483
+					foreach ($query_value as $reference => $value) {
3484
+						if (strpos($reference, 'nonce') !== false) {
3485
+							continue;
3486
+						}
3487
+						// finally we will override any arguments in the referer with
3488
+						// what might be set on the _default_route_query_args array.
3489
+						if (isset($this->_default_route_query_args[ $reference ])) {
3490
+							$args_to_merge[ $reference ] = urlencode($this->_default_route_query_args[ $reference ]);
3491
+						} else {
3492
+							$args_to_merge[ $reference ] = urlencode($value);
3493
+						}
3494
+					}
3495
+					continue;
3496
+				}
3497
+				$args_to_merge[ $query_param ] = $query_value;
3498
+			}
3499
+			// now let's merge these arguments but override with what was specifically sent in to the
3500
+			// redirect.
3501
+			$query_args = array_merge($args_to_merge, $query_args);
3502
+		}
3503
+		$this->_process_notices($query_args);
3504
+		// generate redirect url
3505
+		// if redirecting to anything other than the main page, add a nonce
3506
+		if (isset($query_args['action'])) {
3507
+			// manually generate wp_nonce and merge that with the query vars
3508
+			// becuz the wp_nonce_url function wrecks havoc on some vars
3509
+			$query_args['_wpnonce'] = wp_create_nonce($query_args['action'] . '_nonce');
3510
+		}
3511
+		// we're adding some hooks and filters in here for processing any things just before redirects
3512
+		// (example: an admin page has done an insert or update and we want to run something after that).
3513
+		do_action('AHEE_redirect_' . $this->class_name . $this->_req_action, $query_args);
3514
+		$redirect_url = apply_filters(
3515
+			'FHEE_redirect_' . $this->class_name . $this->_req_action,
3516
+			EE_Admin_Page::add_query_args_and_nonce($query_args, $redirect_url),
3517
+			$query_args
3518
+		);
3519
+		// check if we're doing ajax.  If we are then lets just return the results and js can handle how it wants.
3520
+		if ($this->request->isAjax()) {
3521
+			$default_data                    = [
3522
+				'close'        => true,
3523
+				'redirect_url' => $redirect_url,
3524
+				'where'        => 'main',
3525
+				'what'         => 'append',
3526
+			];
3527
+			$this->_template_args['success'] = $success;
3528
+			$this->_template_args['data']    = ! empty($this->_template_args['data']) ? array_merge(
3529
+				$default_data,
3530
+				$this->_template_args['data']
3531
+			) : $default_data;
3532
+			$this->_return_json();
3533
+		}
3534
+		wp_safe_redirect($redirect_url);
3535
+		exit();
3536
+	}
3537
+
3538
+
3539
+	/**
3540
+	 * process any notices before redirecting (or returning ajax request)
3541
+	 * This method sets the $this->_template_args['notices'] attribute;
3542
+	 *
3543
+	 * @param array $query_args         any query args that need to be used for notice transient ('action')
3544
+	 * @param bool  $skip_route_verify  This is typically used when we are processing notices REALLY early and
3545
+	 *                                  page_routes haven't been defined yet.
3546
+	 * @param bool  $sticky_notices     This is used to flag that regardless of whether this is doing_ajax or not, we
3547
+	 *                                  still save a transient for the notice.
3548
+	 * @return void
3549
+	 * @throws EE_Error
3550
+	 * @throws InvalidArgumentException
3551
+	 * @throws InvalidDataTypeException
3552
+	 * @throws InvalidInterfaceException
3553
+	 */
3554
+	protected function _process_notices(
3555
+		array $query_args = [],
3556
+		bool $skip_route_verify = false,
3557
+		bool $sticky_notices = true
3558
+	) {
3559
+		// first let's set individual error properties if doing_ajax and the properties aren't already set.
3560
+		if ($this->request->isAjax()) {
3561
+			$notices = EE_Error::get_notices(false);
3562
+			if (empty($this->_template_args['success'])) {
3563
+				$this->_template_args['success'] = $notices['success'] ?? false;
3564
+			}
3565
+			if (empty($this->_template_args['errors'])) {
3566
+				$this->_template_args['errors'] = $notices['errors'] ?? false;
3567
+			}
3568
+			if (empty($this->_template_args['attention'])) {
3569
+				$this->_template_args['attention'] = $notices['attention'] ?? false;
3570
+			}
3571
+		}
3572
+		$this->_template_args['notices'] = EE_Error::get_notices();
3573
+		// IF this isn't ajax we need to create a transient for the notices using the route (however, overridden if $sticky_notices == true)
3574
+		if (! $this->request->isAjax() || $sticky_notices) {
3575
+			$route = $query_args['action'] ?? 'default';
3576
+			$this->_add_transient(
3577
+				$route,
3578
+				(array) $this->_template_args['notices'],
3579
+				true,
3580
+				$skip_route_verify
3581
+			);
3582
+		}
3583
+	}
3584
+
3585
+
3586
+	/**
3587
+	 * get_action_link_or_button
3588
+	 * returns the button html for adding, editing, or deleting an item (depending on given type)
3589
+	 *
3590
+	 * @param string $action        use this to indicate which action the url is generated with.
3591
+	 * @param string $type          accepted strings must be defined in the $_labels['button'] array(as the key)
3592
+	 *                              property.
3593
+	 * @param array  $extra_request if the button requires extra params you can include them in $key=>$value pairs.
3594
+	 * @param string $class         Use this to give the class for the button. Defaults to 'button--primary'
3595
+	 * @param string $base_url      If this is not provided
3596
+	 *                              the _admin_base_url will be used as the default for the button base_url.
3597
+	 *                              Otherwise this value will be used.
3598
+	 * @param bool   $exclude_nonce If true then no nonce will be in the generated button link.
3599
+	 * @return string
3600
+	 * @throws InvalidArgumentException
3601
+	 * @throws InvalidInterfaceException
3602
+	 * @throws InvalidDataTypeException
3603
+	 * @throws EE_Error
3604
+	 */
3605
+	public function get_action_link_or_button(
3606
+		string $action,
3607
+		string $type = 'add',
3608
+		array $extra_request = [],
3609
+		string $class = 'button button--primary',
3610
+		string $base_url = '',
3611
+		bool $exclude_nonce = false
3612
+	): string {
3613
+		// first let's validate the action (if $base_url is FALSE otherwise validation will happen further along)
3614
+		if (empty($base_url) && ! isset($this->_page_routes[ $action ])) {
3615
+			throw new EE_Error(
3616
+				sprintf(
3617
+					esc_html__(
3618
+						'There is no page route for given action for the button.  This action was given: %s',
3619
+						'event_espresso'
3620
+					),
3621
+					$action
3622
+				)
3623
+			);
3624
+		}
3625
+		if (! isset($this->_labels['buttons'][ $type ])) {
3626
+			throw new EE_Error(
3627
+				sprintf(
3628
+					esc_html__(
3629
+						'There is no label for the given button type (%s). Labels are set in the <code>_page_config</code> property.',
3630
+						'event_espresso'
3631
+					),
3632
+					$type
3633
+				)
3634
+			);
3635
+		}
3636
+		// finally check user access for this button.
3637
+		$has_access = $this->check_user_access($action, true);
3638
+		if (! $has_access) {
3639
+			return '';
3640
+		}
3641
+		$_base_url  = ! $base_url ? $this->_admin_base_url : $base_url;
3642
+		$query_args = [
3643
+			'action' => $action,
3644
+		];
3645
+		// merge extra_request args but make sure our original action takes precedence and doesn't get overwritten.
3646
+		if (! empty($extra_request)) {
3647
+			$query_args = array_merge($extra_request, $query_args);
3648
+		}
3649
+		$url = EE_Admin_Page::add_query_args_and_nonce($query_args, $_base_url, false, $exclude_nonce);
3650
+		return EEH_Template::get_button_or_link($url, $this->_labels['buttons'][ $type ], $class);
3651
+	}
3652
+
3653
+
3654
+	/**
3655
+	 * _per_page_screen_option
3656
+	 * Utility function for adding in a per_page_option in the screen_options_dropdown.
3657
+	 *
3658
+	 * @return void
3659
+	 * @throws InvalidArgumentException
3660
+	 * @throws InvalidInterfaceException
3661
+	 * @throws InvalidDataTypeException
3662
+	 */
3663
+	protected function _per_page_screen_option()
3664
+	{
3665
+		$option = 'per_page';
3666
+		$args   = [
3667
+			'label'   => apply_filters(
3668
+				'FHEE__EE_Admin_Page___per_page_screen_options___label',
3669
+				$this->_admin_page_title,
3670
+				$this
3671
+			),
3672
+			'default' => (int) apply_filters(
3673
+				'FHEE__EE_Admin_Page___per_page_screen_options__default',
3674
+				20
3675
+			),
3676
+			'option'  => $this->_current_page . '_' . $this->_current_view . '_per_page',
3677
+		];
3678
+		// ONLY add the screen option if the user has access to it.
3679
+		if ($this->check_user_access($this->_current_view, true)) {
3680
+			add_screen_option($option, $args);
3681
+		}
3682
+	}
3683
+
3684
+
3685
+	/**
3686
+	 * set_per_page_screen_option
3687
+	 * All this does is make sure that WordPress saves any per_page screen options (if set) for the current page.
3688
+	 * we have to do this rather than running inside the 'set-screen-options' hook because it runs earlier than
3689
+	 * admin_menu.
3690
+	 *
3691
+	 * @return void
3692
+	 */
3693
+	private function _set_per_page_screen_options()
3694
+	{
3695
+		if ($this->request->requestParamIsSet('wp_screen_options')) {
3696
+			check_admin_referer('screen-options-nonce', 'screenoptionnonce');
3697
+			if (! $user = wp_get_current_user()) {
3698
+				return;
3699
+			}
3700
+			$option = $this->request->getRequestParam('wp_screen_options[option]', '', DataType::KEY);
3701
+			if (! $option) {
3702
+				return;
3703
+			}
3704
+			$value      = $this->request->getRequestParam('wp_screen_options[value]', 0, DataType::INT);
3705
+			$map_option = $option;
3706
+			$option     = str_replace('-', '_', $option);
3707
+			switch ($map_option) {
3708
+				case $this->_current_page . '_' . $this->_current_view . '_per_page':
3709
+					$max_value = apply_filters(
3710
+						'FHEE__EE_Admin_Page___set_per_page_screen_options__max_value',
3711
+						999,
3712
+						$this->_current_page,
3713
+						$this->_current_view
3714
+					);
3715
+					if ($value < 1) {
3716
+						return;
3717
+					}
3718
+					$value = min($value, $max_value);
3719
+					break;
3720
+				default:
3721
+					$value = apply_filters(
3722
+						'FHEE__EE_Admin_Page___set_per_page_screen_options__value',
3723
+						false,
3724
+						$option,
3725
+						$value
3726
+					);
3727
+					if (false === $value) {
3728
+						return;
3729
+					}
3730
+					break;
3731
+			}
3732
+			update_user_meta($user->ID, $option, $value);
3733
+			wp_safe_redirect(remove_query_arg(['pagenum', 'apage', 'paged'], wp_get_referer()));
3734
+			exit;
3735
+		}
3736
+	}
3737
+
3738
+
3739
+	/**
3740
+	 * This just allows for setting the $_template_args property if it needs to be set outside the object
3741
+	 *
3742
+	 * @param array $data array that will be assigned to template args.
3743
+	 */
3744
+	public function set_template_args(array $data)
3745
+	{
3746
+		$this->_template_args = array_merge($this->_template_args, $data);
3747
+	}
3748
+
3749
+
3750
+	public function setAdminPageTitle(string $title)
3751
+	{
3752
+		$this->_admin_page_title = sanitize_text_field($title);
3753
+	}
3754
+
3755
+
3756
+	/**
3757
+	 * This makes available the WP transient system for temporarily moving data between routes
3758
+	 *
3759
+	 * @param string $route             the route that should receive the transient
3760
+	 * @param array  $data              the data that gets sent
3761
+	 * @param bool   $notices           If this is for notices then we use this to indicate so, otherwise it's just a
3762
+	 *                                  normal route transient.
3763
+	 * @param bool   $skip_route_verify Used to indicate we want to skip route verification.  This is usually ONLY used
3764
+	 *                                  when we are adding a transient before page_routes have been defined.
3765
+	 * @return void
3766
+	 * @throws EE_Error
3767
+	 */
3768
+	protected function _add_transient(
3769
+		string $route,
3770
+		array $data,
3771
+		bool $notices = false,
3772
+		bool $skip_route_verify = false
3773
+	) {
3774
+		$user_id = get_current_user_id();
3775
+		if (! $skip_route_verify) {
3776
+			$this->_verify_route($route);
3777
+		}
3778
+		// now let's set the string for what kind of transient we're setting
3779
+		$transient = $notices ? "ee_rte_n_tx_{$route}_$user_id" : "rte_tx_{$route}_$user_id";
3780
+		$data      = $notices ? ['notices' => $data] : $data;
3781
+		// is there already a transient for this route?  If there is then let's ADD to that transient
3782
+		$existing = is_multisite() && is_network_admin()
3783
+			? get_site_transient($transient)
3784
+			: get_transient($transient);
3785
+		if ($existing) {
3786
+			$data = array_merge($data, (array) $existing);
3787
+		}
3788
+		if (is_multisite() && is_network_admin()) {
3789
+			set_site_transient($transient, $data, 8);
3790
+		} else {
3791
+			set_transient($transient, $data, 8);
3792
+		}
3793
+	}
3794
+
3795
+
3796
+	/**
3797
+	 * this retrieves the temporary transient that has been set for moving data between routes.
3798
+	 *
3799
+	 * @param bool   $notices true we get notices transient. False we just return normal route transient
3800
+	 * @param string $route
3801
+	 * @return mixed data
3802
+	 */
3803
+	protected function _get_transient(bool $notices = false, string $route = '')
3804
+	{
3805
+		$user_id   = get_current_user_id();
3806
+		$route     = ! $route ? $this->_req_action : $route;
3807
+		$transient = $notices
3808
+			? 'ee_rte_n_tx_' . $route . '_' . $user_id
3809
+			: 'rte_tx_' . $route . '_' . $user_id;
3810
+		$data      = is_multisite() && is_network_admin()
3811
+			? get_site_transient($transient)
3812
+			: get_transient($transient);
3813
+		// delete transient after retrieval (just in case it hasn't expired);
3814
+		if (is_multisite() && is_network_admin()) {
3815
+			delete_site_transient($transient);
3816
+		} else {
3817
+			delete_transient($transient);
3818
+		}
3819
+		return $notices && isset($data['notices']) ? $data['notices'] : $data;
3820
+	}
3821
+
3822
+
3823
+	/**
3824
+	 * The purpose of this method is just to run garbage collection on any EE transients that might have expired but
3825
+	 * would not be called later. This will be assigned to run on a specific EE Admin page. (place the method in the
3826
+	 * default route callback on the EE_Admin page you want it run.)
3827
+	 *
3828
+	 * @return void
3829
+	 */
3830
+	protected function _transient_garbage_collection()
3831
+	{
3832
+		global $wpdb;
3833
+		// retrieve all existing transients
3834
+		$query =
3835
+			"SELECT option_name FROM $wpdb->options WHERE option_name LIKE '%rte_tx_%' OR option_name LIKE '%rte_n_tx_%'";
3836
+		if ($results = $wpdb->get_results($query)) {
3837
+			foreach ($results as $result) {
3838
+				$transient = str_replace('_transient_', '', $result->option_name);
3839
+				get_transient($transient);
3840
+				if (is_multisite() && is_network_admin()) {
3841
+					get_site_transient($transient);
3842
+				}
3843
+			}
3844
+		}
3845
+	}
3846
+
3847
+
3848
+	/**
3849
+	 * get_view
3850
+	 *
3851
+	 * @return string content of _view property
3852
+	 */
3853
+	public function get_view(): string
3854
+	{
3855
+		return $this->_view;
3856
+	}
3857
+
3858
+
3859
+	/**
3860
+	 * getter for the protected $_views property
3861
+	 *
3862
+	 * @return array
3863
+	 */
3864
+	public function get_views(): array
3865
+	{
3866
+		return $this->_views;
3867
+	}
3868
+
3869
+
3870
+	/**
3871
+	 * @param array $views
3872
+	 * @return void
3873
+	 * @since 5.0.13.p
3874
+	 */
3875
+	public function updateViews(array $views)
3876
+	{
3877
+		$this->_views = array_merge($this->_views, $views);
3878
+	}
3879
+
3880
+
3881
+	/**
3882
+    /**
3883
+	 * get_current_page
3884
+	 *
3885
+	 * @return string _current_page property value
3886
+	 */
3887
+	public function get_current_page(): string
3888
+	{
3889
+		return $this->_current_page;
3890
+	}
3891
+
3892
+
3893
+	/**
3894
+	 * get_current_view
3895
+	 *
3896
+	 * @return string _current_view property value
3897
+	 */
3898
+	public function get_current_view(): string
3899
+	{
3900
+		return $this->_current_view;
3901
+	}
3902
+
3903
+
3904
+	/**
3905
+	 * get_current_screen
3906
+	 *
3907
+	 * @return object The current WP_Screen object
3908
+	 */
3909
+	public function get_current_screen()
3910
+	{
3911
+		return $this->_current_screen;
3912
+	}
3913
+
3914
+
3915
+	/**
3916
+	 * get_current_page_view_url
3917
+	 *
3918
+	 * @return string This returns the url for the current_page_view.
3919
+	 */
3920
+	public function get_current_page_view_url(): string
3921
+	{
3922
+		return $this->_current_page_view_url;
3923
+	}
3924
+
3925
+
3926
+	/**
3927
+	 * just returns the Request
3928
+	 *
3929
+	 * @return RequestInterface
3930
+	 */
3931
+	public function get_request(): ?RequestInterface
3932
+	{
3933
+		return $this->request;
3934
+	}
3935
+
3936
+
3937
+	/**
3938
+	 * just returns the _req_data property
3939
+	 *
3940
+	 * @return array
3941
+	 */
3942
+	public function get_request_data(): array
3943
+	{
3944
+		return $this->request->requestParams();
3945
+	}
3946
+
3947
+
3948
+	/**
3949
+	 * returns the _req_data protected property
3950
+	 *
3951
+	 * @return string
3952
+	 */
3953
+	public function get_req_action(): string
3954
+	{
3955
+		return $this->_req_action;
3956
+	}
3957
+
3958
+
3959
+	/**
3960
+	 * @return bool  value of $_is_caf property
3961
+	 */
3962
+	public function is_caf(): bool
3963
+	{
3964
+		return $this->_is_caf;
3965
+	}
3966
+
3967
+
3968
+	/**
3969
+	 * @return array
3970
+	 */
3971
+	public function default_espresso_metaboxes(): array
3972
+	{
3973
+		return $this->_default_espresso_metaboxes;
3974
+	}
3975
+
3976
+
3977
+	/**
3978
+	 * @return string
3979
+	 */
3980
+	public function admin_base_url(): string
3981
+	{
3982
+		return $this->_admin_base_url;
3983
+	}
3984
+
3985
+
3986
+	/**
3987
+	 * @return string
3988
+	 */
3989
+	public function wp_page_slug(): string
3990
+	{
3991
+		return $this->_wp_page_slug;
3992
+	}
3993
+
3994
+
3995
+	/**
3996
+	 * updates  espresso configuration settings
3997
+	 *
3998
+	 * @param string                   $tab
3999
+	 * @param EE_Config_Base|EE_Config $config
4000
+	 * @param string                   $file file where error occurred
4001
+	 * @param string                   $func function  where error occurred
4002
+	 * @param string                   $line line no where error occurred
4003
+	 * @return bool
4004
+	 */
4005
+	protected function _update_espresso_configuration(
4006
+		string $tab,
4007
+		$config,
4008
+		string $file = '',
4009
+		string $func = '',
4010
+		string $line = ''
4011
+	): bool {
4012
+		// remove any options that are NOT going to be saved with the config settings.
4013
+		if (isset($config->core->ee_ueip_optin)) {
4014
+			// TODO: remove the following two lines and make sure values are migrated from 3.1
4015
+			update_option('ee_ueip_optin', $config->core->ee_ueip_optin);
4016
+			update_option('ee_ueip_has_notified', true);
4017
+		}
4018
+		// and save it (note we're also doing the network save here)
4019
+		$net_saved    = ! is_main_site() || EE_Network_Config::instance()->update_config(false, false);
4020
+		$config_saved = EE_Config::instance()->update_espresso_config(false, false);
4021
+		if ($config_saved && $net_saved) {
4022
+			EE_Error::add_success(sprintf(esc_html__('"%s" have been successfully updated.', 'event_espresso'), $tab));
4023
+			return true;
4024
+		}
4025
+		EE_Error::add_error(
4026
+			sprintf(esc_html__('The "%s" were not updated.', 'event_espresso'), $tab),
4027
+			$file,
4028
+			$func,
4029
+			$line
4030
+		);
4031
+		return false;
4032
+	}
4033
+
4034
+
4035
+	/**
4036
+	 * Returns an array to be used for EE_FOrm_Fields.helper.php's select_input as the $values argument.
4037
+	 *
4038
+	 * @return array
4039
+	 */
4040
+	public function get_yes_no_values(): array
4041
+	{
4042
+		return $this->_yes_no_values;
4043
+	}
4044
+
4045
+
4046
+	/**
4047
+	 * @return string
4048
+	 * @throws ReflectionException
4049
+	 * @since 5.0.0.p
4050
+	 */
4051
+	protected function _get_dir(): string
4052
+	{
4053
+		$reflector = new ReflectionClass($this->class_name);
4054
+		return dirname($reflector->getFileName());
4055
+	}
4056
+
4057
+
4058
+	/**
4059
+	 * A helper for getting a "next link".
4060
+	 *
4061
+	 * @param string $url   The url to link to
4062
+	 * @param string $class The class to use.
4063
+	 * @return string
4064
+	 */
4065
+	protected function _next_link(string $url, string $class = 'dashicons dashicons-arrow-right'): string
4066
+	{
4067
+		return '<a class="' . $class . '" href="' . $url . '"></a>';
4068
+	}
4069
+
4070
+
4071
+	/**
4072
+	 * A helper for getting a "previous link".
4073
+	 *
4074
+	 * @param string $url   The url to link to
4075
+	 * @param string $class The class to use.
4076
+	 * @return string
4077
+	 */
4078
+	protected function _previous_link(string $url, string $class = 'dashicons dashicons-arrow-left'): string
4079
+	{
4080
+		return '<a class="' . $class . '" href="' . $url . '"></a>';
4081
+	}
4082
+
4083
+
4084
+
4085
+
4086
+
4087
+
4088
+
4089
+	// below are some messages related methods that should be available across the EE_Admin system.  Note, these methods are NOT page specific
4090
+
4091
+
4092
+	/**
4093
+	 * This processes a request to resend a registration and assumes we have a _REG_ID for doing so. So if the caller
4094
+	 * knows that the _REG_ID isn't in the req_data array but CAN obtain it, the caller should ADD the _REG_ID to the
4095
+	 * _req_data array.
4096
+	 *
4097
+	 * @return bool success/fail
4098
+	 * @throws EE_Error
4099
+	 * @throws InvalidArgumentException
4100
+	 * @throws ReflectionException
4101
+	 * @throws InvalidDataTypeException
4102
+	 * @throws InvalidInterfaceException
4103
+	 */
4104
+	protected function _process_resend_registration(): bool
4105
+	{
4106
+		$this->_template_args['success'] = EED_Messages::process_resend($this->request->requestParams());
4107
+		do_action(
4108
+			'AHEE__EE_Admin_Page___process_resend_registration',
4109
+			$this->_template_args['success'],
4110
+			$this->request->requestParams()
4111
+		);
4112
+		return $this->_template_args['success'];
4113
+	}
4114
+
4115
+
4116
+	/**
4117
+	 * This automatically processes any payment message notifications when manual payment has been applied.
4118
+	 *
4119
+	 * @param EE_Payment $payment
4120
+	 * @return bool success/fail
4121
+	 */
4122
+	protected function _process_payment_notification(EE_Payment $payment): bool
4123
+	{
4124
+		add_filter('FHEE__EE_Payment_Processor__process_registration_payments__display_notifications', '__return_true');
4125
+		do_action('AHEE__EE_Admin_Page___process_admin_payment_notification', $payment);
4126
+		$this->_template_args['success'] = apply_filters(
4127
+			'FHEE__EE_Admin_Page___process_admin_payment_notification__success',
4128
+			false,
4129
+			$payment
4130
+		);
4131
+		return $this->_template_args['success'];
4132
+	}
4133
+
4134
+
4135
+	/**
4136
+	 * @param EEM_Base      $entity_model
4137
+	 * @param string        $entity_PK_name name of the primary key field used as a request param, ie: id, ID, etc
4138
+	 * @param string        $action         one of the EE_Admin_List_Table::ACTION_* constants: delete, restore, trash
4139
+	 * @param string        $delete_column  name of the field that denotes whether entity is trashed
4140
+	 * @param callable|null $callback       called after entity is trashed, restored, or deleted
4141
+	 * @return int|float
4142
+	 * @throws EE_Error
4143
+	 */
4144
+	protected function trashRestoreDeleteEntities(
4145
+		EEM_Base $entity_model,
4146
+		string $entity_PK_name,
4147
+		string $action = EE_Admin_List_Table::ACTION_DELETE,
4148
+		string $delete_column = '',
4149
+		?callable $callback = null
4150
+	) {
4151
+		$entity_PK      = $entity_model->get_primary_key_field();
4152
+		$entity_PK_name = $entity_PK_name ?: $entity_PK->get_name();
4153
+		$entity_PK_type = $this->resolveEntityFieldDataType($entity_PK);
4154
+		// grab ID if deleting a single entity
4155
+		if ($this->request->requestParamIsSet($entity_PK_name)) {
4156
+			$ID = $this->request->getRequestParam($entity_PK_name, 0, $entity_PK_type);
4157
+			return $this->trashRestoreDeleteEntity($entity_model, $ID, $action, $delete_column, $callback) ? 1 : 0;
4158
+		}
4159
+		// or grab checkbox array if bulk deleting
4160
+		$checkboxes = $this->request->getRequestParam('checkbox', [], $entity_PK_type, true);
4161
+		if (empty($checkboxes)) {
4162
+			return 0;
4163
+		}
4164
+		$success = 0;
4165
+		$IDs     = array_keys($checkboxes);
4166
+		// cycle thru bulk action checkboxes
4167
+		foreach ($IDs as $ID) {
4168
+			// increment $success
4169
+			if ($this->trashRestoreDeleteEntity($entity_model, $ID, $action, $delete_column, $callback)) {
4170
+				$success++;
4171
+			}
4172
+		}
4173
+		$count = (int) count($checkboxes);
4174
+		// if multiple entities were deleted successfully, then $deleted will be full count of deletions,
4175
+		// otherwise it will be a fraction of ( actual deletions / total entities to be deleted )
4176
+		return $success === $count ? $count : $success / $count;
4177
+	}
4178
+
4179
+
4180
+	/**
4181
+	 * @param EE_Primary_Key_Field_Base $entity_PK
4182
+	 * @return string
4183
+	 * @throws EE_Error
4184
+	 * @since   4.10.30.p
4185
+	 */
4186
+	private function resolveEntityFieldDataType(EE_Primary_Key_Field_Base $entity_PK): string
4187
+	{
4188
+		$entity_PK_type = $entity_PK->getSchemaType();
4189
+		switch ($entity_PK_type) {
4190
+			case 'boolean':
4191
+				return DataType::BOOL;
4192
+			case 'integer':
4193
+				return DataType::INT;
4194
+			case 'number':
4195
+				return DataType::FLOAT;
4196
+			case 'string':
4197
+				return DataType::STRING;
4198
+		}
4199
+		throw new RuntimeException(
4200
+			sprintf(
4201
+				esc_html__(
4202
+					'"%1$s" is an invalid schema type for the %2$s primary key.',
4203
+					'event_espresso'
4204
+				),
4205
+				$entity_PK_type,
4206
+				$entity_PK->get_name()
4207
+			)
4208
+		);
4209
+	}
4210
+
4211
+
4212
+	/**
4213
+	 * @param EEM_Base      $entity_model
4214
+	 * @param int|string    $entity_ID
4215
+	 * @param string        $action        one of the EE_Admin_List_Table::ACTION_* constants: delete, restore, trash
4216
+	 * @param string        $delete_column name of the field that denotes whether entity is trashed
4217
+	 * @param callable|null $callback      called after entity is trashed, restored, or deleted
4218
+	 * @return bool
4219
+	 */
4220
+	protected function trashRestoreDeleteEntity(
4221
+		EEM_Base $entity_model,
4222
+		$entity_ID,
4223
+		string $action,
4224
+		string $delete_column,
4225
+		?callable $callback = null
4226
+	): bool {
4227
+		$entity_ID = absint($entity_ID);
4228
+		if (! $entity_ID) {
4229
+			$this->trashRestoreDeleteError($action, $entity_model);
4230
+		}
4231
+		$result = 0;
4232
+		try {
4233
+			$entity = $entity_model->get_one_by_ID($entity_ID);
4234
+			if (! $entity instanceof EE_Base_Class) {
4235
+				throw new DomainException(
4236
+					sprintf(
4237
+						esc_html__(
4238
+							'Missing or invalid %1$s entity with ID of "%2$s" returned from db.',
4239
+							'event_espresso'
4240
+						),
4241
+						str_replace('EEM_', '', $entity_model->get_this_model_name()),
4242
+						$entity_ID
4243
+					)
4244
+				);
4245
+			}
4246
+			switch ($action) {
4247
+				case EE_Admin_List_Table::ACTION_DELETE:
4248
+					$result = (bool) $entity->delete_permanently();
4249
+					break;
4250
+				case EE_Admin_List_Table::ACTION_RESTORE:
4251
+					$result = $entity->delete_or_restore(false);
4252
+					break;
4253
+				case EE_Admin_List_Table::ACTION_TRASH:
4254
+					$result = $entity->delete_or_restore();
4255
+					break;
4256
+			}
4257
+		} catch (Exception $exception) {
4258
+			$this->trashRestoreDeleteError($action, $entity_model, $exception);
4259
+		}
4260
+		if (is_callable($callback)) {
4261
+			call_user_func_array($callback, [$entity_model, $entity_ID, $action, $result, $delete_column]);
4262
+		}
4263
+		return $result;
4264
+	}
4265
+
4266
+
4267
+	/**
4268
+	 * @param EEM_Base $entity_model
4269
+	 * @param string   $delete_column
4270
+	 * @since 4.10.30.p
4271
+	 */
4272
+	private function validateDeleteColumn(EEM_Base $entity_model, string $delete_column)
4273
+	{
4274
+		if (empty($delete_column)) {
4275
+			throw new DomainException(
4276
+				sprintf(
4277
+					esc_html__(
4278
+						'You need to specify the name of the "delete column" on the %2$s model, in order to trash or restore an entity.',
4279
+						'event_espresso'
4280
+					),
4281
+					$entity_model->get_this_model_name()
4282
+				)
4283
+			);
4284
+		}
4285
+		if (! $entity_model->has_field($delete_column)) {
4286
+			throw new DomainException(
4287
+				sprintf(
4288
+					esc_html__(
4289
+						'The %1$s field does not exist on the %2$s model.',
4290
+						'event_espresso'
4291
+					),
4292
+					$delete_column,
4293
+					$entity_model->get_this_model_name()
4294
+				)
4295
+			);
4296
+		}
4297
+	}
4298
+
4299
+
4300
+	/**
4301
+	 * @param EEM_Base       $entity_model
4302
+	 * @param Exception|null $exception
4303
+	 * @param string         $action
4304
+	 * @since 4.10.30.p
4305
+	 */
4306
+	private function trashRestoreDeleteError(string $action, EEM_Base $entity_model, Exception $exception = null)
4307
+	{
4308
+		if ($exception instanceof Exception) {
4309
+			throw new RuntimeException(
4310
+				sprintf(
4311
+					esc_html__(
4312
+						'Could not %1$s the %2$s because the following error occurred: %3$s',
4313
+						'event_espresso'
4314
+					),
4315
+					$action,
4316
+					$entity_model->get_this_model_name(),
4317
+					$exception->getMessage()
4318
+				)
4319
+			);
4320
+		}
4321
+		throw new RuntimeException(
4322
+			sprintf(
4323
+				esc_html__(
4324
+					'Could not %1$s the %2$s because an invalid ID was received.',
4325
+					'event_espresso'
4326
+				),
4327
+				$action,
4328
+				$entity_model->get_this_model_name()
4329
+			)
4330
+		);
4331
+	}
4332 4332
 }
Please login to merge, or discard this patch.
Spacing   +173 added lines, -173 removed lines patch added patch discarded remove patch
@@ -101,7 +101,7 @@  discard block
 block discarded – undo
101 101
      */
102 102
     protected ?bool $_is_UI_request = null;
103 103
 
104
-    protected bool  $_is_caf        = false;                                                                                                                                                                                                                                  // This is just a property that flags whether the given route is a caffeinated route or not.
104
+    protected bool  $_is_caf        = false; // This is just a property that flags whether the given route is a caffeinated route or not.
105 105
 
106 106
     protected bool  $_routing       = false;
107 107
 
@@ -595,7 +595,7 @@  discard block
 block discarded – undo
595 595
         $ee_menu_slugs = (array) $ee_menu_slugs;
596 596
         if (
597 597
             ! $this->request->isAjax()
598
-            && (! $this->_current_page || ! isset($ee_menu_slugs[ $this->_current_page ]))
598
+            && ( ! $this->_current_page || ! isset($ee_menu_slugs[$this->_current_page]))
599 599
         ) {
600 600
             return;
601 601
         }
@@ -614,7 +614,7 @@  discard block
 block discarded – undo
614 614
             ? $route
615 615
             : $req_action;
616 616
         $this->_current_view = $this->_req_action;
617
-        $this->_req_nonce    = $this->_req_action . '_nonce';
617
+        $this->_req_nonce    = $this->_req_action.'_nonce';
618 618
         $this->_define_page_props();
619 619
         $this->_current_page_view_url = add_query_arg(
620 620
             ['page' => $this->_current_page, 'action' => $this->_current_view],
@@ -638,33 +638,33 @@  discard block
 block discarded – undo
638 638
         }
639 639
         // filter routes and page_config so addons can add their stuff. Filtering done per class
640 640
         $this->_page_routes = apply_filters(
641
-            'FHEE__' . $this->class_name . '__page_setup__page_routes',
641
+            'FHEE__'.$this->class_name.'__page_setup__page_routes',
642 642
             $this->_page_routes,
643 643
             $this
644 644
         );
645 645
         $this->_page_config = apply_filters(
646
-            'FHEE__' . $this->class_name . '__page_setup__page_config',
646
+            'FHEE__'.$this->class_name.'__page_setup__page_config',
647 647
             $this->_page_config,
648 648
             $this
649 649
         );
650 650
         if ($this->base_class_name !== '') {
651 651
             $this->_page_routes = apply_filters(
652
-                'FHEE__' . $this->base_class_name . '__page_setup__page_routes',
652
+                'FHEE__'.$this->base_class_name.'__page_setup__page_routes',
653 653
                 $this->_page_routes,
654 654
                 $this
655 655
             );
656 656
             $this->_page_config = apply_filters(
657
-                'FHEE__' . $this->base_class_name . '__page_setup__page_config',
657
+                'FHEE__'.$this->base_class_name.'__page_setup__page_config',
658 658
                 $this->_page_config,
659 659
                 $this
660 660
             );
661 661
         }
662 662
         // if AHEE__EE_Admin_Page__route_admin_request_$this->_current_view method is present
663 663
         // then we call it hooked into the AHEE__EE_Admin_Page__route_admin_request action
664
-        if (method_exists($this, 'AHEE__EE_Admin_Page__route_admin_request_' . $this->_current_view)) {
664
+        if (method_exists($this, 'AHEE__EE_Admin_Page__route_admin_request_'.$this->_current_view)) {
665 665
             add_action(
666 666
                 'AHEE__EE_Admin_Page__route_admin_request',
667
-                [$this, 'AHEE__EE_Admin_Page__route_admin_request_' . $this->_current_view],
667
+                [$this, 'AHEE__EE_Admin_Page__route_admin_request_'.$this->_current_view],
668 668
                 10,
669 669
                 2
670 670
             );
@@ -677,8 +677,8 @@  discard block
 block discarded – undo
677 677
             if ($this->_is_UI_request) {
678 678
                 // admin_init stuff - global, all views for this page class, specific view
679 679
                 add_action('admin_init', [$this, 'admin_init']);
680
-                if (method_exists($this, 'admin_init_' . $this->_current_view)) {
681
-                    add_action('admin_init', [$this, 'admin_init_' . $this->_current_view], 15);
680
+                if (method_exists($this, 'admin_init_'.$this->_current_view)) {
681
+                    add_action('admin_init', [$this, 'admin_init_'.$this->_current_view], 15);
682 682
                 }
683 683
             } else {
684 684
                 // hijack regular WP loading and route admin request immediately
@@ -697,12 +697,12 @@  discard block
 block discarded – undo
697 697
      */
698 698
     private function _do_other_page_hooks()
699 699
     {
700
-        $registered_pages = apply_filters('FHEE_do_other_page_hooks_' . $this->page_slug, []);
700
+        $registered_pages = apply_filters('FHEE_do_other_page_hooks_'.$this->page_slug, []);
701 701
         foreach ($registered_pages as $page) {
702 702
             // now let's set up the file name and class that should be present
703 703
             $classname = str_replace('.class.php', '', $page);
704 704
             // autoloaders should take care of loading file
705
-            if (! class_exists($classname)) {
705
+            if ( ! class_exists($classname)) {
706 706
                 $error_msg[] = sprintf(
707 707
                     esc_html__(
708 708
                         'Something went wrong with loading the %s admin hooks page.',
@@ -719,7 +719,7 @@  discard block
 block discarded – undo
719 719
                                    ),
720 720
                                    $page,
721 721
                                    '<br />',
722
-                                   '<strong>' . $classname . '</strong>'
722
+                                   '<strong>'.$classname.'</strong>'
723 723
                                );
724 724
                 throw new EE_Error(implode('||', $error_msg));
725 725
             }
@@ -768,13 +768,13 @@  discard block
 block discarded – undo
768 768
         // load admin_notices - global, page class, and view specific
769 769
         add_action('admin_notices', [$this, 'admin_notices_global'], 5);
770 770
         add_action('admin_notices', [$this, 'admin_notices']);
771
-        if (method_exists($this, 'admin_notices_' . $this->_current_view)) {
772
-            add_action('admin_notices', [$this, 'admin_notices_' . $this->_current_view], 15);
771
+        if (method_exists($this, 'admin_notices_'.$this->_current_view)) {
772
+            add_action('admin_notices', [$this, 'admin_notices_'.$this->_current_view], 15);
773 773
         }
774 774
         // load network admin_notices - global, page class, and view specific
775 775
         add_action('network_admin_notices', [$this, 'network_admin_notices_global'], 5);
776
-        if (method_exists($this, 'network_admin_notices_' . $this->_current_view)) {
777
-            add_action('network_admin_notices', [$this, 'network_admin_notices_' . $this->_current_view]);
776
+        if (method_exists($this, 'network_admin_notices_'.$this->_current_view)) {
777
+            add_action('network_admin_notices', [$this, 'network_admin_notices_'.$this->_current_view]);
778 778
         }
779 779
         // this will save any per_page screen options if they are present
780 780
         $this->_set_per_page_screen_options();
@@ -900,7 +900,7 @@  discard block
 block discarded – undo
900 900
      */
901 901
     protected function _verify_routes(): bool
902 902
     {
903
-        if (! $this->_current_page && ! $this->request->isAjax()) {
903
+        if ( ! $this->_current_page && ! $this->request->isAjax()) {
904 904
             return false;
905 905
         }
906 906
         // check that the page_routes array is not empty
@@ -911,7 +911,7 @@  discard block
 block discarded – undo
911 911
                 $this->_admin_page_title
912 912
             );
913 913
             // developer error msg
914
-            $error_msg .= '||' . $error_msg
914
+            $error_msg .= '||'.$error_msg
915 915
                           . esc_html__(
916 916
                               ' Make sure the "set_page_routes()" method exists, and is setting the "_page_routes" array properly.',
917 917
                               'event_espresso'
@@ -929,8 +929,8 @@  discard block
 block discarded – undo
929 929
         }
930 930
         // and that the requested page route exists
931 931
         if (array_key_exists($this->_req_action, $this->_page_routes)) {
932
-            $this->_route        = $this->_page_routes[ $this->_req_action ];
933
-            $this->_route_config = $this->_page_config[ $this->_req_action ] ?? [];
932
+            $this->_route        = $this->_page_routes[$this->_req_action];
933
+            $this->_route_config = $this->_page_config[$this->_req_action] ?? [];
934 934
         } else {
935 935
             // user error msg
936 936
             $error_msg = sprintf(
@@ -941,7 +941,7 @@  discard block
 block discarded – undo
941 941
                 $this->_admin_page_title
942 942
             );
943 943
             // developer error msg
944
-            $error_msg .= '||' . $error_msg
944
+            $error_msg .= '||'.$error_msg
945 945
                           . sprintf(
946 946
                               esc_html__(
947 947
                                   ' Create a key in the "_page_routes" array named "%s" and set its value to the appropriate method.',
@@ -952,7 +952,7 @@  discard block
 block discarded – undo
952 952
             throw new EE_Error($error_msg);
953 953
         }
954 954
         // and that a default route exists
955
-        if (! array_key_exists('default', $this->_page_routes)) {
955
+        if ( ! array_key_exists('default', $this->_page_routes)) {
956 956
             // user error msg
957 957
             $error_msg = sprintf(
958 958
                 esc_html__(
@@ -962,7 +962,7 @@  discard block
 block discarded – undo
962 962
                 $this->_admin_page_title
963 963
             );
964 964
             // developer error msg
965
-            $error_msg .= '||' . $error_msg
965
+            $error_msg .= '||'.$error_msg
966 966
                           . esc_html__(
967 967
                               ' Create a key in the "_page_routes" array named "default" and set its value to your default page method.',
968 968
                               'event_espresso'
@@ -1002,7 +1002,7 @@  discard block
 block discarded – undo
1002 1002
             $this->_admin_page_title
1003 1003
         );
1004 1004
         // developer error msg
1005
-        $error_msg .= '||' . $error_msg
1005
+        $error_msg .= '||'.$error_msg
1006 1006
                       . sprintf(
1007 1007
                           esc_html__(
1008 1008
                               ' Check the route you are using in your method (%s) and make sure it matches a route set in your "_page_routes" array property',
@@ -1030,7 +1030,7 @@  discard block
 block discarded – undo
1030 1030
     protected function _verify_nonce(string $nonce, string $nonce_ref)
1031 1031
     {
1032 1032
         // verify nonce against expected value
1033
-        if (! wp_verify_nonce($nonce, $nonce_ref)) {
1033
+        if ( ! wp_verify_nonce($nonce, $nonce_ref)) {
1034 1034
             // these are not the droids you are looking for !!!
1035 1035
             $msg = sprintf(
1036 1036
                 esc_html__('%sNonce Fail.%s', 'event_espresso'),
@@ -1047,7 +1047,7 @@  discard block
 block discarded – undo
1047 1047
                     __CLASS__
1048 1048
                 );
1049 1049
             }
1050
-            if (! $this->request->isAjax()) {
1050
+            if ( ! $this->request->isAjax()) {
1051 1051
                 wp_die($msg);
1052 1052
             }
1053 1053
             EE_Error::add_error($msg, __FILE__, __FUNCTION__, __LINE__);
@@ -1072,7 +1072,7 @@  discard block
 block discarded – undo
1072 1072
      */
1073 1073
     protected function _route_admin_request()
1074 1074
     {
1075
-        if (! $this->_is_UI_request) {
1075
+        if ( ! $this->_is_UI_request) {
1076 1076
             $this->_verify_routes();
1077 1077
         }
1078 1078
         $nonce_check = ! isset($this->_route_config['require_nonce']) || $this->_route_config['require_nonce'];
@@ -1092,7 +1092,7 @@  discard block
 block discarded – undo
1092 1092
 
1093 1093
         // action right before calling route
1094 1094
         // (hook is something like 'AHEE__Registrations_Admin_Page__route_admin_request')
1095
-        if (! did_action('AHEE__EE_Admin_Page__route_admin_request')) {
1095
+        if ( ! did_action('AHEE__EE_Admin_Page__route_admin_request')) {
1096 1096
             do_action('AHEE__EE_Admin_Page__route_admin_request', $this->_current_view, $this);
1097 1097
         }
1098 1098
         // strip _wp_http_referer from the server REQUEST_URI
@@ -1107,7 +1107,7 @@  discard block
 block discarded – undo
1107 1107
         $this->request->setRequestParam('_wp_http_referer', $cleaner_request_uri, true);
1108 1108
         $this->request->setServerParam('REQUEST_URI', $cleaner_request_uri, true);
1109 1109
         $route_callback = [];
1110
-        if (! empty($func)) {
1110
+        if ( ! empty($func)) {
1111 1111
             if (is_array($func) && is_callable($func)) {
1112 1112
                 $route_callback = $func;
1113 1113
             } elseif (is_string($func)) {
@@ -1121,7 +1121,7 @@  discard block
 block discarded – undo
1121 1121
             }
1122 1122
             [$class, $method] = $route_callback;
1123 1123
             // is it neither a class method NOR a standalone function?
1124
-            if (! is_callable($route_callback)) {
1124
+            if ( ! is_callable($route_callback)) {
1125 1125
                 // user error msg
1126 1126
                 $error_msg = esc_html__(
1127 1127
                     'An error occurred. The  requested page route could not be found.',
@@ -1230,7 +1230,7 @@  discard block
 block discarded – undo
1230 1230
                 if (strpos($key, 'nonce') !== false) {
1231 1231
                     continue;
1232 1232
                 }
1233
-                $args[ 'wp_referer[' . $key . ']' ] = is_string($value) ? htmlspecialchars($value) : $value;
1233
+                $args['wp_referer['.$key.']'] = is_string($value) ? htmlspecialchars($value) : $value;
1234 1234
             }
1235 1235
         }
1236 1236
         return EEH_URL::add_query_args_and_nonce($args, $url, $exclude_nonce, $context);
@@ -1270,12 +1270,12 @@  discard block
 block discarded – undo
1270 1270
      */
1271 1271
     protected function _add_help_tabs()
1272 1272
     {
1273
-        if (isset($this->_page_config[ $this->_req_action ])) {
1274
-            $config = $this->_page_config[ $this->_req_action ];
1273
+        if (isset($this->_page_config[$this->_req_action])) {
1274
+            $config = $this->_page_config[$this->_req_action];
1275 1275
             // let's see if there is a help_sidebar set for the current route and we'll set that up for usage as well.
1276 1276
             if (is_array($config) && isset($config['help_sidebar'])) {
1277 1277
                 // check that the callback given is valid
1278
-                if (! method_exists($this, $config['help_sidebar'])) {
1278
+                if ( ! method_exists($this, $config['help_sidebar'])) {
1279 1279
                     throw new EE_Error(
1280 1280
                         sprintf(
1281 1281
                             esc_html__(
@@ -1288,18 +1288,18 @@  discard block
 block discarded – undo
1288 1288
                     );
1289 1289
                 }
1290 1290
                 $content = apply_filters(
1291
-                    'FHEE__' . $this->class_name . '__add_help_tabs__help_sidebar',
1291
+                    'FHEE__'.$this->class_name.'__add_help_tabs__help_sidebar',
1292 1292
                     $this->{$config['help_sidebar']}()
1293 1293
                 );
1294 1294
                 $this->_current_screen->set_help_sidebar($content);
1295 1295
             }
1296
-            if (! isset($config['help_tabs'])) {
1296
+            if ( ! isset($config['help_tabs'])) {
1297 1297
                 return;
1298 1298
             } //no help tabs for this route
1299 1299
             foreach ((array) $config['help_tabs'] as $tab_id => $cfg) {
1300 1300
                 // we're here so there ARE help tabs!
1301 1301
                 // make sure we've got what we need
1302
-                if (! isset($cfg['title'])) {
1302
+                if ( ! isset($cfg['title'])) {
1303 1303
                     throw new EE_Error(
1304 1304
                         esc_html__(
1305 1305
                             'The _page_config array is not set up properly for help tabs.  It is missing a title',
@@ -1307,7 +1307,7 @@  discard block
 block discarded – undo
1307 1307
                         )
1308 1308
                     );
1309 1309
                 }
1310
-                if (! isset($cfg['filename']) && ! isset($cfg['callback']) && ! isset($cfg['content'])) {
1310
+                if ( ! isset($cfg['filename']) && ! isset($cfg['callback']) && ! isset($cfg['content'])) {
1311 1311
                     throw new EE_Error(
1312 1312
                         esc_html__(
1313 1313
                             'The _page_config array is not setup properly for help tabs. It is missing a either a filename reference, or a callback reference or a content reference so there is no way to know the content for the help tab',
@@ -1316,11 +1316,11 @@  discard block
 block discarded – undo
1316 1316
                     );
1317 1317
                 }
1318 1318
                 // first priority goes to content.
1319
-                if (! empty($cfg['content'])) {
1319
+                if ( ! empty($cfg['content'])) {
1320 1320
                     $content = $cfg['content'];
1321 1321
                     // second priority goes to filename
1322
-                } elseif (! empty($cfg['filename'])) {
1323
-                    $file_path = $this->_get_dir() . '/help_tabs/' . $cfg['filename'] . '.help_tab.php';
1322
+                } elseif ( ! empty($cfg['filename'])) {
1323
+                    $file_path = $this->_get_dir().'/help_tabs/'.$cfg['filename'].'.help_tab.php';
1324 1324
                     // it's possible that the file is located on decaf route (and above sets up for caf route, if this is the case then lets check decaf route too)
1325 1325
                     $file_path = ! is_readable($file_path) ? EE_ADMIN_PAGES
1326 1326
                                                              . basename($this->_get_dir())
@@ -1328,7 +1328,7 @@  discard block
 block discarded – undo
1328 1328
                                                              . $cfg['filename']
1329 1329
                                                              . '.help_tab.php' : $file_path;
1330 1330
                     // if file is STILL not readable then let's do an EE_Error so its more graceful than a fatal error.
1331
-                    if (! isset($cfg['callback']) && ! is_readable($file_path)) {
1331
+                    if ( ! isset($cfg['callback']) && ! is_readable($file_path)) {
1332 1332
                         EE_Error::add_error(
1333 1333
                             sprintf(
1334 1334
                                 esc_html__(
@@ -1376,7 +1376,7 @@  discard block
 block discarded – undo
1376 1376
                     return;
1377 1377
                 }
1378 1378
                 // setup config array for help tab method
1379
-                $id  = $this->page_slug . '-' . $this->_req_action . '-' . $tab_id;
1379
+                $id  = $this->page_slug.'-'.$this->_req_action.'-'.$tab_id;
1380 1380
                 $_ht = [
1381 1381
                     'id'       => $id,
1382 1382
                     'title'    => $cfg['title'],
@@ -1402,8 +1402,8 @@  discard block
 block discarded – undo
1402 1402
             $qtips = (array) $this->_route_config['qtips'];
1403 1403
             // load qtip loader
1404 1404
             $path = [
1405
-                $this->_get_dir() . '/qtips/',
1406
-                EE_ADMIN_PAGES . basename($this->_get_dir()) . '/qtips/',
1405
+                $this->_get_dir().'/qtips/',
1406
+                EE_ADMIN_PAGES.basename($this->_get_dir()).'/qtips/',
1407 1407
             ];
1408 1408
             EEH_Qtip_Loader::instance()->register($qtips, $path);
1409 1409
         }
@@ -1425,7 +1425,7 @@  discard block
 block discarded – undo
1425 1425
         $i        = 0;
1426 1426
         $only_tab = count($this->_page_config) < 2;
1427 1427
         foreach ($this->_page_config as $slug => $config) {
1428
-            if (! is_array($config) || empty($config['nav'])) {
1428
+            if ( ! is_array($config) || empty($config['nav'])) {
1429 1429
                 continue;
1430 1430
             }
1431 1431
             // no nav tab for this config
@@ -1434,7 +1434,7 @@  discard block
 block discarded – undo
1434 1434
                 // nav tab is only to appear when route requested.
1435 1435
                 continue;
1436 1436
             }
1437
-            if (! $this->check_user_access($slug, true)) {
1437
+            if ( ! $this->check_user_access($slug, true)) {
1438 1438
                 // no nav tab because current user does not have access.
1439 1439
                 continue;
1440 1440
             }
@@ -1442,20 +1442,20 @@  discard block
 block discarded – undo
1442 1442
             $css_class .= $only_tab ? ' ee-only-tab' : '';
1443 1443
             $css_class .= " ee-nav-tab__$slug";
1444 1444
 
1445
-            $this->_nav_tabs[ $slug ] = [
1445
+            $this->_nav_tabs[$slug] = [
1446 1446
                 'url'       => $config['nav']['url'] ?? EE_Admin_Page::add_query_args_and_nonce(
1447 1447
                         ['action' => $slug],
1448 1448
                         $this->_admin_base_url
1449 1449
                     ),
1450 1450
                 'link_text' => $this->navTabLabel($config['nav'], $slug),
1451
-                'css_class' => $this->_req_action === $slug ? $css_class . ' nav-tab-active' : $css_class,
1451
+                'css_class' => $this->_req_action === $slug ? $css_class.' nav-tab-active' : $css_class,
1452 1452
                 'order'     => $config['nav']['order'] ?? $i,
1453 1453
             ];
1454 1454
             $i++;
1455 1455
         }
1456 1456
         // if $this->_nav_tabs is empty then lets set the default
1457 1457
         if (empty($this->_nav_tabs)) {
1458
-            $this->_nav_tabs[ $this->_default_nav_tab_name ] = [
1458
+            $this->_nav_tabs[$this->_default_nav_tab_name] = [
1459 1459
                 'url'       => $this->_admin_base_url,
1460 1460
                 'link_text' => ucwords(str_replace('_', ' ', $this->_default_nav_tab_name)),
1461 1461
                 'css_class' => 'nav-tab-active',
@@ -1471,11 +1471,11 @@  discard block
 block discarded – undo
1471 1471
     {
1472 1472
         $label = $nav_tab['label'] ?? ucwords(str_replace('_', ' ', $slug));
1473 1473
         $icon  = $nav_tab['icon'] ?? null;
1474
-        $icon  = $icon ? '<span class="dashicons ' . $icon . '"></span>' : '';
1474
+        $icon  = $icon ? '<span class="dashicons '.$icon.'"></span>' : '';
1475 1475
         return '
1476 1476
             <span class="ee-admin-screen-tab__label">
1477
-                ' . $icon . '
1478
-                <span class="ee-nav-label__text">' . $label . '</span>
1477
+                ' . $icon.'
1478
+                <span class="ee-nav-label__text">' . $label.'</span>
1479 1479
             </span>';
1480 1480
     }
1481 1481
 
@@ -1493,10 +1493,10 @@  discard block
 block discarded – undo
1493 1493
             foreach ($this->_route_config['labels'] as $label => $text) {
1494 1494
                 if (is_array($text)) {
1495 1495
                     foreach ($text as $sublabel => $subtext) {
1496
-                        $this->_labels[ $label ][ $sublabel ] = $subtext;
1496
+                        $this->_labels[$label][$sublabel] = $subtext;
1497 1497
                     }
1498 1498
                 } else {
1499
-                    $this->_labels[ $label ] = $text;
1499
+                    $this->_labels[$label] = $text;
1500 1500
                 }
1501 1501
             }
1502 1502
         }
@@ -1518,8 +1518,8 @@  discard block
 block discarded – undo
1518 1518
     {
1519 1519
         // if no route_to_check is passed in then use the current route set via _req_action
1520 1520
         $action = $route_to_check ?: $this->_req_action;
1521
-        $capability = ! empty($this->_page_routes[ $action ]['capability'])
1522
-            ? $this->_page_routes[ $action ]['capability']
1521
+        $capability = ! empty($this->_page_routes[$action]['capability'])
1522
+            ? $this->_page_routes[$action]['capability']
1523 1523
             : null;
1524 1524
 
1525 1525
         if (empty($capability)) {
@@ -1569,14 +1569,14 @@  discard block
 block discarded – undo
1569 1569
         string $priority = 'default',
1570 1570
         ?array $callback_args = null
1571 1571
     ) {
1572
-        if (! (is_callable($callback) || ! function_exists($callback))) {
1572
+        if ( ! (is_callable($callback) || ! function_exists($callback))) {
1573 1573
             return;
1574 1574
         }
1575 1575
 
1576 1576
         add_meta_box($box_id, $title, $callback, $screen, $context, $priority, $callback_args);
1577 1577
         add_filter(
1578 1578
             "postbox_classes_{$this->_wp_page_slug}_$box_id",
1579
-            function ($classes) {
1579
+            function($classes) {
1580 1580
                 $classes[] = 'ee-admin-container';
1581 1581
                 return $classes;
1582 1582
             }
@@ -1667,8 +1667,8 @@  discard block
 block discarded – undo
1667 1667
             <div class="ee-notices"></div>
1668 1668
             <div class="ee-admin-dialog-container-inner-content"></div>
1669 1669
         </div>
1670
-        <span id="current_timezone" class="hidden">' . esc_html(EEH_DTT_Helper::get_timezone()) . '</span>
1671
-        <input type="hidden" id="espresso_admin_current_page" value="' . esc_attr($this->_current_page) . '"/>';
1670
+        <span id="current_timezone" class="hidden">' . esc_html(EEH_DTT_Helper::get_timezone()).'</span>
1671
+        <input type="hidden" id="espresso_admin_current_page" value="' . esc_attr($this->_current_page).'"/>';
1672 1672
     }
1673 1673
 
1674 1674
 
@@ -1702,7 +1702,7 @@  discard block
 block discarded – undo
1702 1702
         // loop through the array and setup content
1703 1703
         foreach ($help_array as $trigger => $help) {
1704 1704
             // make sure the array is set up properly
1705
-            if (! isset($help['title'], $help['content'])) {
1705
+            if ( ! isset($help['title'], $help['content'])) {
1706 1706
                 throw new EE_Error(
1707 1707
                     esc_html__(
1708 1708
                         'Does not look like the popup content array has been setup correctly.  Might want to double check that.  Read the comments for the _get_help_popup_content method found in "EE_Admin_Page" class',
@@ -1716,8 +1716,8 @@  discard block
 block discarded – undo
1716 1716
                 'help_popup_title'   => $help['title'],
1717 1717
                 'help_popup_content' => $help['content'],
1718 1718
             ];
1719
-            $content       .= EEH_Template::display_template(
1720
-                EE_ADMIN_TEMPLATE . 'admin_help_popup.template.php',
1719
+            $content .= EEH_Template::display_template(
1720
+                EE_ADMIN_TEMPLATE.'admin_help_popup.template.php',
1721 1721
                 $template_args,
1722 1722
                 true
1723 1723
             );
@@ -1739,15 +1739,15 @@  discard block
 block discarded – undo
1739 1739
     private function _get_help_content(): array
1740 1740
     {
1741 1741
         // what is the method we're looking for?
1742
-        $method_name = '_help_popup_content_' . $this->_req_action;
1742
+        $method_name = '_help_popup_content_'.$this->_req_action;
1743 1743
         // if method doesn't exist let's get out.
1744
-        if (! method_exists($this, $method_name)) {
1744
+        if ( ! method_exists($this, $method_name)) {
1745 1745
             return [];
1746 1746
         }
1747 1747
         // k we're good to go let's retrieve the help array
1748 1748
         $help_array = $this->{$method_name}();
1749 1749
         // make sure we've got an array!
1750
-        if (! is_array($help_array)) {
1750
+        if ( ! is_array($help_array)) {
1751 1751
             throw new EE_Error(
1752 1752
                 esc_html__(
1753 1753
                     'Something went wrong with help popup content generation. Expecting an array and well, this ain\'t no array bub.',
@@ -1779,15 +1779,15 @@  discard block
 block discarded – undo
1779 1779
         // let's check and see if there is any content set for this popup.  If there isn't then we'll include a default title and content so that developers know something needs to be corrected
1780 1780
         $help_array   = $this->_get_help_content();
1781 1781
         $help_content = '';
1782
-        if (empty($help_array) || ! isset($help_array[ $trigger_id ])) {
1783
-            $help_array[ $trigger_id ] = [
1782
+        if (empty($help_array) || ! isset($help_array[$trigger_id])) {
1783
+            $help_array[$trigger_id] = [
1784 1784
                 'title'   => esc_html__('Missing Content', 'event_espresso'),
1785 1785
                 'content' => esc_html__(
1786 1786
                     'A trigger has been set that doesn\'t have any corresponding content. Make sure you have set the help content. (see the "_set_help_popup_content" method in the EE_Admin_Page for instructions.)',
1787 1787
                     'event_espresso'
1788 1788
                 ),
1789 1789
             ];
1790
-            $help_content              = $this->_set_help_popup_content($help_array);
1790
+            $help_content = $this->_set_help_popup_content($help_array);
1791 1791
         }
1792 1792
         $height   = esc_attr($dimensions[0]) ?? 400;
1793 1793
         $width    = esc_attr($dimensions[1]) ?? 640;
@@ -1875,7 +1875,7 @@  discard block
 block discarded – undo
1875 1875
 
1876 1876
         add_filter(
1877 1877
             'admin_body_class',
1878
-            function ($classes) {
1878
+            function($classes) {
1879 1879
                 if (strpos($classes, 'espresso-admin') === false) {
1880 1880
                     $classes .= ' espresso-admin';
1881 1881
                 }
@@ -1966,12 +1966,12 @@  discard block
 block discarded – undo
1966 1966
     protected function _set_list_table()
1967 1967
     {
1968 1968
         // first is this a list_table view?
1969
-        if (! isset($this->_route_config['list_table'])) {
1969
+        if ( ! isset($this->_route_config['list_table'])) {
1970 1970
             return;
1971 1971
         } //not a list_table view so get out.
1972 1972
         // list table functions are per view specific (because some admin pages might have more than one list table!)
1973
-        $list_table_view = '_set_list_table_views_' . $this->_req_action;
1974
-        if (! method_exists($this, $list_table_view) || $this->{$list_table_view}() === false) {
1973
+        $list_table_view = '_set_list_table_views_'.$this->_req_action;
1974
+        if ( ! method_exists($this, $list_table_view) || $this->{$list_table_view}() === false) {
1975 1975
             // user error msg
1976 1976
             $error_msg = esc_html__(
1977 1977
                 'An error occurred. The requested list table views could not be found.',
@@ -1991,10 +1991,10 @@  discard block
 block discarded – undo
1991 1991
         }
1992 1992
         // let's provide the ability to filter the views per PAGE AND ROUTE, per PAGE, and globally
1993 1993
         $this->_views = apply_filters(
1994
-            'FHEE_list_table_views_' . $this->page_slug . '_' . $this->_req_action,
1994
+            'FHEE_list_table_views_'.$this->page_slug.'_'.$this->_req_action,
1995 1995
             $this->_views
1996 1996
         );
1997
-        $this->_views = apply_filters('FHEE_list_table_views_' . $this->page_slug, $this->_views);
1997
+        $this->_views = apply_filters('FHEE_list_table_views_'.$this->page_slug, $this->_views);
1998 1998
         $this->_views = apply_filters('FHEE_list_table_views', $this->_views);
1999 1999
         $this->_set_list_table_view();
2000 2000
         $this->_set_list_table_object();
@@ -2029,7 +2029,7 @@  discard block
 block discarded – undo
2029 2029
     protected function _set_list_table_object()
2030 2030
     {
2031 2031
         if (isset($this->_route_config['list_table'])) {
2032
-            if (! class_exists($this->_route_config['list_table'])) {
2032
+            if ( ! class_exists($this->_route_config['list_table'])) {
2033 2033
                 throw new EE_Error(
2034 2034
                     sprintf(
2035 2035
                         esc_html__(
@@ -2071,16 +2071,16 @@  discard block
 block discarded – undo
2071 2071
         foreach ($this->_views as $key => $view) {
2072 2072
             $query_args = [];
2073 2073
             // check for current view
2074
-            $this->_views[ $key ]['class'] = $this->_view === $view['slug'] ? 'current' : '';
2074
+            $this->_views[$key]['class'] = $this->_view === $view['slug'] ? 'current' : '';
2075 2075
             $query_args['action']          = $this->_req_action;
2076 2076
             $action_nonce                  = "{$this->_req_action}_nonce";
2077
-            $query_args[ $action_nonce ]   = wp_create_nonce($action_nonce);
2077
+            $query_args[$action_nonce]   = wp_create_nonce($action_nonce);
2078 2078
             $query_args['status']          = $view['slug'];
2079 2079
             // merge any other arguments sent in.
2080
-            if (isset($extra_query_args[ $view['slug'] ])) {
2081
-                $query_args = array_merge($query_args, $extra_query_args[ $view['slug'] ]);
2080
+            if (isset($extra_query_args[$view['slug']])) {
2081
+                $query_args = array_merge($query_args, $extra_query_args[$view['slug']]);
2082 2082
             }
2083
-            $this->_views[ $key ]['url'] = EE_Admin_Page::add_query_args_and_nonce($query_args, $this->_admin_base_url);
2083
+            $this->_views[$key]['url'] = EE_Admin_Page::add_query_args_and_nonce($query_args, $this->_admin_base_url);
2084 2084
         }
2085 2085
         return $this->_views;
2086 2086
     }
@@ -2109,14 +2109,14 @@  discard block
 block discarded – undo
2109 2109
 					<select id="entries-per-page-slct" name="entries-per-page-slct">';
2110 2110
         foreach ($values as $value) {
2111 2111
             if ($value < $max_entries) {
2112
-                $selected                  = $value === $per_page ? ' selected="' . $per_page . '"' : '';
2112
+                $selected = $value === $per_page ? ' selected="'.$per_page.'"' : '';
2113 2113
                 $entries_per_page_dropdown .= '
2114
-						<option value="' . $value . '"' . $selected . '>' . $value . '&nbsp;&nbsp;</option>';
2114
+						<option value="' . $value.'"'.$selected.'>'.$value.'&nbsp;&nbsp;</option>';
2115 2115
             }
2116 2116
         }
2117
-        $selected                  = $max_entries === $per_page ? ' selected="' . $per_page . '"' : '';
2117
+        $selected = $max_entries === $per_page ? ' selected="'.$per_page.'"' : '';
2118 2118
         $entries_per_page_dropdown .= '
2119
-						<option value="' . $max_entries . '"' . $selected . '>All&nbsp;&nbsp;</option>';
2119
+						<option value="' . $max_entries.'"'.$selected.'>All&nbsp;&nbsp;</option>';
2120 2120
         $entries_per_page_dropdown .= '
2121 2121
 					</select>
2122 2122
 					entries
@@ -2140,7 +2140,7 @@  discard block
 block discarded – undo
2140 2140
             empty($this->_search_btn_label) ? $this->page_label
2141 2141
                 : $this->_search_btn_label
2142 2142
         );
2143
-        $this->_template_args['search']['callback']  = 'search_' . $this->page_slug;
2143
+        $this->_template_args['search']['callback'] = 'search_'.$this->page_slug;
2144 2144
     }
2145 2145
 
2146 2146
 
@@ -2201,7 +2201,7 @@  discard block
 block discarded – undo
2201 2201
                                   );
2202 2202
                     throw new EE_Error($error_msg);
2203 2203
                 }
2204
-                unset($this->_route_config['metaboxes'][ $key ]);
2204
+                unset($this->_route_config['metaboxes'][$key]);
2205 2205
             }
2206 2206
         }
2207 2207
     }
@@ -2234,7 +2234,7 @@  discard block
 block discarded – undo
2234 2234
             $total_columns                                       = ! empty($screen_columns)
2235 2235
                 ? $screen_columns
2236 2236
                 : $this->_route_config['columns'][1];
2237
-            $this->_template_args['current_screen_widget_class'] = 'columns-' . $total_columns;
2237
+            $this->_template_args['current_screen_widget_class'] = 'columns-'.$total_columns;
2238 2238
             $this->_template_args['current_page']                = $this->_wp_page_slug;
2239 2239
             $this->_template_args['screen']                      = $this->_current_screen;
2240 2240
             $this->_column_template_path                         = EE_ADMIN_TEMPLATE
@@ -2280,7 +2280,7 @@  discard block
 block discarded – undo
2280 2280
      */
2281 2281
     protected function _espresso_ratings_request()
2282 2282
     {
2283
-        if (! apply_filters('FHEE_show_ratings_request_meta_box', true)) {
2283
+        if ( ! apply_filters('FHEE_show_ratings_request_meta_box', true)) {
2284 2284
             return;
2285 2285
         }
2286 2286
         $ratings_box_title = apply_filters(
@@ -2307,28 +2307,28 @@  discard block
 block discarded – undo
2307 2307
      */
2308 2308
     public function espresso_ratings_request()
2309 2309
     {
2310
-        EEH_Template::display_template(EE_ADMIN_TEMPLATE . 'espresso_ratings_request_content.template.php');
2310
+        EEH_Template::display_template(EE_ADMIN_TEMPLATE.'espresso_ratings_request_content.template.php');
2311 2311
     }
2312 2312
 
2313 2313
 
2314 2314
     public static function cached_rss_display(string $rss_id, string $url): bool
2315 2315
     {
2316
-        $loading   = '<p class="widget-loading hide-if-no-js">'
2316
+        $loading = '<p class="widget-loading hide-if-no-js">'
2317 2317
                      . esc_html__('Loading&#8230;', 'event_espresso')
2318 2318
                      . '</p><p class="hide-if-js">'
2319 2319
                      . esc_html__('This widget requires JavaScript.', 'event_espresso')
2320 2320
                      . '</p>';
2321
-        $pre       = '<div class="espresso-rss-display">' . "\n\t";
2322
-        $pre       .= '<span id="' . esc_attr($rss_id) . '_url" class="hidden">' . esc_url_raw($url) . '</span>';
2323
-        $post      = '</div>' . "\n";
2324
-        $cache_key = 'ee_rss_' . md5($rss_id);
2321
+        $pre       = '<div class="espresso-rss-display">'."\n\t";
2322
+        $pre .= '<span id="'.esc_attr($rss_id).'_url" class="hidden">'.esc_url_raw($url).'</span>';
2323
+        $post      = '</div>'."\n";
2324
+        $cache_key = 'ee_rss_'.md5($rss_id);
2325 2325
         $output    = get_transient($cache_key);
2326 2326
         if ($output !== false) {
2327
-            echo wp_kses($pre . $output . $post, AllowedTags::getWithFormTags());
2327
+            echo wp_kses($pre.$output.$post, AllowedTags::getWithFormTags());
2328 2328
             return true;
2329 2329
         }
2330
-        if (! (defined('DOING_AJAX') && DOING_AJAX)) {
2331
-            echo wp_kses($pre . $loading . $post, AllowedTags::getWithFormTags());
2330
+        if ( ! (defined('DOING_AJAX') && DOING_AJAX)) {
2331
+            echo wp_kses($pre.$loading.$post, AllowedTags::getWithFormTags());
2332 2332
             return false;
2333 2333
         }
2334 2334
         ob_start();
@@ -2395,7 +2395,7 @@  discard block
 block discarded – undo
2395 2395
     public function espresso_sponsors_post_box()
2396 2396
     {
2397 2397
         EEH_Template::display_template(
2398
-            EE_ADMIN_TEMPLATE . 'admin_general_metabox_contents_espresso_sponsors.template.php'
2398
+            EE_ADMIN_TEMPLATE.'admin_general_metabox_contents_espresso_sponsors.template.php'
2399 2399
         );
2400 2400
     }
2401 2401
 
@@ -2412,9 +2412,9 @@  discard block
 block discarded – undo
2412 2412
     protected function getPublishBoxTitle(): string
2413 2413
     {
2414 2414
         $publish_box_title = esc_html__('Publish', 'event_espresso');
2415
-        if (! empty($this->_labels['publishbox'])) {
2415
+        if ( ! empty($this->_labels['publishbox'])) {
2416 2416
             if (is_array($this->_labels['publishbox'])) {
2417
-                $publish_box_title = $this->_labels['publishbox'][ $this->_req_action ] ?? $publish_box_title;
2417
+                $publish_box_title = $this->_labels['publishbox'][$this->_req_action] ?? $publish_box_title;
2418 2418
             } else {
2419 2419
                 $publish_box_title = $this->_labels['publishbox'];
2420 2420
             }
@@ -2464,7 +2464,7 @@  discard block
 block discarded – undo
2464 2464
         // if we have extra content set let's add it in if not make sure its empty
2465 2465
         $this->_template_args['publish_box_extra_content'] = $this->_template_args['publish_box_extra_content'] ?? '';
2466 2466
         echo EEH_Template::display_template(
2467
-            EE_ADMIN_TEMPLATE . 'admin_details_publish_metabox.template.php',
2467
+            EE_ADMIN_TEMPLATE.'admin_details_publish_metabox.template.php',
2468 2468
             $this->_template_args,
2469 2469
             true
2470 2470
         );
@@ -2517,10 +2517,10 @@  discard block
 block discarded – undo
2517 2517
                 'submitdelete deletion button button--outline button--caution'
2518 2518
             );
2519 2519
         }
2520
-        if (! isset($this->_template_args['publish_delete_link'])) {
2520
+        if ( ! isset($this->_template_args['publish_delete_link'])) {
2521 2521
             $this->_template_args['publish_delete_link'] = '';
2522 2522
         }
2523
-        if (! empty($name) && ! empty($id)) {
2523
+        if ( ! empty($name) && ! empty($id)) {
2524 2524
             $this->addPublishPostMetaBoxHiddenFields($name, ['type' => 'hidden', 'value' => $id]);
2525 2525
         }
2526 2526
         $hidden_fields = $this->_generate_admin_form_fields($this->publish_post_meta_box_hidden_fields, 'array');
@@ -2553,7 +2553,7 @@  discard block
 block discarded – undo
2553 2553
 
2554 2554
     protected function addPublishPostMetaBoxHiddenFields(string $field_name, array $field_attributes)
2555 2555
     {
2556
-        $this->publish_post_meta_box_hidden_fields[ $field_name ] = $field_attributes;
2556
+        $this->publish_post_meta_box_hidden_fields[$field_name] = $field_attributes;
2557 2557
     }
2558 2558
 
2559 2559
 
@@ -2655,7 +2655,7 @@  discard block
 block discarded – undo
2655 2655
         }
2656 2656
         // if $create_func is true (default) then we automatically create the function for displaying the actual meta box.  If false then we take the $callback reference passed through and use it instead (so callers can define their own callback function/method if they wish)
2657 2657
         $call_back_func = $create_func
2658
-            ? static function ($post, $metabox) {
2658
+            ? static function($post, $metabox) {
2659 2659
                 echo EEH_Template::display_template(
2660 2660
                     $metabox['args']['template_path'],
2661 2661
                     $metabox['args']['template_args'],
@@ -2664,7 +2664,7 @@  discard block
 block discarded – undo
2664 2664
             }
2665 2665
             : $callback;
2666 2666
         $this->addMetaBox(
2667
-            str_replace('_', '-', $action) . '-mbox',
2667
+            str_replace('_', '-', $action).'-mbox',
2668 2668
             $title,
2669 2669
             $call_back_func,
2670 2670
             $this->_wp_page_slug,
@@ -2780,15 +2780,15 @@  discard block
 block discarded – undo
2780 2780
                                                                     'event-espresso_page_espresso_',
2781 2781
                                                                     '',
2782 2782
                                                                     $this->_wp_page_slug
2783
-                                                                ) . ' ' . $this->_req_action . '-route';
2783
+                                                                ).' '.$this->_req_action.'-route';
2784 2784
 
2785 2785
         $template_path = $sidebar
2786 2786
             ? EE_ADMIN_TEMPLATE . 'admin_details_wrapper.template.php'
2787
-            : EE_ADMIN_TEMPLATE . 'admin_details_wrapper_no_sidebar.template.php';
2787
+            : EE_ADMIN_TEMPLATE.'admin_details_wrapper_no_sidebar.template.php';
2788 2788
 
2789 2789
         $this->_template_args['is_ajax'] = $this->request->isAjax();
2790 2790
         if ($this->request->isAjax()) {
2791
-            $template_path = EE_ADMIN_TEMPLATE . 'admin_details_wrapper_no_sidebar_ajax.template.php';
2791
+            $template_path = EE_ADMIN_TEMPLATE.'admin_details_wrapper_no_sidebar_ajax.template.php';
2792 2792
         }
2793 2793
         $template_path = ! empty($this->_column_template_path) ? $this->_column_template_path : $template_path;
2794 2794
 
@@ -2800,10 +2800,10 @@  discard block
 block discarded – undo
2800 2800
         // to prevent WooCommerce from blowing things up if not using CPT
2801 2801
         global $post_type, $post;
2802 2802
         $this->_template_args['post_type'] = $post_type ?? '';
2803
-        $this->_template_args['post']  = $post ?? new WP_Post( (object) [ 'ID' => 0, 'filter' => 'raw' ] );
2803
+        $this->_template_args['post'] = $post ?? new WP_Post((object) ['ID' => 0, 'filter' => 'raw']);
2804 2804
 
2805 2805
         $this->_template_args['post_body_content'] = EEH_Template::display_template(
2806
-            EE_ADMIN_TEMPLATE . 'admin_details_wrapper_post_body_content.template.php',
2806
+            EE_ADMIN_TEMPLATE.'admin_details_wrapper_post_body_content.template.php',
2807 2807
             $this->_template_args,
2808 2808
             true
2809 2809
         );
@@ -2835,11 +2835,11 @@  discard block
 block discarded – undo
2835 2835
     public function display_admin_caf_preview_page(string $utm_campaign_source = '', bool $display_sidebar = true)
2836 2836
     {
2837 2837
         // let's generate a default preview action button if there isn't one already present.
2838
-        $this->_labels['buttons']['buy_now']           = esc_html__(
2838
+        $this->_labels['buttons']['buy_now'] = esc_html__(
2839 2839
             'Upgrade to Event Espresso 4 Right Now',
2840 2840
             'event_espresso'
2841 2841
         );
2842
-        $buy_now_url                                   = add_query_arg(
2842
+        $buy_now_url = add_query_arg(
2843 2843
             [
2844 2844
                 'ee_ver'       => 'ee4',
2845 2845
                 'utm_source'   => 'ee4_plugin_admin',
@@ -2859,8 +2859,8 @@  discard block
 block discarded – undo
2859 2859
                 true
2860 2860
             )
2861 2861
             : $this->_template_args['preview_action_button'];
2862
-        $this->_template_args['admin_page_content']    = EEH_Template::display_template(
2863
-            EE_ADMIN_TEMPLATE . 'admin_caf_full_page_preview.template.php',
2862
+        $this->_template_args['admin_page_content'] = EEH_Template::display_template(
2863
+            EE_ADMIN_TEMPLATE.'admin_caf_full_page_preview.template.php',
2864 2864
             $this->_template_args,
2865 2865
             true
2866 2866
         );
@@ -2918,7 +2918,7 @@  discard block
 block discarded – undo
2918 2918
         // setup search attributes
2919 2919
         $this->_set_search_attributes();
2920 2920
         $this->_template_args['current_page']     = $this->_wp_page_slug;
2921
-        $template_path                            = EE_ADMIN_TEMPLATE . 'admin_list_wrapper.template.php';
2921
+        $template_path                            = EE_ADMIN_TEMPLATE.'admin_list_wrapper.template.php';
2922 2922
         $this->_template_args['table_url']        = $this->request->isAjax()
2923 2923
             ? add_query_arg(['noheader' => 'true', 'route' => $this->_req_action], $this->_admin_base_url)
2924 2924
             : add_query_arg(['route' => $this->_req_action], $this->_admin_base_url);
@@ -2926,10 +2926,10 @@  discard block
 block discarded – undo
2926 2926
         $this->_template_args['current_route']    = $this->_req_action;
2927 2927
         $this->_template_args['list_table_class'] = get_class($this->_list_table_object);
2928 2928
         $ajax_sorting_callback                    = $this->_list_table_object->get_ajax_sorting_callback();
2929
-        if (! empty($ajax_sorting_callback)) {
2929
+        if ( ! empty($ajax_sorting_callback)) {
2930 2930
             $sortable_list_table_form_fields = wp_nonce_field(
2931
-                $ajax_sorting_callback . '_nonce',
2932
-                $ajax_sorting_callback . '_nonce',
2931
+                $ajax_sorting_callback.'_nonce',
2932
+                $ajax_sorting_callback.'_nonce',
2933 2933
                 false,
2934 2934
                 false
2935 2935
             );
@@ -2946,18 +2946,18 @@  discard block
 block discarded – undo
2946 2946
 
2947 2947
         $hidden_form_fields = $this->_template_args['list_table_hidden_fields'] ?? '';
2948 2948
 
2949
-        $nonce_ref          = $this->_req_action . '_nonce';
2949
+        $nonce_ref          = $this->_req_action.'_nonce';
2950 2950
         $hidden_form_fields .= '
2951
-            <input type="hidden" name="' . $nonce_ref . '" value="' . wp_create_nonce($nonce_ref) . '">';
2951
+            <input type="hidden" name="' . $nonce_ref.'" value="'.wp_create_nonce($nonce_ref).'">';
2952 2952
 
2953 2953
         $this->_template_args['list_table_hidden_fields'] = $hidden_form_fields;
2954 2954
         // display message about search results?
2955
-        $search                                    = $this->request->getRequestParam('s');
2955
+        $search = $this->request->getRequestParam('s');
2956 2956
         $this->_template_args['before_list_table'] .= ! empty($search)
2957
-            ? '<p class="ee-search-results">' . sprintf(
2957
+            ? '<p class="ee-search-results">'.sprintf(
2958 2958
                 esc_html__('Displaying search results for the search string: %1$s', 'event_espresso'),
2959 2959
                 trim($search, '%')
2960
-            ) . '</p>'
2960
+            ).'</p>'
2961 2961
             : '';
2962 2962
         // filter before_list_table template arg
2963 2963
         $this->_template_args['before_list_table'] = apply_filters(
@@ -2991,7 +2991,7 @@  discard block
 block discarded – undo
2991 2991
         // convert to array and filter again
2992 2992
         // arrays are easier to inject new items in a specific location,
2993 2993
         // but would not be backwards compatible, so we have to add a new filter
2994
-        $this->_template_args['after_list_table']   = implode(
2994
+        $this->_template_args['after_list_table'] = implode(
2995 2995
             " \n",
2996 2996
             (array) apply_filters(
2997 2997
                 'FHEE__EE_Admin_Page___display_admin_list_table_page__after_list_table__template_args_array',
@@ -3046,7 +3046,7 @@  discard block
 block discarded – undo
3046 3046
             $this->page_slug
3047 3047
         );
3048 3048
         return EEH_Template::display_template(
3049
-            EE_ADMIN_TEMPLATE . 'admin_details_legend.template.php',
3049
+            EE_ADMIN_TEMPLATE.'admin_details_legend.template.php',
3050 3050
             $this->_template_args,
3051 3051
             true
3052 3052
         );
@@ -3169,7 +3169,7 @@  discard block
 block discarded – undo
3169 3169
         if ($this->request->isAjax()) {
3170 3170
             $this->_template_args['admin_page_content'] = EEH_Template::display_template(
3171 3171
             // $template_path,
3172
-                EE_ADMIN_TEMPLATE . 'admin_wrapper_ajax.template.php',
3172
+                EE_ADMIN_TEMPLATE.'admin_wrapper_ajax.template.php',
3173 3173
                 $this->_template_args,
3174 3174
                 true
3175 3175
             );
@@ -3178,7 +3178,7 @@  discard block
 block discarded – undo
3178 3178
         // load settings page wrapper template
3179 3179
         $template_path = $about
3180 3180
             ? EE_ADMIN_TEMPLATE . 'about_admin_wrapper.template.php'
3181
-            : EE_ADMIN_TEMPLATE . 'admin_wrapper.template.php';
3181
+            : EE_ADMIN_TEMPLATE.'admin_wrapper.template.php';
3182 3182
 
3183 3183
         EEH_Template::display_template($template_path, $this->_template_args);
3184 3184
     }
@@ -3262,12 +3262,12 @@  discard block
 block discarded – undo
3262 3262
         $default_names = ['save', 'save_and_close'];
3263 3263
         $buttons       = '';
3264 3264
         foreach ($button_text as $key => $button) {
3265
-            $ref     = $default_names[ $key ];
3266
-            $name    = ! empty($actions) ? $actions[ $key ] : $ref;
3267
-            $buttons .= '<input type="submit" class="button button--primary ' . $ref . '" '
3268
-                        . 'value="' . $button . '" name="' . $name . '" '
3269
-                        . 'id="' . $this->_current_view . '_' . $ref . '" />';
3270
-            if (! $both) {
3265
+            $ref     = $default_names[$key];
3266
+            $name    = ! empty($actions) ? $actions[$key] : $ref;
3267
+            $buttons .= '<input type="submit" class="button button--primary '.$ref.'" '
3268
+                        . 'value="'.$button.'" name="'.$name.'" '
3269
+                        . 'id="'.$this->_current_view.'_'.$ref.'" />';
3270
+            if ( ! $both) {
3271 3271
                 break;
3272 3272
             }
3273 3273
         }
@@ -3308,13 +3308,13 @@  discard block
 block discarded – undo
3308 3308
                 'An error occurred. No action was set for this page\'s form.',
3309 3309
                 'event_espresso'
3310 3310
             );
3311
-            $dev_msg  = $user_msg . "\n"
3311
+            $dev_msg = $user_msg."\n"
3312 3312
                         . sprintf(
3313 3313
                             esc_html__('The $route argument is required for the %s->%s method.', 'event_espresso'),
3314 3314
                             __FUNCTION__,
3315 3315
                             __CLASS__
3316 3316
                         );
3317
-            EE_Error::add_error($user_msg . '||' . $dev_msg, __FILE__, __FUNCTION__, __LINE__);
3317
+            EE_Error::add_error($user_msg.'||'.$dev_msg, __FILE__, __FUNCTION__, __LINE__);
3318 3318
         }
3319 3319
         // open form
3320 3320
         $action                                            = $this->_admin_base_url;
@@ -3322,9 +3322,9 @@  discard block
 block discarded – undo
3322 3322
             <form name='form' method='post' action='$action' id='{$route}_event_form' class='ee-admin-page-form' >
3323 3323
             ";
3324 3324
         // add nonce
3325
-        $nonce                                             =
3326
-            wp_nonce_field($route . '_nonce', $route . '_nonce', false, false);
3327
-        $this->_template_args['before_admin_page_content'] .= "\n\t" . $nonce;
3325
+        $nonce =
3326
+            wp_nonce_field($route.'_nonce', $route.'_nonce', false, false);
3327
+        $this->_template_args['before_admin_page_content'] .= "\n\t".$nonce;
3328 3328
         // add REQUIRED form action
3329 3329
         $hidden_fields = [
3330 3330
             'action' => ['type' => 'hidden', 'value' => $route],
@@ -3337,7 +3337,7 @@  discard block
 block discarded – undo
3337 3337
         $form_fields = $this->_generate_admin_form_fields($hidden_fields, 'array');
3338 3338
         // add fields to form
3339 3339
         foreach ((array) $form_fields as $form_field) {
3340
-            $this->_template_args['before_admin_page_content'] .= "\n\t" . $form_field['field'];
3340
+            $this->_template_args['before_admin_page_content'] .= "\n\t".$form_field['field'];
3341 3341
         }
3342 3342
         // close form
3343 3343
         $this->_template_args['after_admin_page_content'] = '</form>';
@@ -3420,10 +3420,10 @@  discard block
 block discarded – undo
3420 3420
     ) {
3421 3421
         $notices = EE_Error::get_notices(false);
3422 3422
         // overwrite default success messages //BUT ONLY if overwrite not overridden
3423
-        if (! $override_overwrite || ! empty($notices['errors'])) {
3423
+        if ( ! $override_overwrite || ! empty($notices['errors'])) {
3424 3424
             EE_Error::overwrite_success();
3425 3425
         }
3426
-        if (! $override_overwrite && ! empty($what) && ! empty($action_desc) && empty($notices['errors'])) {
3426
+        if ( ! $override_overwrite && ! empty($what) && ! empty($action_desc) && empty($notices['errors'])) {
3427 3427
             // how many records affected ? more than one record ? or just one ?
3428 3428
             EE_Error::add_success(
3429 3429
                 sprintf(
@@ -3474,7 +3474,7 @@  discard block
 block discarded – undo
3474 3474
             $redirect_url = admin_url('admin.php');
3475 3475
         }
3476 3476
         // merge any default query_args set in _default_route_query_args property
3477
-        if (! empty($this->_default_route_query_args) && ! $this->_is_UI_request) {
3477
+        if ( ! empty($this->_default_route_query_args) && ! $this->_is_UI_request) {
3478 3478
             $args_to_merge = [];
3479 3479
             foreach ($this->_default_route_query_args as $query_param => $query_value) {
3480 3480
                 // is there a wp_referer array in our _default_route_query_args property?
@@ -3486,15 +3486,15 @@  discard block
 block discarded – undo
3486 3486
                         }
3487 3487
                         // finally we will override any arguments in the referer with
3488 3488
                         // what might be set on the _default_route_query_args array.
3489
-                        if (isset($this->_default_route_query_args[ $reference ])) {
3490
-                            $args_to_merge[ $reference ] = urlencode($this->_default_route_query_args[ $reference ]);
3489
+                        if (isset($this->_default_route_query_args[$reference])) {
3490
+                            $args_to_merge[$reference] = urlencode($this->_default_route_query_args[$reference]);
3491 3491
                         } else {
3492
-                            $args_to_merge[ $reference ] = urlencode($value);
3492
+                            $args_to_merge[$reference] = urlencode($value);
3493 3493
                         }
3494 3494
                     }
3495 3495
                     continue;
3496 3496
                 }
3497
-                $args_to_merge[ $query_param ] = $query_value;
3497
+                $args_to_merge[$query_param] = $query_value;
3498 3498
             }
3499 3499
             // now let's merge these arguments but override with what was specifically sent in to the
3500 3500
             // redirect.
@@ -3506,19 +3506,19 @@  discard block
 block discarded – undo
3506 3506
         if (isset($query_args['action'])) {
3507 3507
             // manually generate wp_nonce and merge that with the query vars
3508 3508
             // becuz the wp_nonce_url function wrecks havoc on some vars
3509
-            $query_args['_wpnonce'] = wp_create_nonce($query_args['action'] . '_nonce');
3509
+            $query_args['_wpnonce'] = wp_create_nonce($query_args['action'].'_nonce');
3510 3510
         }
3511 3511
         // we're adding some hooks and filters in here for processing any things just before redirects
3512 3512
         // (example: an admin page has done an insert or update and we want to run something after that).
3513
-        do_action('AHEE_redirect_' . $this->class_name . $this->_req_action, $query_args);
3513
+        do_action('AHEE_redirect_'.$this->class_name.$this->_req_action, $query_args);
3514 3514
         $redirect_url = apply_filters(
3515
-            'FHEE_redirect_' . $this->class_name . $this->_req_action,
3515
+            'FHEE_redirect_'.$this->class_name.$this->_req_action,
3516 3516
             EE_Admin_Page::add_query_args_and_nonce($query_args, $redirect_url),
3517 3517
             $query_args
3518 3518
         );
3519 3519
         // check if we're doing ajax.  If we are then lets just return the results and js can handle how it wants.
3520 3520
         if ($this->request->isAjax()) {
3521
-            $default_data                    = [
3521
+            $default_data = [
3522 3522
                 'close'        => true,
3523 3523
                 'redirect_url' => $redirect_url,
3524 3524
                 'where'        => 'main',
@@ -3571,7 +3571,7 @@  discard block
 block discarded – undo
3571 3571
         }
3572 3572
         $this->_template_args['notices'] = EE_Error::get_notices();
3573 3573
         // IF this isn't ajax we need to create a transient for the notices using the route (however, overridden if $sticky_notices == true)
3574
-        if (! $this->request->isAjax() || $sticky_notices) {
3574
+        if ( ! $this->request->isAjax() || $sticky_notices) {
3575 3575
             $route = $query_args['action'] ?? 'default';
3576 3576
             $this->_add_transient(
3577 3577
                 $route,
@@ -3611,7 +3611,7 @@  discard block
 block discarded – undo
3611 3611
         bool $exclude_nonce = false
3612 3612
     ): string {
3613 3613
         // first let's validate the action (if $base_url is FALSE otherwise validation will happen further along)
3614
-        if (empty($base_url) && ! isset($this->_page_routes[ $action ])) {
3614
+        if (empty($base_url) && ! isset($this->_page_routes[$action])) {
3615 3615
             throw new EE_Error(
3616 3616
                 sprintf(
3617 3617
                     esc_html__(
@@ -3622,7 +3622,7 @@  discard block
 block discarded – undo
3622 3622
                 )
3623 3623
             );
3624 3624
         }
3625
-        if (! isset($this->_labels['buttons'][ $type ])) {
3625
+        if ( ! isset($this->_labels['buttons'][$type])) {
3626 3626
             throw new EE_Error(
3627 3627
                 sprintf(
3628 3628
                     esc_html__(
@@ -3635,7 +3635,7 @@  discard block
 block discarded – undo
3635 3635
         }
3636 3636
         // finally check user access for this button.
3637 3637
         $has_access = $this->check_user_access($action, true);
3638
-        if (! $has_access) {
3638
+        if ( ! $has_access) {
3639 3639
             return '';
3640 3640
         }
3641 3641
         $_base_url  = ! $base_url ? $this->_admin_base_url : $base_url;
@@ -3643,11 +3643,11 @@  discard block
 block discarded – undo
3643 3643
             'action' => $action,
3644 3644
         ];
3645 3645
         // merge extra_request args but make sure our original action takes precedence and doesn't get overwritten.
3646
-        if (! empty($extra_request)) {
3646
+        if ( ! empty($extra_request)) {
3647 3647
             $query_args = array_merge($extra_request, $query_args);
3648 3648
         }
3649 3649
         $url = EE_Admin_Page::add_query_args_and_nonce($query_args, $_base_url, false, $exclude_nonce);
3650
-        return EEH_Template::get_button_or_link($url, $this->_labels['buttons'][ $type ], $class);
3650
+        return EEH_Template::get_button_or_link($url, $this->_labels['buttons'][$type], $class);
3651 3651
     }
3652 3652
 
3653 3653
 
@@ -3673,7 +3673,7 @@  discard block
 block discarded – undo
3673 3673
                 'FHEE__EE_Admin_Page___per_page_screen_options__default',
3674 3674
                 20
3675 3675
             ),
3676
-            'option'  => $this->_current_page . '_' . $this->_current_view . '_per_page',
3676
+            'option'  => $this->_current_page.'_'.$this->_current_view.'_per_page',
3677 3677
         ];
3678 3678
         // ONLY add the screen option if the user has access to it.
3679 3679
         if ($this->check_user_access($this->_current_view, true)) {
@@ -3694,18 +3694,18 @@  discard block
 block discarded – undo
3694 3694
     {
3695 3695
         if ($this->request->requestParamIsSet('wp_screen_options')) {
3696 3696
             check_admin_referer('screen-options-nonce', 'screenoptionnonce');
3697
-            if (! $user = wp_get_current_user()) {
3697
+            if ( ! $user = wp_get_current_user()) {
3698 3698
                 return;
3699 3699
             }
3700 3700
             $option = $this->request->getRequestParam('wp_screen_options[option]', '', DataType::KEY);
3701
-            if (! $option) {
3701
+            if ( ! $option) {
3702 3702
                 return;
3703 3703
             }
3704 3704
             $value      = $this->request->getRequestParam('wp_screen_options[value]', 0, DataType::INT);
3705 3705
             $map_option = $option;
3706 3706
             $option     = str_replace('-', '_', $option);
3707 3707
             switch ($map_option) {
3708
-                case $this->_current_page . '_' . $this->_current_view . '_per_page':
3708
+                case $this->_current_page.'_'.$this->_current_view.'_per_page':
3709 3709
                     $max_value = apply_filters(
3710 3710
                         'FHEE__EE_Admin_Page___set_per_page_screen_options__max_value',
3711 3711
                         999,
@@ -3772,7 +3772,7 @@  discard block
 block discarded – undo
3772 3772
         bool $skip_route_verify = false
3773 3773
     ) {
3774 3774
         $user_id = get_current_user_id();
3775
-        if (! $skip_route_verify) {
3775
+        if ( ! $skip_route_verify) {
3776 3776
             $this->_verify_route($route);
3777 3777
         }
3778 3778
         // now let's set the string for what kind of transient we're setting
@@ -3805,8 +3805,8 @@  discard block
 block discarded – undo
3805 3805
         $user_id   = get_current_user_id();
3806 3806
         $route     = ! $route ? $this->_req_action : $route;
3807 3807
         $transient = $notices
3808
-            ? 'ee_rte_n_tx_' . $route . '_' . $user_id
3809
-            : 'rte_tx_' . $route . '_' . $user_id;
3808
+            ? 'ee_rte_n_tx_'.$route.'_'.$user_id
3809
+            : 'rte_tx_'.$route.'_'.$user_id;
3810 3810
         $data      = is_multisite() && is_network_admin()
3811 3811
             ? get_site_transient($transient)
3812 3812
             : get_transient($transient);
@@ -4064,7 +4064,7 @@  discard block
 block discarded – undo
4064 4064
      */
4065 4065
     protected function _next_link(string $url, string $class = 'dashicons dashicons-arrow-right'): string
4066 4066
     {
4067
-        return '<a class="' . $class . '" href="' . $url . '"></a>';
4067
+        return '<a class="'.$class.'" href="'.$url.'"></a>';
4068 4068
     }
4069 4069
 
4070 4070
 
@@ -4077,7 +4077,7 @@  discard block
 block discarded – undo
4077 4077
      */
4078 4078
     protected function _previous_link(string $url, string $class = 'dashicons dashicons-arrow-left'): string
4079 4079
     {
4080
-        return '<a class="' . $class . '" href="' . $url . '"></a>';
4080
+        return '<a class="'.$class.'" href="'.$url.'"></a>';
4081 4081
     }
4082 4082
 
4083 4083
 
@@ -4225,13 +4225,13 @@  discard block
 block discarded – undo
4225 4225
         ?callable $callback = null
4226 4226
     ): bool {
4227 4227
         $entity_ID = absint($entity_ID);
4228
-        if (! $entity_ID) {
4228
+        if ( ! $entity_ID) {
4229 4229
             $this->trashRestoreDeleteError($action, $entity_model);
4230 4230
         }
4231 4231
         $result = 0;
4232 4232
         try {
4233 4233
             $entity = $entity_model->get_one_by_ID($entity_ID);
4234
-            if (! $entity instanceof EE_Base_Class) {
4234
+            if ( ! $entity instanceof EE_Base_Class) {
4235 4235
                 throw new DomainException(
4236 4236
                     sprintf(
4237 4237
                         esc_html__(
@@ -4282,7 +4282,7 @@  discard block
 block discarded – undo
4282 4282
                 )
4283 4283
             );
4284 4284
         }
4285
-        if (! $entity_model->has_field($delete_column)) {
4285
+        if ( ! $entity_model->has_field($delete_column)) {
4286 4286
             throw new DomainException(
4287 4287
                 sprintf(
4288 4288
                     esc_html__(
Please login to merge, or discard this patch.
core/libraries/batch/JobHandlers/RegistrationsReport.php 2 patches
Indentation   +640 added lines, -640 removed lines patch added patch discarded remove patch
@@ -46,644 +46,644 @@
 block discarded – undo
46 46
  */
47 47
 class RegistrationsReport extends JobHandlerFile
48 48
 {
49
-    // phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps
50
-    // phpcs:disable PSR2.Methods.MethodDeclaration.Underscore
51
-    /**
52
-     * Performs any necessary setup for starting the job. This is also a good
53
-     * place to set up the $job_arguments which will be used for subsequent HTTP requests
54
-     * when continue_job will be called
55
-     *
56
-     * @param JobParameters $job_parameters
57
-     * @return JobStepResponse
58
-     * @throws BatchRequestException
59
-     * @throws EE_Error
60
-     * @throws ReflectionException
61
-     * @throws Exception
62
-     */
63
-    public function create_job(JobParameters $job_parameters): JobStepResponse
64
-    {
65
-        $event_id = absint($job_parameters->request_datum('EVT_ID', '0'));
66
-        $DTT_ID   = absint($job_parameters->request_datum('DTT_ID', '0'));
67
-        if (! EE_Capabilities::instance()->current_user_can('ee_read_registrations', 'generating_report')) {
68
-            throw new BatchRequestException(
69
-                esc_html__('You do not have permission to view registrations', 'event_espresso')
70
-            );
71
-        }
72
-        $filepath = $this->create_file_from_job_with_name(
73
-            $job_parameters->job_id(),
74
-            $this->get_filename()
75
-        );
76
-        $job_parameters->add_extra_data('filepath', $filepath);
77
-
78
-        if ($job_parameters->request_datum('use_filters', false)) {
79
-            $query_params = maybe_unserialize($job_parameters->request_datum('filters', []));
80
-        } else {
81
-            $query_params = [
82
-                [ 'Ticket.TKT_deleted' => ['IN', [true, false]] ],
83
-                'order_by'   => ['Transaction.TXN_ID' => 'asc', 'REG_count' => 'asc'],
84
-                'force_join' => ['Transaction', 'Ticket', 'Attendee'],
85
-                'caps'       => EEM_Base::caps_read_admin,
86
-            ];
87
-            if ($event_id) {
88
-                $query_params[0]['EVT_ID'] = $event_id;
89
-            } else {
90
-                $query_params['force_join'][] = 'Event';
91
-            }
92
-        }
93
-        // unless the query params already include a status,
94
-        // we want to exclude registrations from failed or abandoned transactions
95
-        if (! isset($query_params[0]['Transaction.STS_ID'])) {
96
-            $query_params[0]['OR'] = [
97
-                // don't include registrations from failed or abandoned transactions...
98
-                'Transaction.STS_ID' => [
99
-                    'NOT IN',
100
-                    [
101
-                        EEM_Transaction::failed_status_code,
102
-                        EEM_Transaction::abandoned_status_code,
103
-                    ],
104
-                ],
105
-                // unless the registration is approved,
106
-                // in which case include it regardless of transaction status
107
-                'STS_ID' => EEM_Registration::status_id_approved,
108
-            ];
109
-        }
110
-
111
-        if (! isset($query_params['force_join'])) {
112
-            $query_params['force_join'] = ['Event', 'Transaction', 'Ticket', 'Attendee'];
113
-        }
114
-
115
-        $return_url_args = [];
116
-        parse_str(
117
-            parse_url(
118
-                $job_parameters->request_datum('return_url'),
119
-                PHP_URL_QUERY
120
-            ),
121
-            $return_url_args
122
-        );
123
-
124
-        if (
125
-            isset($return_url_args['orderby'], $return_url_args['order'])
126
-            && $return_url_args['orderby'] === 'ATT_lname'
127
-        ) {
128
-            $query_params['order_by'] = [
129
-                'Attendee.ATT_lname' => $return_url_args['order'],
130
-                'Attendee.ATT_fname' => $return_url_args['order'],
131
-                'REG_ID' => $return_url_args['order']
132
-            ];
133
-        }
134
-
135
-        $query_params = apply_filters(
136
-            'FHEE__EE_Export__report_registration_for_event',
137
-            $query_params,
138
-            $event_id
139
-        );
140
-
141
-        $utc_timezone = new DateTimeZone('UTC');
142
-        $site_timezone = new DateTimeZone(EEH_DTT_Helper::get_timezone());
143
-        $query_params = $this->convertDateStringsToObjects($query_params, $site_timezone, $utc_timezone);
144
-
145
-        $job_parameters->add_extra_data('query_params', $query_params);
146
-        $question_labels = $this->_get_question_labels($query_params);
147
-        $job_parameters->add_extra_data('question_labels', $question_labels);
148
-        $job_parameters->set_job_size($this->count_units_to_process($query_params));
149
-        // we need to set the header columns
150
-        // but to do that we need to process one row so that we can extract ALL the column headers
151
-        $csv_data_for_row = $this->get_csv_data_for(
152
-            $event_id,
153
-            0,
154
-            1,
155
-            $question_labels,
156
-            $query_params,
157
-            $DTT_ID
158
-        );
159
-        // but we don't want to write any actual data yet...
160
-        // so let's blank out all the values for that first row
161
-        array_walk(
162
-            $csv_data_for_row[0],
163
-            function (&$value) {
164
-                $value = null;
165
-            }
166
-        );
167
-
168
-        EEH_Export::write_data_array_to_csv($filepath, $csv_data_for_row, true, true);
169
-        $this->updateTextHeader(
170
-            esc_html__('Registrations report started successfully...', 'event_espresso')
171
-        );
172
-        return new JobStepResponse($job_parameters, $this->feedback);
173
-    }
174
-
175
-
176
-    /**
177
-     * Gets the filename
178
-     *
179
-     * @return string
180
-     */
181
-    protected function get_filename(): string
182
-    {
183
-        return apply_filters(
184
-            'FHEE__EventEspressoBatchRequest__JobHandlers__RegistrationsReport__get_filename',
185
-            sprintf(
186
-                'event-espresso-registrations-%s.csv',
187
-                str_replace([':', ' '], '-', current_time('mysql'))
188
-            )
189
-        );
190
-    }
191
-
192
-
193
-    /**
194
-     * Gets the questions which are to be used for this report,
195
-     * so they can be remembered for later
196
-     *
197
-     * @param array $registration_query_params
198
-     * @return array question admin labels to be used for this report
199
-     * @throws EE_Error
200
-     * @throws ReflectionException
201
-     */
202
-    protected function _get_question_labels(array $registration_query_params): array
203
-    {
204
-        $where                 = $registration_query_params[0] ?? null;
205
-        $question_query_params = [];
206
-        if ($where !== null) {
207
-            $question_query_params = [
208
-                $this->_change_registration_where_params_to_question_where_params($registration_query_params[0]),
209
-            ];
210
-        }
211
-        // Make sure it's not a system question
212
-        $question_query_params[0]['OR*not-system-questions'] = [
213
-            'QST_system'      => '',
214
-            'QST_system*null' => ['IS_NULL']
215
-        ];
216
-        if (
217
-            apply_filters(
218
-                'FHEE__EventEspressoBatchRequest__JobHandlers__RegistrationsReport___get_question_labels__only_include_answered_questions',
219
-                false,
220
-                $registration_query_params
221
-            )
222
-        ) {
223
-            $question_query_params[0]['Answer.ANS_ID'] = ['IS_NOT_NULL'];
224
-        }
225
-        $question_query_params['order_by'] = [
226
-            'Question_Group_Question.QGQ_order' => 'ASC',
227
-            'QST_order' => 'ASC',
228
-            'QST_admin_label' => 'ASC'
229
-        ];
230
-        $question_query_params['group_by'] = ['QST_ID'];
231
-        return array_unique(EEM_Question::instance()->get_col($question_query_params, 'QST_admin_label'));
232
-    }
233
-
234
-
235
-    /**
236
-     * Takes where params meant for registrations and changes them to work for questions
237
-     *
238
-     * @param array $reg_where_params
239
-     * @return array
240
-     * @throws EE_Error
241
-     * @throws ReflectionException
242
-     */
243
-    protected function _change_registration_where_params_to_question_where_params(array $reg_where_params): array
244
-    {
245
-        $question_where_params = [];
246
-        foreach ($reg_where_params as $key => $val) {
247
-            if (EEM_Registration::instance()->is_logic_query_param_key($key)) {
248
-                $question_where_params[ $key ] =
249
-                    $this->_change_registration_where_params_to_question_where_params($val);
250
-            } else {
251
-                // it's a normal where condition
252
-                $question_where_params[ 'Question_Group.Event.Registration.' . $key ] = $val;
253
-            }
254
-        }
255
-        return $question_where_params;
256
-    }
257
-
258
-
259
-    /**
260
-     * Performs another step of the job
261
-     *
262
-     * @param JobParameters $job_parameters
263
-     * @param int           $batch_size
264
-     * @return JobStepResponse
265
-     * @throws EE_Error
266
-     * @throws ReflectionException
267
-     */
268
-    public function continue_job(JobParameters $job_parameters, int $batch_size = 50): JobStepResponse
269
-    {
270
-        if ($job_parameters->units_processed() < $job_parameters->job_size()) {
271
-            $csv_data = $this->get_csv_data_for(
272
-                (int) $job_parameters->request_datum('EVT_ID', '0'),
273
-                $job_parameters->units_processed(),
274
-                $batch_size,
275
-                $job_parameters->extra_datum('question_labels'),
276
-                $job_parameters->extra_datum('query_params'),
277
-                (int) $job_parameters->request_datum('DTT_ID', '0')
278
-            );
279
-            EEH_Export::write_data_array_to_csv(
280
-                $job_parameters->extra_datum('filepath'),
281
-                $csv_data,
282
-                false
283
-            );
284
-            $units_processed = count($csv_data);
285
-            if ($units_processed) {
286
-                $job_parameters->mark_processed($units_processed);
287
-                $this->updateText(
288
-                    sprintf(
289
-                        esc_html__('Wrote %1$s rows to report CSV file...', 'event_espresso'),
290
-                        $units_processed
291
-                    )
292
-                );
293
-            }
294
-        }
295
-        $extra_response_data = ['file_url' => ''];
296
-        if ($job_parameters->units_processed() >= $job_parameters->job_size()) {
297
-            $job_parameters->set_status(JobParameters::status_complete);
298
-            $extra_response_data['file_url'] = $this->get_url_to_file($job_parameters->extra_datum('filepath'));
299
-            $this->displayJobFinalResults($job_parameters);
300
-        } else {
301
-            $job_parameters->set_status(JobParameters::status_continue);
302
-        }
303
-        return new JobStepResponse($job_parameters, $this->feedback, $extra_response_data);
304
-    }
305
-
306
-
307
-    /**
308
-     * Gets the csv data for a batch of registrations
309
-     *
310
-     * @param int|null $event_id
311
-     * @param int      $offset
312
-     * @param int      $limit
313
-     * @param array    $question_labels the IDs for all the questions which were answered by someone in this selection
314
-     * @param array    $query_params    for using where querying the model
315
-     * @param int      $DTT_ID
316
-     * @return array top-level keys are numeric, next-level keys are column headers
317
-     * @throws EE_Error
318
-     * @throws ReflectionException
319
-     */
320
-    public function get_csv_data_for(
321
-        ?int $event_id,
322
-        int $offset,
323
-        int $limit,
324
-        array $question_labels,
325
-        array $query_params,
326
-        int $DTT_ID = 0
327
-    ): array {
328
-        $reg_fields_to_include = [
329
-            'TXN_ID',
330
-            'ATT_ID',
331
-            'REG_ID',
332
-            'REG_date',
333
-            'REG_code',
334
-            'REG_count',
335
-            'REG_final_price',
336
-        ];
337
-        $att_fields_to_include = [
338
-            'ATT_fname',
339
-            'ATT_lname',
340
-            'ATT_email',
341
-            'ATT_address',
342
-            'ATT_address2',
343
-            'ATT_city',
344
-            'STA_ID',
345
-            'CNT_ISO',
346
-            'ATT_zip',
347
-            'ATT_phone',
348
-        ];
349
-
350
-        // get models
351
-        $event_model   = EEM_Event::instance();
352
-        $date_model    = EEM_Datetime::instance();
353
-        $ticket_model  = EEM_Ticket::instance();
354
-        $txn_model     = EEM_Transaction::instance();
355
-        $reg_model     = EEM_Registration::instance();
356
-        $pay_model     = EEM_Payment::instance();
357
-        $status_model  = EEM_Status::instance();
358
-
359
-        $registrations_csv_ready_array = [];
360
-        $query_params['limit']         = [$offset, $limit];
361
-        $registration_rows             = $reg_model->get_all_wpdb_results($query_params);
362
-
363
-        foreach ($registration_rows as $reg_row) {
364
-            if (! is_array($reg_row)) {
365
-                continue;
366
-            }
367
-            $reg_csv_array = [];
368
-            // registration ID
369
-            $reg_id_field = $reg_model->field_settings_for('REG_ID');
370
-            $reg_csv_array[ EEH_Export::get_column_name_for_field($reg_id_field) ] =
371
-                EEH_Export::prepare_value_from_db_for_display(
372
-                    $reg_model,
373
-                    'REG_ID',
374
-                    $reg_row[ $reg_id_field->get_qualified_column() ]
375
-                );
376
-            // ALL registrations, or is list filtered to just one?
377
-            if (! $event_id) {
378
-                // ALL registrations, so get each event's name and ID
379
-                $reg_csv_array[ esc_html__('Event', 'event_espresso') ] = sprintf(
380
-                    /* translators: 1: event name, 2: event ID */
381
-                    esc_html__('%1$s (%2$s)', 'event_espresso'),
382
-                    EEH_Export::prepare_value_from_db_for_display(
383
-                        $event_model,
384
-                        'EVT_name',
385
-                        $reg_row['Event_CPT.post_title']
386
-                    ),
387
-                    $reg_row['Event_CPT.ID']
388
-                );
389
-            }
390
-            // add attendee columns
391
-            $reg_csv_array = AttendeeCSV::addAttendeeColumns($att_fields_to_include, $reg_row, $reg_csv_array);
392
-            // add registration columns
393
-            $reg_csv_array = RegistrationCSV::addRegistrationColumns($reg_fields_to_include, $reg_row, $reg_csv_array);
394
-            // get pretty status
395
-            $stati = $status_model->localized_status(
396
-                [
397
-                    $reg_row['Registration.STS_ID']     => esc_html__('unknown', 'event_espresso'),
398
-                    $reg_row['TransactionTable.STS_ID'] => esc_html__('unknown', 'event_espresso'),
399
-                ],
400
-                false,
401
-                'sentence'
402
-            );
403
-            $is_primary_reg = $reg_row['Registration.REG_count'] == '1';
404
-
405
-            $reg_csv_array[ esc_html__('Registration Status', 'event_espresso') ] =
406
-                $stati[ $reg_row['Registration.STS_ID'] ];
407
-            // get pretty transaction status
408
-            $reg_csv_array[ esc_html__('Transaction Status', 'event_espresso') ]     =
409
-                $stati[ $reg_row['TransactionTable.STS_ID'] ];
410
-            $reg_csv_array[ esc_html__('Transaction Amount Due', 'event_espresso') ] = $is_primary_reg
411
-                ? EEH_Export::prepare_value_from_db_for_display(
412
-                    $txn_model,
413
-                    'TXN_total',
414
-                    $reg_row['TransactionTable.TXN_total'],
415
-                    'localized_float'
416
-                )
417
-                : '0.00';
418
-
419
-            $reg_csv_array[ esc_html__('Amount Paid', 'event_espresso') ]            = $is_primary_reg
420
-                ? EEH_Export::prepare_value_from_db_for_display(
421
-                    $txn_model,
422
-                    'TXN_paid',
423
-                    $reg_row['TransactionTable.TXN_paid'],
424
-                    'localized_float'
425
-                )
426
-                : '0.00';
427
-
428
-            $payment_methods                                                                  = [];
429
-            $gateway_txn_ids_etc                                                              = [];
430
-            $payment_times                                                                    = [];
431
-            if ($is_primary_reg && $reg_row['TransactionTable.TXN_ID']) {
432
-                $payments_info = $pay_model->get_all_wpdb_results(
433
-                    [
434
-                        [
435
-                            'TXN_ID' => $reg_row['TransactionTable.TXN_ID'],
436
-                            'STS_ID' => EEM_Payment::status_id_approved,
437
-                        ],
438
-                        'force_join' => ['Payment_Method'],
439
-                    ],
440
-                    ARRAY_A,
441
-                    'Payment_Method.PMD_admin_name as name, Payment.PAY_txn_id_chq_nmbr as gateway_txn_id, Payment.PAY_timestamp as payment_time'
442
-                );
443
-                [$payment_methods, $gateway_txn_ids_etc, $payment_times] = PaymentsInfoCSV::extractPaymentInfo(
444
-                    $payments_info
445
-                );
446
-            }
447
-
448
-            $reg_csv_array[ esc_html__('Payment Date(s)', 'event_espresso') ] = implode(
449
-                ',',
450
-                $payment_times
451
-            );
452
-
453
-            $reg_csv_array[ esc_html__('Payment Method(s)', 'event_espresso') ] = implode(
454
-                ',',
455
-                $payment_methods
456
-            );
457
-
458
-            $reg_csv_array[ esc_html__('Gateway Transaction ID(s)', 'event_espresso') ] = implode(
459
-                ',',
460
-                $gateway_txn_ids_etc
461
-            );
462
-
463
-            $ticket_name      = esc_html__('Unknown', 'event_espresso');
464
-            $datetime_strings = [esc_html__('Unknown', 'event_espresso')];
465
-            if ($reg_row['Ticket.TKT_ID']) {
466
-                $ticket_name       = EEH_Export::prepare_value_from_db_for_display(
467
-                    $ticket_model,
468
-                    'TKT_name',
469
-                    $reg_row['Ticket.TKT_name']
470
-                );
471
-                $datetime_strings = [];
472
-                $datetimes        = $date_model->get_all_wpdb_results(
473
-                    [
474
-                        ['Ticket.TKT_ID' => $reg_row['Ticket.TKT_ID']],
475
-                        'order_by'                 => ['DTT_EVT_start' => 'ASC'],
476
-                        'default_where_conditions' => 'none',
477
-                    ]
478
-                );
479
-                foreach ($datetimes as $datetime) {
480
-                    $datetime_strings[] = EEH_Export::prepare_value_from_db_for_display(
481
-                        $date_model,
482
-                        'DTT_EVT_start',
483
-                        $datetime['Datetime.DTT_EVT_start']
484
-                    );
485
-                }
486
-            }
487
-
488
-            $reg_csv_array[ $ticket_model->field_settings_for('TKT_name')->get_nicename() ] = $ticket_name;
489
-
490
-
491
-            $reg_csv_array[ esc_html__('Ticket Datetimes', 'event_espresso') ] = implode(
492
-                ', ',
493
-                $datetime_strings
494
-            );
495
-
496
-            // add answer columns
497
-            $reg_csv_array = AnswersCSV::addAnswerColumns($reg_row, $reg_csv_array, $question_labels);
498
-            // Include check-in data
499
-            if ($event_id && $DTT_ID) {
500
-                // get whether the user has checked in
501
-                $reg_csv_array[ esc_html__('Datetime Check-ins #', 'event_espresso') ] =
502
-                    $reg_model->count_related(
503
-                        $reg_row['Registration.REG_ID'],
504
-                        'Checkin',
505
-                        [
506
-                            [
507
-                                'DTT_ID' => $DTT_ID
508
-                            ]
509
-                        ]
510
-                    );
511
-                $datetime     = $date_model->get_one_by_ID($DTT_ID);
512
-                $checkin_rows = EEM_Checkin::instance()->get_all(
513
-                    [
514
-                        [
515
-                            'REG_ID' => $reg_row['Registration.REG_ID'],
516
-                            'DTT_ID' => $datetime->get('DTT_ID'),
517
-                        ],
518
-                    ]
519
-                );
520
-                $checkins     = [];
521
-                foreach ($checkin_rows as $checkin_row) {
522
-                    /** @var EE_Checkin $checkin_row */
523
-                    $checkin_value = CheckinsCSV::getCheckinValue($checkin_row);
524
-                    if ($checkin_value) {
525
-                        $checkins[] = $checkin_value;
526
-                    }
527
-                }
528
-                $datetime_name                   = CheckinsCSV::getDatetimeLabel($datetime);
529
-                $reg_csv_array[ $datetime_name ] = implode(' --- ', $checkins);
530
-            } elseif ($event_id) {
531
-                // get whether the user has checked in
532
-                $reg_csv_array[ esc_html__('Event Check-ins #', 'event_espresso') ] =
533
-                    $reg_model->count_related(
534
-                        $reg_row['Registration.REG_ID'],
535
-                        'Checkin'
536
-                    );
537
-
538
-                $datetimes = $date_model->get_all(
539
-                    [
540
-                        [
541
-                            'Ticket.TKT_ID' => $reg_row['Ticket.TKT_ID'],
542
-                        ],
543
-                        'order_by'                 => ['DTT_EVT_start' => 'ASC'],
544
-                        'default_where_conditions' => 'none',
545
-                    ]
546
-                );
547
-                foreach ($datetimes as $datetime) {
548
-                    if (! $datetime instanceof EE_Datetime) {
549
-                        continue;
550
-                    }
551
-
552
-                    /** @var EE_Checkin $checkin_row */
553
-                    $checkin_row = EEM_Checkin::instance()->get_one(
554
-                        [
555
-                            [
556
-                                'REG_ID' => $reg_row['Registration.REG_ID'],
557
-                                'DTT_ID' => $datetime->get('DTT_ID'),
558
-                            ],
559
-                            'limit'    => 1,
560
-                            'order_by' => [
561
-                                'CHK_ID' => 'DESC'
562
-                            ]
563
-                        ]
564
-                    );
565
-
566
-                    $checkin_value = CheckinsCSV::getCheckinValue($checkin_row);
567
-                    $datetime_name = CheckinsCSV::getDatetimeLabel($datetime);
568
-
569
-                    $reg_csv_array[ $datetime_name ] = $checkin_value;
570
-                }
571
-            }
572
-            /**
573
-             * Filter to change the contents of each row of the registrations report CSV file.
574
-             * This can be used to add or remote columns from the CSV file, or change their values.
575
-             * Note when using: all rows in the CSV should have the same columns.
576
-             *
577
-             * @param array $reg_csv_array keys are the column names, values are their cell values
578
-             * @param array $reg_row       one entry from EEM_Registration::get_all_wpdb_results()
579
-             */
580
-            $registrations_csv_ready_array[] = apply_filters(
581
-                'FHEE__EventEspressoBatchRequest__JobHandlers__RegistrationsReport__reg_csv_array',
582
-                $reg_csv_array,
583
-                $reg_row
584
-            );
585
-        }
586
-        // if we couldn't export anything, we want to at least show the column headers
587
-        if (empty($registrations_csv_ready_array)) {
588
-            $reg_csv_array               = [];
589
-            $model_and_fields_to_include = [
590
-                'Registration' => $reg_fields_to_include,
591
-                'Attendee'     => $att_fields_to_include,
592
-            ];
593
-            foreach ($model_and_fields_to_include as $model_name => $field_list) {
594
-                $model = EE_Registry::instance()->load_model($model_name);
595
-                foreach ($field_list as $field_name) {
596
-                    $field                                                          =
597
-                        $model->field_settings_for($field_name);
598
-                    $reg_csv_array[ EEH_Export::get_column_name_for_field($field) ] = null;
599
-                }
600
-            }
601
-            $registrations_csv_ready_array[] = $reg_csv_array;
602
-        }
603
-        return $registrations_csv_ready_array;
604
-    }
605
-
606
-
607
-    /**
608
-     * recursively convert MySQL format date strings in query params array to Datetime objects
609
-     *
610
-     * @param array        $query_params
611
-     * @param DateTimeZone $site_timezone
612
-     * @param DateTimeZone $utc_timezone
613
-     * @return array
614
-     * @throws Exception
615
-     * @since 5.0.19.p
616
-     */
617
-    private function convertDateStringsToObjects(
618
-        array $query_params,
619
-        DateTimeZone $site_timezone,
620
-        DateTimeZone $utc_timezone
621
-    ): array {
622
-        foreach ($query_params as $key => $value) {
623
-            if (is_array($value)) {
624
-                $query_params[$key] = $this->convertDateStringsToObjects($value, $site_timezone, $utc_timezone);
625
-                continue;
626
-            }
627
-            if (preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/', $value)) {
628
-                $query_params[$key] = DbSafeDateTime::createFromFormat('Y-m-d H:i:s', $value, $site_timezone);
629
-                $query_params[$key] = $query_params[$key]->setTimezone($utc_timezone);
630
-            }
631
-        }
632
-        return $query_params;
633
-    }
634
-
635
-
636
-    /**
637
-     * Counts total unit to process
638
-     *
639
-     * @param array $query_params
640
-     * @return int
641
-     * @throws EE_Error
642
-     * @throws ReflectionException
643
-     */
644
-    public function count_units_to_process(array $query_params): int
645
-    {
646
-        return EEM_Registration::instance()->count(
647
-            array_diff_key(
648
-                $query_params,
649
-                array_flip(
650
-                    ['limit']
651
-                )
652
-            )
653
-        );
654
-    }
655
-
656
-
657
-    /**
658
-     * Performs any clean-up logic when we know the job is completed.
659
-     * In this case, we delete the temporary file
660
-     *
661
-     * @param JobParameters $job_parameters
662
-     * @return JobStepResponse
663
-     */
664
-    public function cleanup_job(JobParameters $job_parameters): JobStepResponse
665
-    {
666
-        $this->updateText(esc_html__('File Generation complete and downloaded', 'event_espresso'));
667
-
668
-        $this->_file_helper->delete(
669
-            EEH_File::remove_filename_from_filepath($job_parameters->extra_datum('filepath')),
670
-            true,
671
-            'd'
672
-        );
673
-        $this->updateText(esc_html__('Cleaned up temporary file', 'event_espresso'));
674
-        $this->updateText(
675
-            $this->infoWrapper(
676
-                sprintf(
677
-                    esc_html__(
678
-                        'If not automatically redirected in %1$s seconds, click here to return to the %2$sRegistrations List Table%3$s',
679
-                        'event_espresso'
680
-                    ),
681
-                    '<span id="ee-redirect-timer">10</span>',
682
-                    '<a href="' . $job_parameters->request_datum('return_url') . '">',
683
-                    '</a>'
684
-                )
685
-            )
686
-        );
687
-        return new JobStepResponse($job_parameters, $this->feedback);
688
-    }
49
+	// phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps
50
+	// phpcs:disable PSR2.Methods.MethodDeclaration.Underscore
51
+	/**
52
+	 * Performs any necessary setup for starting the job. This is also a good
53
+	 * place to set up the $job_arguments which will be used for subsequent HTTP requests
54
+	 * when continue_job will be called
55
+	 *
56
+	 * @param JobParameters $job_parameters
57
+	 * @return JobStepResponse
58
+	 * @throws BatchRequestException
59
+	 * @throws EE_Error
60
+	 * @throws ReflectionException
61
+	 * @throws Exception
62
+	 */
63
+	public function create_job(JobParameters $job_parameters): JobStepResponse
64
+	{
65
+		$event_id = absint($job_parameters->request_datum('EVT_ID', '0'));
66
+		$DTT_ID   = absint($job_parameters->request_datum('DTT_ID', '0'));
67
+		if (! EE_Capabilities::instance()->current_user_can('ee_read_registrations', 'generating_report')) {
68
+			throw new BatchRequestException(
69
+				esc_html__('You do not have permission to view registrations', 'event_espresso')
70
+			);
71
+		}
72
+		$filepath = $this->create_file_from_job_with_name(
73
+			$job_parameters->job_id(),
74
+			$this->get_filename()
75
+		);
76
+		$job_parameters->add_extra_data('filepath', $filepath);
77
+
78
+		if ($job_parameters->request_datum('use_filters', false)) {
79
+			$query_params = maybe_unserialize($job_parameters->request_datum('filters', []));
80
+		} else {
81
+			$query_params = [
82
+				[ 'Ticket.TKT_deleted' => ['IN', [true, false]] ],
83
+				'order_by'   => ['Transaction.TXN_ID' => 'asc', 'REG_count' => 'asc'],
84
+				'force_join' => ['Transaction', 'Ticket', 'Attendee'],
85
+				'caps'       => EEM_Base::caps_read_admin,
86
+			];
87
+			if ($event_id) {
88
+				$query_params[0]['EVT_ID'] = $event_id;
89
+			} else {
90
+				$query_params['force_join'][] = 'Event';
91
+			}
92
+		}
93
+		// unless the query params already include a status,
94
+		// we want to exclude registrations from failed or abandoned transactions
95
+		if (! isset($query_params[0]['Transaction.STS_ID'])) {
96
+			$query_params[0]['OR'] = [
97
+				// don't include registrations from failed or abandoned transactions...
98
+				'Transaction.STS_ID' => [
99
+					'NOT IN',
100
+					[
101
+						EEM_Transaction::failed_status_code,
102
+						EEM_Transaction::abandoned_status_code,
103
+					],
104
+				],
105
+				// unless the registration is approved,
106
+				// in which case include it regardless of transaction status
107
+				'STS_ID' => EEM_Registration::status_id_approved,
108
+			];
109
+		}
110
+
111
+		if (! isset($query_params['force_join'])) {
112
+			$query_params['force_join'] = ['Event', 'Transaction', 'Ticket', 'Attendee'];
113
+		}
114
+
115
+		$return_url_args = [];
116
+		parse_str(
117
+			parse_url(
118
+				$job_parameters->request_datum('return_url'),
119
+				PHP_URL_QUERY
120
+			),
121
+			$return_url_args
122
+		);
123
+
124
+		if (
125
+			isset($return_url_args['orderby'], $return_url_args['order'])
126
+			&& $return_url_args['orderby'] === 'ATT_lname'
127
+		) {
128
+			$query_params['order_by'] = [
129
+				'Attendee.ATT_lname' => $return_url_args['order'],
130
+				'Attendee.ATT_fname' => $return_url_args['order'],
131
+				'REG_ID' => $return_url_args['order']
132
+			];
133
+		}
134
+
135
+		$query_params = apply_filters(
136
+			'FHEE__EE_Export__report_registration_for_event',
137
+			$query_params,
138
+			$event_id
139
+		);
140
+
141
+		$utc_timezone = new DateTimeZone('UTC');
142
+		$site_timezone = new DateTimeZone(EEH_DTT_Helper::get_timezone());
143
+		$query_params = $this->convertDateStringsToObjects($query_params, $site_timezone, $utc_timezone);
144
+
145
+		$job_parameters->add_extra_data('query_params', $query_params);
146
+		$question_labels = $this->_get_question_labels($query_params);
147
+		$job_parameters->add_extra_data('question_labels', $question_labels);
148
+		$job_parameters->set_job_size($this->count_units_to_process($query_params));
149
+		// we need to set the header columns
150
+		// but to do that we need to process one row so that we can extract ALL the column headers
151
+		$csv_data_for_row = $this->get_csv_data_for(
152
+			$event_id,
153
+			0,
154
+			1,
155
+			$question_labels,
156
+			$query_params,
157
+			$DTT_ID
158
+		);
159
+		// but we don't want to write any actual data yet...
160
+		// so let's blank out all the values for that first row
161
+		array_walk(
162
+			$csv_data_for_row[0],
163
+			function (&$value) {
164
+				$value = null;
165
+			}
166
+		);
167
+
168
+		EEH_Export::write_data_array_to_csv($filepath, $csv_data_for_row, true, true);
169
+		$this->updateTextHeader(
170
+			esc_html__('Registrations report started successfully...', 'event_espresso')
171
+		);
172
+		return new JobStepResponse($job_parameters, $this->feedback);
173
+	}
174
+
175
+
176
+	/**
177
+	 * Gets the filename
178
+	 *
179
+	 * @return string
180
+	 */
181
+	protected function get_filename(): string
182
+	{
183
+		return apply_filters(
184
+			'FHEE__EventEspressoBatchRequest__JobHandlers__RegistrationsReport__get_filename',
185
+			sprintf(
186
+				'event-espresso-registrations-%s.csv',
187
+				str_replace([':', ' '], '-', current_time('mysql'))
188
+			)
189
+		);
190
+	}
191
+
192
+
193
+	/**
194
+	 * Gets the questions which are to be used for this report,
195
+	 * so they can be remembered for later
196
+	 *
197
+	 * @param array $registration_query_params
198
+	 * @return array question admin labels to be used for this report
199
+	 * @throws EE_Error
200
+	 * @throws ReflectionException
201
+	 */
202
+	protected function _get_question_labels(array $registration_query_params): array
203
+	{
204
+		$where                 = $registration_query_params[0] ?? null;
205
+		$question_query_params = [];
206
+		if ($where !== null) {
207
+			$question_query_params = [
208
+				$this->_change_registration_where_params_to_question_where_params($registration_query_params[0]),
209
+			];
210
+		}
211
+		// Make sure it's not a system question
212
+		$question_query_params[0]['OR*not-system-questions'] = [
213
+			'QST_system'      => '',
214
+			'QST_system*null' => ['IS_NULL']
215
+		];
216
+		if (
217
+			apply_filters(
218
+				'FHEE__EventEspressoBatchRequest__JobHandlers__RegistrationsReport___get_question_labels__only_include_answered_questions',
219
+				false,
220
+				$registration_query_params
221
+			)
222
+		) {
223
+			$question_query_params[0]['Answer.ANS_ID'] = ['IS_NOT_NULL'];
224
+		}
225
+		$question_query_params['order_by'] = [
226
+			'Question_Group_Question.QGQ_order' => 'ASC',
227
+			'QST_order' => 'ASC',
228
+			'QST_admin_label' => 'ASC'
229
+		];
230
+		$question_query_params['group_by'] = ['QST_ID'];
231
+		return array_unique(EEM_Question::instance()->get_col($question_query_params, 'QST_admin_label'));
232
+	}
233
+
234
+
235
+	/**
236
+	 * Takes where params meant for registrations and changes them to work for questions
237
+	 *
238
+	 * @param array $reg_where_params
239
+	 * @return array
240
+	 * @throws EE_Error
241
+	 * @throws ReflectionException
242
+	 */
243
+	protected function _change_registration_where_params_to_question_where_params(array $reg_where_params): array
244
+	{
245
+		$question_where_params = [];
246
+		foreach ($reg_where_params as $key => $val) {
247
+			if (EEM_Registration::instance()->is_logic_query_param_key($key)) {
248
+				$question_where_params[ $key ] =
249
+					$this->_change_registration_where_params_to_question_where_params($val);
250
+			} else {
251
+				// it's a normal where condition
252
+				$question_where_params[ 'Question_Group.Event.Registration.' . $key ] = $val;
253
+			}
254
+		}
255
+		return $question_where_params;
256
+	}
257
+
258
+
259
+	/**
260
+	 * Performs another step of the job
261
+	 *
262
+	 * @param JobParameters $job_parameters
263
+	 * @param int           $batch_size
264
+	 * @return JobStepResponse
265
+	 * @throws EE_Error
266
+	 * @throws ReflectionException
267
+	 */
268
+	public function continue_job(JobParameters $job_parameters, int $batch_size = 50): JobStepResponse
269
+	{
270
+		if ($job_parameters->units_processed() < $job_parameters->job_size()) {
271
+			$csv_data = $this->get_csv_data_for(
272
+				(int) $job_parameters->request_datum('EVT_ID', '0'),
273
+				$job_parameters->units_processed(),
274
+				$batch_size,
275
+				$job_parameters->extra_datum('question_labels'),
276
+				$job_parameters->extra_datum('query_params'),
277
+				(int) $job_parameters->request_datum('DTT_ID', '0')
278
+			);
279
+			EEH_Export::write_data_array_to_csv(
280
+				$job_parameters->extra_datum('filepath'),
281
+				$csv_data,
282
+				false
283
+			);
284
+			$units_processed = count($csv_data);
285
+			if ($units_processed) {
286
+				$job_parameters->mark_processed($units_processed);
287
+				$this->updateText(
288
+					sprintf(
289
+						esc_html__('Wrote %1$s rows to report CSV file...', 'event_espresso'),
290
+						$units_processed
291
+					)
292
+				);
293
+			}
294
+		}
295
+		$extra_response_data = ['file_url' => ''];
296
+		if ($job_parameters->units_processed() >= $job_parameters->job_size()) {
297
+			$job_parameters->set_status(JobParameters::status_complete);
298
+			$extra_response_data['file_url'] = $this->get_url_to_file($job_parameters->extra_datum('filepath'));
299
+			$this->displayJobFinalResults($job_parameters);
300
+		} else {
301
+			$job_parameters->set_status(JobParameters::status_continue);
302
+		}
303
+		return new JobStepResponse($job_parameters, $this->feedback, $extra_response_data);
304
+	}
305
+
306
+
307
+	/**
308
+	 * Gets the csv data for a batch of registrations
309
+	 *
310
+	 * @param int|null $event_id
311
+	 * @param int      $offset
312
+	 * @param int      $limit
313
+	 * @param array    $question_labels the IDs for all the questions which were answered by someone in this selection
314
+	 * @param array    $query_params    for using where querying the model
315
+	 * @param int      $DTT_ID
316
+	 * @return array top-level keys are numeric, next-level keys are column headers
317
+	 * @throws EE_Error
318
+	 * @throws ReflectionException
319
+	 */
320
+	public function get_csv_data_for(
321
+		?int $event_id,
322
+		int $offset,
323
+		int $limit,
324
+		array $question_labels,
325
+		array $query_params,
326
+		int $DTT_ID = 0
327
+	): array {
328
+		$reg_fields_to_include = [
329
+			'TXN_ID',
330
+			'ATT_ID',
331
+			'REG_ID',
332
+			'REG_date',
333
+			'REG_code',
334
+			'REG_count',
335
+			'REG_final_price',
336
+		];
337
+		$att_fields_to_include = [
338
+			'ATT_fname',
339
+			'ATT_lname',
340
+			'ATT_email',
341
+			'ATT_address',
342
+			'ATT_address2',
343
+			'ATT_city',
344
+			'STA_ID',
345
+			'CNT_ISO',
346
+			'ATT_zip',
347
+			'ATT_phone',
348
+		];
349
+
350
+		// get models
351
+		$event_model   = EEM_Event::instance();
352
+		$date_model    = EEM_Datetime::instance();
353
+		$ticket_model  = EEM_Ticket::instance();
354
+		$txn_model     = EEM_Transaction::instance();
355
+		$reg_model     = EEM_Registration::instance();
356
+		$pay_model     = EEM_Payment::instance();
357
+		$status_model  = EEM_Status::instance();
358
+
359
+		$registrations_csv_ready_array = [];
360
+		$query_params['limit']         = [$offset, $limit];
361
+		$registration_rows             = $reg_model->get_all_wpdb_results($query_params);
362
+
363
+		foreach ($registration_rows as $reg_row) {
364
+			if (! is_array($reg_row)) {
365
+				continue;
366
+			}
367
+			$reg_csv_array = [];
368
+			// registration ID
369
+			$reg_id_field = $reg_model->field_settings_for('REG_ID');
370
+			$reg_csv_array[ EEH_Export::get_column_name_for_field($reg_id_field) ] =
371
+				EEH_Export::prepare_value_from_db_for_display(
372
+					$reg_model,
373
+					'REG_ID',
374
+					$reg_row[ $reg_id_field->get_qualified_column() ]
375
+				);
376
+			// ALL registrations, or is list filtered to just one?
377
+			if (! $event_id) {
378
+				// ALL registrations, so get each event's name and ID
379
+				$reg_csv_array[ esc_html__('Event', 'event_espresso') ] = sprintf(
380
+					/* translators: 1: event name, 2: event ID */
381
+					esc_html__('%1$s (%2$s)', 'event_espresso'),
382
+					EEH_Export::prepare_value_from_db_for_display(
383
+						$event_model,
384
+						'EVT_name',
385
+						$reg_row['Event_CPT.post_title']
386
+					),
387
+					$reg_row['Event_CPT.ID']
388
+				);
389
+			}
390
+			// add attendee columns
391
+			$reg_csv_array = AttendeeCSV::addAttendeeColumns($att_fields_to_include, $reg_row, $reg_csv_array);
392
+			// add registration columns
393
+			$reg_csv_array = RegistrationCSV::addRegistrationColumns($reg_fields_to_include, $reg_row, $reg_csv_array);
394
+			// get pretty status
395
+			$stati = $status_model->localized_status(
396
+				[
397
+					$reg_row['Registration.STS_ID']     => esc_html__('unknown', 'event_espresso'),
398
+					$reg_row['TransactionTable.STS_ID'] => esc_html__('unknown', 'event_espresso'),
399
+				],
400
+				false,
401
+				'sentence'
402
+			);
403
+			$is_primary_reg = $reg_row['Registration.REG_count'] == '1';
404
+
405
+			$reg_csv_array[ esc_html__('Registration Status', 'event_espresso') ] =
406
+				$stati[ $reg_row['Registration.STS_ID'] ];
407
+			// get pretty transaction status
408
+			$reg_csv_array[ esc_html__('Transaction Status', 'event_espresso') ]     =
409
+				$stati[ $reg_row['TransactionTable.STS_ID'] ];
410
+			$reg_csv_array[ esc_html__('Transaction Amount Due', 'event_espresso') ] = $is_primary_reg
411
+				? EEH_Export::prepare_value_from_db_for_display(
412
+					$txn_model,
413
+					'TXN_total',
414
+					$reg_row['TransactionTable.TXN_total'],
415
+					'localized_float'
416
+				)
417
+				: '0.00';
418
+
419
+			$reg_csv_array[ esc_html__('Amount Paid', 'event_espresso') ]            = $is_primary_reg
420
+				? EEH_Export::prepare_value_from_db_for_display(
421
+					$txn_model,
422
+					'TXN_paid',
423
+					$reg_row['TransactionTable.TXN_paid'],
424
+					'localized_float'
425
+				)
426
+				: '0.00';
427
+
428
+			$payment_methods                                                                  = [];
429
+			$gateway_txn_ids_etc                                                              = [];
430
+			$payment_times                                                                    = [];
431
+			if ($is_primary_reg && $reg_row['TransactionTable.TXN_ID']) {
432
+				$payments_info = $pay_model->get_all_wpdb_results(
433
+					[
434
+						[
435
+							'TXN_ID' => $reg_row['TransactionTable.TXN_ID'],
436
+							'STS_ID' => EEM_Payment::status_id_approved,
437
+						],
438
+						'force_join' => ['Payment_Method'],
439
+					],
440
+					ARRAY_A,
441
+					'Payment_Method.PMD_admin_name as name, Payment.PAY_txn_id_chq_nmbr as gateway_txn_id, Payment.PAY_timestamp as payment_time'
442
+				);
443
+				[$payment_methods, $gateway_txn_ids_etc, $payment_times] = PaymentsInfoCSV::extractPaymentInfo(
444
+					$payments_info
445
+				);
446
+			}
447
+
448
+			$reg_csv_array[ esc_html__('Payment Date(s)', 'event_espresso') ] = implode(
449
+				',',
450
+				$payment_times
451
+			);
452
+
453
+			$reg_csv_array[ esc_html__('Payment Method(s)', 'event_espresso') ] = implode(
454
+				',',
455
+				$payment_methods
456
+			);
457
+
458
+			$reg_csv_array[ esc_html__('Gateway Transaction ID(s)', 'event_espresso') ] = implode(
459
+				',',
460
+				$gateway_txn_ids_etc
461
+			);
462
+
463
+			$ticket_name      = esc_html__('Unknown', 'event_espresso');
464
+			$datetime_strings = [esc_html__('Unknown', 'event_espresso')];
465
+			if ($reg_row['Ticket.TKT_ID']) {
466
+				$ticket_name       = EEH_Export::prepare_value_from_db_for_display(
467
+					$ticket_model,
468
+					'TKT_name',
469
+					$reg_row['Ticket.TKT_name']
470
+				);
471
+				$datetime_strings = [];
472
+				$datetimes        = $date_model->get_all_wpdb_results(
473
+					[
474
+						['Ticket.TKT_ID' => $reg_row['Ticket.TKT_ID']],
475
+						'order_by'                 => ['DTT_EVT_start' => 'ASC'],
476
+						'default_where_conditions' => 'none',
477
+					]
478
+				);
479
+				foreach ($datetimes as $datetime) {
480
+					$datetime_strings[] = EEH_Export::prepare_value_from_db_for_display(
481
+						$date_model,
482
+						'DTT_EVT_start',
483
+						$datetime['Datetime.DTT_EVT_start']
484
+					);
485
+				}
486
+			}
487
+
488
+			$reg_csv_array[ $ticket_model->field_settings_for('TKT_name')->get_nicename() ] = $ticket_name;
489
+
490
+
491
+			$reg_csv_array[ esc_html__('Ticket Datetimes', 'event_espresso') ] = implode(
492
+				', ',
493
+				$datetime_strings
494
+			);
495
+
496
+			// add answer columns
497
+			$reg_csv_array = AnswersCSV::addAnswerColumns($reg_row, $reg_csv_array, $question_labels);
498
+			// Include check-in data
499
+			if ($event_id && $DTT_ID) {
500
+				// get whether the user has checked in
501
+				$reg_csv_array[ esc_html__('Datetime Check-ins #', 'event_espresso') ] =
502
+					$reg_model->count_related(
503
+						$reg_row['Registration.REG_ID'],
504
+						'Checkin',
505
+						[
506
+							[
507
+								'DTT_ID' => $DTT_ID
508
+							]
509
+						]
510
+					);
511
+				$datetime     = $date_model->get_one_by_ID($DTT_ID);
512
+				$checkin_rows = EEM_Checkin::instance()->get_all(
513
+					[
514
+						[
515
+							'REG_ID' => $reg_row['Registration.REG_ID'],
516
+							'DTT_ID' => $datetime->get('DTT_ID'),
517
+						],
518
+					]
519
+				);
520
+				$checkins     = [];
521
+				foreach ($checkin_rows as $checkin_row) {
522
+					/** @var EE_Checkin $checkin_row */
523
+					$checkin_value = CheckinsCSV::getCheckinValue($checkin_row);
524
+					if ($checkin_value) {
525
+						$checkins[] = $checkin_value;
526
+					}
527
+				}
528
+				$datetime_name                   = CheckinsCSV::getDatetimeLabel($datetime);
529
+				$reg_csv_array[ $datetime_name ] = implode(' --- ', $checkins);
530
+			} elseif ($event_id) {
531
+				// get whether the user has checked in
532
+				$reg_csv_array[ esc_html__('Event Check-ins #', 'event_espresso') ] =
533
+					$reg_model->count_related(
534
+						$reg_row['Registration.REG_ID'],
535
+						'Checkin'
536
+					);
537
+
538
+				$datetimes = $date_model->get_all(
539
+					[
540
+						[
541
+							'Ticket.TKT_ID' => $reg_row['Ticket.TKT_ID'],
542
+						],
543
+						'order_by'                 => ['DTT_EVT_start' => 'ASC'],
544
+						'default_where_conditions' => 'none',
545
+					]
546
+				);
547
+				foreach ($datetimes as $datetime) {
548
+					if (! $datetime instanceof EE_Datetime) {
549
+						continue;
550
+					}
551
+
552
+					/** @var EE_Checkin $checkin_row */
553
+					$checkin_row = EEM_Checkin::instance()->get_one(
554
+						[
555
+							[
556
+								'REG_ID' => $reg_row['Registration.REG_ID'],
557
+								'DTT_ID' => $datetime->get('DTT_ID'),
558
+							],
559
+							'limit'    => 1,
560
+							'order_by' => [
561
+								'CHK_ID' => 'DESC'
562
+							]
563
+						]
564
+					);
565
+
566
+					$checkin_value = CheckinsCSV::getCheckinValue($checkin_row);
567
+					$datetime_name = CheckinsCSV::getDatetimeLabel($datetime);
568
+
569
+					$reg_csv_array[ $datetime_name ] = $checkin_value;
570
+				}
571
+			}
572
+			/**
573
+			 * Filter to change the contents of each row of the registrations report CSV file.
574
+			 * This can be used to add or remote columns from the CSV file, or change their values.
575
+			 * Note when using: all rows in the CSV should have the same columns.
576
+			 *
577
+			 * @param array $reg_csv_array keys are the column names, values are their cell values
578
+			 * @param array $reg_row       one entry from EEM_Registration::get_all_wpdb_results()
579
+			 */
580
+			$registrations_csv_ready_array[] = apply_filters(
581
+				'FHEE__EventEspressoBatchRequest__JobHandlers__RegistrationsReport__reg_csv_array',
582
+				$reg_csv_array,
583
+				$reg_row
584
+			);
585
+		}
586
+		// if we couldn't export anything, we want to at least show the column headers
587
+		if (empty($registrations_csv_ready_array)) {
588
+			$reg_csv_array               = [];
589
+			$model_and_fields_to_include = [
590
+				'Registration' => $reg_fields_to_include,
591
+				'Attendee'     => $att_fields_to_include,
592
+			];
593
+			foreach ($model_and_fields_to_include as $model_name => $field_list) {
594
+				$model = EE_Registry::instance()->load_model($model_name);
595
+				foreach ($field_list as $field_name) {
596
+					$field                                                          =
597
+						$model->field_settings_for($field_name);
598
+					$reg_csv_array[ EEH_Export::get_column_name_for_field($field) ] = null;
599
+				}
600
+			}
601
+			$registrations_csv_ready_array[] = $reg_csv_array;
602
+		}
603
+		return $registrations_csv_ready_array;
604
+	}
605
+
606
+
607
+	/**
608
+	 * recursively convert MySQL format date strings in query params array to Datetime objects
609
+	 *
610
+	 * @param array        $query_params
611
+	 * @param DateTimeZone $site_timezone
612
+	 * @param DateTimeZone $utc_timezone
613
+	 * @return array
614
+	 * @throws Exception
615
+	 * @since 5.0.19.p
616
+	 */
617
+	private function convertDateStringsToObjects(
618
+		array $query_params,
619
+		DateTimeZone $site_timezone,
620
+		DateTimeZone $utc_timezone
621
+	): array {
622
+		foreach ($query_params as $key => $value) {
623
+			if (is_array($value)) {
624
+				$query_params[$key] = $this->convertDateStringsToObjects($value, $site_timezone, $utc_timezone);
625
+				continue;
626
+			}
627
+			if (preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/', $value)) {
628
+				$query_params[$key] = DbSafeDateTime::createFromFormat('Y-m-d H:i:s', $value, $site_timezone);
629
+				$query_params[$key] = $query_params[$key]->setTimezone($utc_timezone);
630
+			}
631
+		}
632
+		return $query_params;
633
+	}
634
+
635
+
636
+	/**
637
+	 * Counts total unit to process
638
+	 *
639
+	 * @param array $query_params
640
+	 * @return int
641
+	 * @throws EE_Error
642
+	 * @throws ReflectionException
643
+	 */
644
+	public function count_units_to_process(array $query_params): int
645
+	{
646
+		return EEM_Registration::instance()->count(
647
+			array_diff_key(
648
+				$query_params,
649
+				array_flip(
650
+					['limit']
651
+				)
652
+			)
653
+		);
654
+	}
655
+
656
+
657
+	/**
658
+	 * Performs any clean-up logic when we know the job is completed.
659
+	 * In this case, we delete the temporary file
660
+	 *
661
+	 * @param JobParameters $job_parameters
662
+	 * @return JobStepResponse
663
+	 */
664
+	public function cleanup_job(JobParameters $job_parameters): JobStepResponse
665
+	{
666
+		$this->updateText(esc_html__('File Generation complete and downloaded', 'event_espresso'));
667
+
668
+		$this->_file_helper->delete(
669
+			EEH_File::remove_filename_from_filepath($job_parameters->extra_datum('filepath')),
670
+			true,
671
+			'd'
672
+		);
673
+		$this->updateText(esc_html__('Cleaned up temporary file', 'event_espresso'));
674
+		$this->updateText(
675
+			$this->infoWrapper(
676
+				sprintf(
677
+					esc_html__(
678
+						'If not automatically redirected in %1$s seconds, click here to return to the %2$sRegistrations List Table%3$s',
679
+						'event_espresso'
680
+					),
681
+					'<span id="ee-redirect-timer">10</span>',
682
+					'<a href="' . $job_parameters->request_datum('return_url') . '">',
683
+					'</a>'
684
+				)
685
+			)
686
+		);
687
+		return new JobStepResponse($job_parameters, $this->feedback);
688
+	}
689 689
 }
Please login to merge, or discard this patch.
Spacing   +32 added lines, -32 removed lines patch added patch discarded remove patch
@@ -64,7 +64,7 @@  discard block
 block discarded – undo
64 64
     {
65 65
         $event_id = absint($job_parameters->request_datum('EVT_ID', '0'));
66 66
         $DTT_ID   = absint($job_parameters->request_datum('DTT_ID', '0'));
67
-        if (! EE_Capabilities::instance()->current_user_can('ee_read_registrations', 'generating_report')) {
67
+        if ( ! EE_Capabilities::instance()->current_user_can('ee_read_registrations', 'generating_report')) {
68 68
             throw new BatchRequestException(
69 69
                 esc_html__('You do not have permission to view registrations', 'event_espresso')
70 70
             );
@@ -79,7 +79,7 @@  discard block
 block discarded – undo
79 79
             $query_params = maybe_unserialize($job_parameters->request_datum('filters', []));
80 80
         } else {
81 81
             $query_params = [
82
-                [ 'Ticket.TKT_deleted' => ['IN', [true, false]] ],
82
+                ['Ticket.TKT_deleted' => ['IN', [true, false]]],
83 83
                 'order_by'   => ['Transaction.TXN_ID' => 'asc', 'REG_count' => 'asc'],
84 84
                 'force_join' => ['Transaction', 'Ticket', 'Attendee'],
85 85
                 'caps'       => EEM_Base::caps_read_admin,
@@ -92,7 +92,7 @@  discard block
 block discarded – undo
92 92
         }
93 93
         // unless the query params already include a status,
94 94
         // we want to exclude registrations from failed or abandoned transactions
95
-        if (! isset($query_params[0]['Transaction.STS_ID'])) {
95
+        if ( ! isset($query_params[0]['Transaction.STS_ID'])) {
96 96
             $query_params[0]['OR'] = [
97 97
                 // don't include registrations from failed or abandoned transactions...
98 98
                 'Transaction.STS_ID' => [
@@ -108,7 +108,7 @@  discard block
 block discarded – undo
108 108
             ];
109 109
         }
110 110
 
111
-        if (! isset($query_params['force_join'])) {
111
+        if ( ! isset($query_params['force_join'])) {
112 112
             $query_params['force_join'] = ['Event', 'Transaction', 'Ticket', 'Attendee'];
113 113
         }
114 114
 
@@ -160,7 +160,7 @@  discard block
 block discarded – undo
160 160
         // so let's blank out all the values for that first row
161 161
         array_walk(
162 162
             $csv_data_for_row[0],
163
-            function (&$value) {
163
+            function(&$value) {
164 164
                 $value = null;
165 165
             }
166 166
         );
@@ -245,11 +245,11 @@  discard block
 block discarded – undo
245 245
         $question_where_params = [];
246 246
         foreach ($reg_where_params as $key => $val) {
247 247
             if (EEM_Registration::instance()->is_logic_query_param_key($key)) {
248
-                $question_where_params[ $key ] =
248
+                $question_where_params[$key] =
249 249
                     $this->_change_registration_where_params_to_question_where_params($val);
250 250
             } else {
251 251
                 // it's a normal where condition
252
-                $question_where_params[ 'Question_Group.Event.Registration.' . $key ] = $val;
252
+                $question_where_params['Question_Group.Event.Registration.'.$key] = $val;
253 253
             }
254 254
         }
255 255
         return $question_where_params;
@@ -361,22 +361,22 @@  discard block
 block discarded – undo
361 361
         $registration_rows             = $reg_model->get_all_wpdb_results($query_params);
362 362
 
363 363
         foreach ($registration_rows as $reg_row) {
364
-            if (! is_array($reg_row)) {
364
+            if ( ! is_array($reg_row)) {
365 365
                 continue;
366 366
             }
367 367
             $reg_csv_array = [];
368 368
             // registration ID
369 369
             $reg_id_field = $reg_model->field_settings_for('REG_ID');
370
-            $reg_csv_array[ EEH_Export::get_column_name_for_field($reg_id_field) ] =
370
+            $reg_csv_array[EEH_Export::get_column_name_for_field($reg_id_field)] =
371 371
                 EEH_Export::prepare_value_from_db_for_display(
372 372
                     $reg_model,
373 373
                     'REG_ID',
374
-                    $reg_row[ $reg_id_field->get_qualified_column() ]
374
+                    $reg_row[$reg_id_field->get_qualified_column()]
375 375
                 );
376 376
             // ALL registrations, or is list filtered to just one?
377
-            if (! $event_id) {
377
+            if ( ! $event_id) {
378 378
                 // ALL registrations, so get each event's name and ID
379
-                $reg_csv_array[ esc_html__('Event', 'event_espresso') ] = sprintf(
379
+                $reg_csv_array[esc_html__('Event', 'event_espresso')] = sprintf(
380 380
                     /* translators: 1: event name, 2: event ID */
381 381
                     esc_html__('%1$s (%2$s)', 'event_espresso'),
382 382
                     EEH_Export::prepare_value_from_db_for_display(
@@ -402,12 +402,12 @@  discard block
 block discarded – undo
402 402
             );
403 403
             $is_primary_reg = $reg_row['Registration.REG_count'] == '1';
404 404
 
405
-            $reg_csv_array[ esc_html__('Registration Status', 'event_espresso') ] =
406
-                $stati[ $reg_row['Registration.STS_ID'] ];
405
+            $reg_csv_array[esc_html__('Registration Status', 'event_espresso')] =
406
+                $stati[$reg_row['Registration.STS_ID']];
407 407
             // get pretty transaction status
408
-            $reg_csv_array[ esc_html__('Transaction Status', 'event_espresso') ]     =
409
-                $stati[ $reg_row['TransactionTable.STS_ID'] ];
410
-            $reg_csv_array[ esc_html__('Transaction Amount Due', 'event_espresso') ] = $is_primary_reg
408
+            $reg_csv_array[esc_html__('Transaction Status', 'event_espresso')]     =
409
+                $stati[$reg_row['TransactionTable.STS_ID']];
410
+            $reg_csv_array[esc_html__('Transaction Amount Due', 'event_espresso')] = $is_primary_reg
411 411
                 ? EEH_Export::prepare_value_from_db_for_display(
412 412
                     $txn_model,
413 413
                     'TXN_total',
@@ -416,7 +416,7 @@  discard block
 block discarded – undo
416 416
                 )
417 417
                 : '0.00';
418 418
 
419
-            $reg_csv_array[ esc_html__('Amount Paid', 'event_espresso') ]            = $is_primary_reg
419
+            $reg_csv_array[esc_html__('Amount Paid', 'event_espresso')] = $is_primary_reg
420 420
                 ? EEH_Export::prepare_value_from_db_for_display(
421 421
                     $txn_model,
422 422
                     'TXN_paid',
@@ -445,17 +445,17 @@  discard block
 block discarded – undo
445 445
                 );
446 446
             }
447 447
 
448
-            $reg_csv_array[ esc_html__('Payment Date(s)', 'event_espresso') ] = implode(
448
+            $reg_csv_array[esc_html__('Payment Date(s)', 'event_espresso')] = implode(
449 449
                 ',',
450 450
                 $payment_times
451 451
             );
452 452
 
453
-            $reg_csv_array[ esc_html__('Payment Method(s)', 'event_espresso') ] = implode(
453
+            $reg_csv_array[esc_html__('Payment Method(s)', 'event_espresso')] = implode(
454 454
                 ',',
455 455
                 $payment_methods
456 456
             );
457 457
 
458
-            $reg_csv_array[ esc_html__('Gateway Transaction ID(s)', 'event_espresso') ] = implode(
458
+            $reg_csv_array[esc_html__('Gateway Transaction ID(s)', 'event_espresso')] = implode(
459 459
                 ',',
460 460
                 $gateway_txn_ids_etc
461 461
             );
@@ -463,7 +463,7 @@  discard block
 block discarded – undo
463 463
             $ticket_name      = esc_html__('Unknown', 'event_espresso');
464 464
             $datetime_strings = [esc_html__('Unknown', 'event_espresso')];
465 465
             if ($reg_row['Ticket.TKT_ID']) {
466
-                $ticket_name       = EEH_Export::prepare_value_from_db_for_display(
466
+                $ticket_name = EEH_Export::prepare_value_from_db_for_display(
467 467
                     $ticket_model,
468 468
                     'TKT_name',
469 469
                     $reg_row['Ticket.TKT_name']
@@ -485,10 +485,10 @@  discard block
 block discarded – undo
485 485
                 }
486 486
             }
487 487
 
488
-            $reg_csv_array[ $ticket_model->field_settings_for('TKT_name')->get_nicename() ] = $ticket_name;
488
+            $reg_csv_array[$ticket_model->field_settings_for('TKT_name')->get_nicename()] = $ticket_name;
489 489
 
490 490
 
491
-            $reg_csv_array[ esc_html__('Ticket Datetimes', 'event_espresso') ] = implode(
491
+            $reg_csv_array[esc_html__('Ticket Datetimes', 'event_espresso')] = implode(
492 492
                 ', ',
493 493
                 $datetime_strings
494 494
             );
@@ -498,7 +498,7 @@  discard block
 block discarded – undo
498 498
             // Include check-in data
499 499
             if ($event_id && $DTT_ID) {
500 500
                 // get whether the user has checked in
501
-                $reg_csv_array[ esc_html__('Datetime Check-ins #', 'event_espresso') ] =
501
+                $reg_csv_array[esc_html__('Datetime Check-ins #', 'event_espresso')] =
502 502
                     $reg_model->count_related(
503 503
                         $reg_row['Registration.REG_ID'],
504 504
                         'Checkin',
@@ -517,7 +517,7 @@  discard block
 block discarded – undo
517 517
                         ],
518 518
                     ]
519 519
                 );
520
-                $checkins     = [];
520
+                $checkins = [];
521 521
                 foreach ($checkin_rows as $checkin_row) {
522 522
                     /** @var EE_Checkin $checkin_row */
523 523
                     $checkin_value = CheckinsCSV::getCheckinValue($checkin_row);
@@ -526,10 +526,10 @@  discard block
 block discarded – undo
526 526
                     }
527 527
                 }
528 528
                 $datetime_name                   = CheckinsCSV::getDatetimeLabel($datetime);
529
-                $reg_csv_array[ $datetime_name ] = implode(' --- ', $checkins);
529
+                $reg_csv_array[$datetime_name] = implode(' --- ', $checkins);
530 530
             } elseif ($event_id) {
531 531
                 // get whether the user has checked in
532
-                $reg_csv_array[ esc_html__('Event Check-ins #', 'event_espresso') ] =
532
+                $reg_csv_array[esc_html__('Event Check-ins #', 'event_espresso')] =
533 533
                     $reg_model->count_related(
534 534
                         $reg_row['Registration.REG_ID'],
535 535
                         'Checkin'
@@ -545,7 +545,7 @@  discard block
 block discarded – undo
545 545
                     ]
546 546
                 );
547 547
                 foreach ($datetimes as $datetime) {
548
-                    if (! $datetime instanceof EE_Datetime) {
548
+                    if ( ! $datetime instanceof EE_Datetime) {
549 549
                         continue;
550 550
                     }
551 551
 
@@ -566,7 +566,7 @@  discard block
 block discarded – undo
566 566
                     $checkin_value = CheckinsCSV::getCheckinValue($checkin_row);
567 567
                     $datetime_name = CheckinsCSV::getDatetimeLabel($datetime);
568 568
 
569
-                    $reg_csv_array[ $datetime_name ] = $checkin_value;
569
+                    $reg_csv_array[$datetime_name] = $checkin_value;
570 570
                 }
571 571
             }
572 572
             /**
@@ -595,7 +595,7 @@  discard block
 block discarded – undo
595 595
                 foreach ($field_list as $field_name) {
596 596
                     $field                                                          =
597 597
                         $model->field_settings_for($field_name);
598
-                    $reg_csv_array[ EEH_Export::get_column_name_for_field($field) ] = null;
598
+                    $reg_csv_array[EEH_Export::get_column_name_for_field($field)] = null;
599 599
                 }
600 600
             }
601 601
             $registrations_csv_ready_array[] = $reg_csv_array;
@@ -679,7 +679,7 @@  discard block
 block discarded – undo
679 679
                         'event_espresso'
680 680
                     ),
681 681
                     '<span id="ee-redirect-timer">10</span>',
682
-                    '<a href="' . $job_parameters->request_datum('return_url') . '">',
682
+                    '<a href="'.$job_parameters->request_datum('return_url').'">',
683 683
                     '</a>'
684 684
                 )
685 685
             )
Please login to merge, or discard this patch.
core/CPTs/EE_CPT_Strategy.core.php 2 patches
Indentation   +341 added lines, -341 removed lines patch added patch discarded remove patch
@@ -16,345 +16,345 @@
 block discarded – undo
16 16
  */
17 17
 class EE_CPT_Strategy extends EE_Base
18 18
 {
19
-    private static ?EE_CPT_Strategy $_instance = null;
20
-
21
-    protected ?EEM_CPT_Base $CPT_model = null;
22
-
23
-    /**
24
-     * @var CptQueryModifier[]
25
-     */
26
-    protected array $query_modifier = [];
27
-
28
-    /**
29
-     * the current page, if it utilizes CPTs
30
-     */
31
-    protected array $CPT = [];
32
-
33
-    /**
34
-     * return value from CustomPostTypeDefinitions::getDefinitions()
35
-     */
36
-    protected array $_CPTs = [];
37
-
38
-    protected array $_CPT_taxonomies = [];
39
-
40
-    protected array $_CPT_terms = [];
41
-
42
-    protected array $_CPT_endpoints = [];
43
-
44
-
45
-    /**
46
-     * @singleton method used to instantiate class object
47
-     * @param CustomPostTypeDefinitions|null $custom_post_types
48
-     * @param CustomTaxonomyDefinitions|null $taxonomies
49
-     * @return EE_CPT_Strategy
50
-     */
51
-    public static function instance(
52
-        CustomPostTypeDefinitions $custom_post_types = null,
53
-        CustomTaxonomyDefinitions $taxonomies = null
54
-    ): EE_CPT_Strategy {
55
-        // check if class object is instantiated
56
-        if (
57
-            ! self::$_instance instanceof EE_CPT_Strategy
58
-            && $custom_post_types instanceof CustomPostTypeDefinitions
59
-            && $taxonomies instanceof CustomTaxonomyDefinitions
60
-        ) {
61
-            self::$_instance = new self($custom_post_types, $taxonomies);
62
-        }
63
-        return self::$_instance;
64
-    }
65
-
66
-
67
-    /**
68
-     * @param CustomPostTypeDefinitions $custom_post_types
69
-     * @param CustomTaxonomyDefinitions $taxonomies
70
-     */
71
-    protected function __construct(
72
-        CustomPostTypeDefinitions $custom_post_types,
73
-        CustomTaxonomyDefinitions $taxonomies
74
-    ) {
75
-        // get CPT data
76
-        $this->_CPTs           = $custom_post_types->getDefinitions();
77
-        $this->_CPT_endpoints  = $this->_set_CPT_endpoints();
78
-        $this->_CPT_taxonomies = $taxonomies->getCustomTaxonomyDefinitions();
79
-        add_action('pre_get_posts', [$this, 'pre_get_posts'], 5);
80
-    }
81
-
82
-
83
-    /**
84
-     * @return array
85
-     */
86
-    public function get_CPT_endpoints(): array
87
-    {
88
-        return $this->_CPT_endpoints;
89
-    }
90
-
91
-
92
-    /**
93
-     * @return array
94
-     */
95
-    public function get_CPT_taxonomies(): array
96
-    {
97
-        return $this->_CPT_taxonomies;
98
-    }
99
-
100
-
101
-    /**
102
-     * add CPT "slugs" to array of default espresso "pages"
103
-     *
104
-     * @return array
105
-     */
106
-    private function _set_CPT_endpoints(): array
107
-    {
108
-        $_CPT_endpoints = [];
109
-        foreach ($this->_CPTs as $CPT_type => $CPT) {
110
-            if (isset($CPT['plural_slug'])) {
111
-                $_CPT_endpoints [ (string) $CPT['plural_slug'] ] = $CPT_type;
112
-            }
113
-        }
114
-        return $_CPT_endpoints;
115
-    }
116
-
117
-
118
-    public function wpQueryPostType(?WP_Query $wp_query): string
119
-    {
120
-        if (! $wp_query instanceof WP_Query) {
121
-            return '';
122
-        }
123
-        $post_type = $wp_query->query->post_type ?? '';
124
-        $post_type = $post_type === '' && isset($wp_query->query['post_type']) ? $wp_query->query['post_type'] : '';
125
-        return is_array($post_type) ? (string) reset($post_type) : (string) $post_type;
126
-    }
127
-
128
-
129
-    public function isEspressoPostType(WP_Query $wp_query): bool
130
-    {
131
-        $post_type = $this->wpQueryPostType($wp_query);
132
-        return isset($this->_CPTs[ $post_type ]);
133
-    }
134
-
135
-
136
-    /**
137
-     * If this query (not just "main" queries (ie, for WP's infamous "loop")) is for an EE CPT, then we want to
138
-     * supercharge the get_posts query to add our EE stuff (like joining to our tables, selecting extra columns, and
139
-     * adding EE objects to the post to facilitate further querying of related data etc)
140
-     *
141
-     * @param WP_Query $wp_query
142
-     * @return void
143
-     * @throws EE_Error
144
-     * @throws ReflectionException
145
-     */
146
-    public function pre_get_posts(WP_Query $wp_query)
147
-    {
148
-        // add our conditionals
149
-        $this->_set_EE_tags_on_WP_Query($wp_query);
150
-        // check for terms
151
-        $this->_set_post_type_for_terms($wp_query);
152
-        // make sure paging is always set
153
-        $this->_set_paging($wp_query);
154
-        // is a taxonomy set ?
155
-        $this->_set_CPT_taxonomies_on_WP_Query($wp_query);
156
-        // loop thru post_types if set
157
-        $this->_process_WP_Query_post_types($wp_query);
158
-    }
159
-
160
-
161
-    /**
162
-     * @param WP_Query $wp_query
163
-     * @return void
164
-     */
165
-    private function _set_EE_tags_on_WP_Query(WP_Query $wp_query)
166
-    {
167
-        $wp_query->is_espresso_event_single   = false;
168
-        $wp_query->is_espresso_event_archive  = false;
169
-        $wp_query->is_espresso_event_taxonomy = false;
170
-        $wp_query->is_espresso_venue_single   = false;
171
-        $wp_query->is_espresso_venue_archive  = false;
172
-        $wp_query->is_espresso_venue_taxonomy = false;
173
-    }
174
-
175
-
176
-    /**
177
-     * @return void
178
-     * @throws EE_Error
179
-     * @throws ReflectionException
180
-     */
181
-    private function _set_CPT_terms()
182
-    {
183
-        if (empty($this->_CPT_terms)) {
184
-            $terms = EEM_Term::instance()->get_all_CPT_post_tags();
185
-            foreach ($terms as $term) {
186
-                if ($term instanceof EE_Term) {
187
-                    $this->_CPT_terms[ $term->slug() ] = $term;
188
-                }
189
-            }
190
-        }
191
-    }
192
-
193
-
194
-    /**
195
-     * @param WP_Query $wp_query
196
-     * @return void
197
-     * @throws EE_Error
198
-     * @throws ReflectionException
199
-     */
200
-    private function _set_post_type_for_terms(WP_Query $wp_query)
201
-    {
202
-        // is a tag set ?
203
-        if (! isset($wp_query->query['tag'])) {
204
-            return;
205
-        }
206
-        // get term for tag
207
-        $term = EEM_Term::instance()->get_post_tag_for_event_or_venue($wp_query->query['tag']);
208
-        // verify the term
209
-        if (! $term instanceof EE_Term) {
210
-            return;
211
-        }
212
-        // set post_type from term
213
-        $term->post_type = (array) apply_filters(
214
-            'FHEE__EE_CPT_Strategy___set_post_type_for_terms__term_post_type',
215
-            array_merge(['post', 'page'], $term->post_type),
216
-            $term
217
-        );
218
-        // if a post type is already set
219
-        if (isset($wp_query->query_vars['post_type'])) {
220
-            // add to existing array
221
-            $term->post_type = array_merge((array) $wp_query->query_vars['post_type'], $term->post_type);
222
-        }
223
-        // just set post_type to our CPT
224
-        $wp_query->set('post_type', array_unique($term->post_type));
225
-    }
226
-
227
-
228
-    /**
229
-     * @param WP_Query $wp_query
230
-     * @return void
231
-     */
232
-    public function _set_paging(WP_Query $wp_query)
233
-    {
234
-        if ($wp_query->is_main_query() && apply_filters('FHEE__EE_CPT_Strategy___set_paging', true)) {
235
-            $page  = get_query_var('page') ? get_query_var('page') : null;
236
-            $paged = get_query_var('paged') ? get_query_var('paged') : $page;
237
-            $wp_query->set('paged', $paged);
238
-        }
239
-    }
240
-
241
-
242
-    /**
243
-     * @param WP_Query $wp_query
244
-     */
245
-    protected function _set_CPT_taxonomies_on_WP_Query(WP_Query $wp_query)
246
-    {
247
-        // is a taxonomy set ?
248
-        if (! $wp_query->is_tax) {
249
-            return;
250
-        }
251
-        // loop thru our taxonomies
252
-        foreach ($this->_CPT_taxonomies as $CPT_taxonomy => $CPT_taxonomy_details) {
253
-            // check if one of our taxonomies is set as a query var
254
-            if (! isset($wp_query->query[ $CPT_taxonomy ])) {
255
-                continue;
256
-            }
257
-            // but which CPT does that correspond to??? hmmm... guess we gotta go looping
258
-            foreach ($this->_CPTs as $post_type => $CPT) {
259
-                // verify our CPT has args, is public and has taxonomies set
260
-                if (
261
-                    ! isset($CPT['args']['public'])
262
-                    || ! $CPT['args']['public']
263
-                    || empty($CPT['args']['taxonomies'])
264
-                    || ! in_array($CPT_taxonomy, $CPT['args']['taxonomies'], true)
265
-                ) {
266
-                    continue;
267
-                }
268
-                // if so, then add this CPT post_type to the current query's array of post_types'
269
-                $wp_query->query_vars['post_type']   = isset($wp_query->query_vars['post_type'])
270
-                    ? (array) $wp_query->query_vars['post_type']
271
-                    : [];
272
-                $wp_query->query_vars['post_type'][] = $post_type;
273
-                switch ($post_type) {
274
-                    case EspressoPostType::EVENTS:
275
-                        $wp_query->is_espresso_event_taxonomy = true;
276
-                        break;
277
-
278
-                    case EspressoPostType::VENUES:
279
-                        $wp_query->is_espresso_venue_taxonomy = true;
280
-                        break;
281
-
282
-                    default:
283
-                        do_action(
284
-                            'AHEE__EE_CPT_Strategy___set_CPT_taxonomies_on_WP_Query__for_' . $post_type . '_post_type',
285
-                            $wp_query,
286
-                            $this
287
-                        );
288
-                }
289
-            }
290
-        }
291
-    }
292
-
293
-
294
-    /**
295
-     * @param WP_Query $wp_query
296
-     */
297
-    protected function _process_WP_Query_post_types(WP_Query $wp_query)
298
-    {
299
-        if (! isset($wp_query->query_vars['post_type'])) {
300
-            return;
301
-        }
302
-        // loop thru post_types as array
303
-        foreach ((array) $wp_query->query_vars['post_type'] as $post_type) {
304
-            // is current query for an EE CPT ?
305
-            if (! isset($this->_CPTs[ $post_type ])) {
306
-                continue;
307
-            }
308
-            // is EE on or off ?
309
-            if (MaintenanceStatus::isNotDisabled()) {
310
-                // reroute CPT template view to maintenance_mode.template.php
311
-                if (! has_filter('template_include', ['EE_Maintenance_Mode', 'template_include'])) {
312
-                    add_filter('template_include', ['EE_Maintenance_Mode', 'template_include'], 99999);
313
-                }
314
-                if (has_filter('the_content', [EE_Maintenance_Mode::instance(), 'the_content'])) {
315
-                    add_filter('the_content', [$this, 'inject_EE_shortcode_placeholder'], 1);
316
-                }
317
-                return;
318
-            }
319
-            $this->_generate_CptQueryModifier($wp_query, $post_type);
320
-        }
321
-    }
322
-
323
-
324
-    /**
325
-     * @param WP_Query $wp_query
326
-     * @param string   $post_type
327
-     */
328
-    protected function _generate_CptQueryModifier(WP_Query $wp_query, string $post_type)
329
-    {
330
-        if (
331
-            isset($this->query_modifier[ $post_type ])
332
-            && $this->query_modifier[ $post_type ] instanceof CptQueryModifier
333
-        ) {
334
-            return;
335
-        }
336
-        $this->query_modifier[ $post_type ] = LoaderFactory::getLoader()->getShared(
337
-            CptQueryModifier::class,
338
-            [
339
-                $post_type,
340
-                $this->_CPTs[ $post_type ],
341
-                $wp_query,
342
-            ]
343
-        );
344
-        $this->_CPT_taxonomies              = $this->query_modifier[ $post_type ]->taxonomies();
345
-    }
346
-
347
-
348
-    /**
349
-     * inject_EE_shortcode_placeholder
350
-     * in order to display the M-Mode notice on our CPT routes,
351
-     * we need to first inject what looks like one of our shortcodes,
352
-     * so that it can be replaced with the actual M-Mode notice
353
-     *
354
-     * @return string
355
-     */
356
-    public function inject_EE_shortcode_placeholder(): string
357
-    {
358
-        return '[ESPRESSO_';
359
-    }
19
+	private static ?EE_CPT_Strategy $_instance = null;
20
+
21
+	protected ?EEM_CPT_Base $CPT_model = null;
22
+
23
+	/**
24
+	 * @var CptQueryModifier[]
25
+	 */
26
+	protected array $query_modifier = [];
27
+
28
+	/**
29
+	 * the current page, if it utilizes CPTs
30
+	 */
31
+	protected array $CPT = [];
32
+
33
+	/**
34
+	 * return value from CustomPostTypeDefinitions::getDefinitions()
35
+	 */
36
+	protected array $_CPTs = [];
37
+
38
+	protected array $_CPT_taxonomies = [];
39
+
40
+	protected array $_CPT_terms = [];
41
+
42
+	protected array $_CPT_endpoints = [];
43
+
44
+
45
+	/**
46
+	 * @singleton method used to instantiate class object
47
+	 * @param CustomPostTypeDefinitions|null $custom_post_types
48
+	 * @param CustomTaxonomyDefinitions|null $taxonomies
49
+	 * @return EE_CPT_Strategy
50
+	 */
51
+	public static function instance(
52
+		CustomPostTypeDefinitions $custom_post_types = null,
53
+		CustomTaxonomyDefinitions $taxonomies = null
54
+	): EE_CPT_Strategy {
55
+		// check if class object is instantiated
56
+		if (
57
+			! self::$_instance instanceof EE_CPT_Strategy
58
+			&& $custom_post_types instanceof CustomPostTypeDefinitions
59
+			&& $taxonomies instanceof CustomTaxonomyDefinitions
60
+		) {
61
+			self::$_instance = new self($custom_post_types, $taxonomies);
62
+		}
63
+		return self::$_instance;
64
+	}
65
+
66
+
67
+	/**
68
+	 * @param CustomPostTypeDefinitions $custom_post_types
69
+	 * @param CustomTaxonomyDefinitions $taxonomies
70
+	 */
71
+	protected function __construct(
72
+		CustomPostTypeDefinitions $custom_post_types,
73
+		CustomTaxonomyDefinitions $taxonomies
74
+	) {
75
+		// get CPT data
76
+		$this->_CPTs           = $custom_post_types->getDefinitions();
77
+		$this->_CPT_endpoints  = $this->_set_CPT_endpoints();
78
+		$this->_CPT_taxonomies = $taxonomies->getCustomTaxonomyDefinitions();
79
+		add_action('pre_get_posts', [$this, 'pre_get_posts'], 5);
80
+	}
81
+
82
+
83
+	/**
84
+	 * @return array
85
+	 */
86
+	public function get_CPT_endpoints(): array
87
+	{
88
+		return $this->_CPT_endpoints;
89
+	}
90
+
91
+
92
+	/**
93
+	 * @return array
94
+	 */
95
+	public function get_CPT_taxonomies(): array
96
+	{
97
+		return $this->_CPT_taxonomies;
98
+	}
99
+
100
+
101
+	/**
102
+	 * add CPT "slugs" to array of default espresso "pages"
103
+	 *
104
+	 * @return array
105
+	 */
106
+	private function _set_CPT_endpoints(): array
107
+	{
108
+		$_CPT_endpoints = [];
109
+		foreach ($this->_CPTs as $CPT_type => $CPT) {
110
+			if (isset($CPT['plural_slug'])) {
111
+				$_CPT_endpoints [ (string) $CPT['plural_slug'] ] = $CPT_type;
112
+			}
113
+		}
114
+		return $_CPT_endpoints;
115
+	}
116
+
117
+
118
+	public function wpQueryPostType(?WP_Query $wp_query): string
119
+	{
120
+		if (! $wp_query instanceof WP_Query) {
121
+			return '';
122
+		}
123
+		$post_type = $wp_query->query->post_type ?? '';
124
+		$post_type = $post_type === '' && isset($wp_query->query['post_type']) ? $wp_query->query['post_type'] : '';
125
+		return is_array($post_type) ? (string) reset($post_type) : (string) $post_type;
126
+	}
127
+
128
+
129
+	public function isEspressoPostType(WP_Query $wp_query): bool
130
+	{
131
+		$post_type = $this->wpQueryPostType($wp_query);
132
+		return isset($this->_CPTs[ $post_type ]);
133
+	}
134
+
135
+
136
+	/**
137
+	 * If this query (not just "main" queries (ie, for WP's infamous "loop")) is for an EE CPT, then we want to
138
+	 * supercharge the get_posts query to add our EE stuff (like joining to our tables, selecting extra columns, and
139
+	 * adding EE objects to the post to facilitate further querying of related data etc)
140
+	 *
141
+	 * @param WP_Query $wp_query
142
+	 * @return void
143
+	 * @throws EE_Error
144
+	 * @throws ReflectionException
145
+	 */
146
+	public function pre_get_posts(WP_Query $wp_query)
147
+	{
148
+		// add our conditionals
149
+		$this->_set_EE_tags_on_WP_Query($wp_query);
150
+		// check for terms
151
+		$this->_set_post_type_for_terms($wp_query);
152
+		// make sure paging is always set
153
+		$this->_set_paging($wp_query);
154
+		// is a taxonomy set ?
155
+		$this->_set_CPT_taxonomies_on_WP_Query($wp_query);
156
+		// loop thru post_types if set
157
+		$this->_process_WP_Query_post_types($wp_query);
158
+	}
159
+
160
+
161
+	/**
162
+	 * @param WP_Query $wp_query
163
+	 * @return void
164
+	 */
165
+	private function _set_EE_tags_on_WP_Query(WP_Query $wp_query)
166
+	{
167
+		$wp_query->is_espresso_event_single   = false;
168
+		$wp_query->is_espresso_event_archive  = false;
169
+		$wp_query->is_espresso_event_taxonomy = false;
170
+		$wp_query->is_espresso_venue_single   = false;
171
+		$wp_query->is_espresso_venue_archive  = false;
172
+		$wp_query->is_espresso_venue_taxonomy = false;
173
+	}
174
+
175
+
176
+	/**
177
+	 * @return void
178
+	 * @throws EE_Error
179
+	 * @throws ReflectionException
180
+	 */
181
+	private function _set_CPT_terms()
182
+	{
183
+		if (empty($this->_CPT_terms)) {
184
+			$terms = EEM_Term::instance()->get_all_CPT_post_tags();
185
+			foreach ($terms as $term) {
186
+				if ($term instanceof EE_Term) {
187
+					$this->_CPT_terms[ $term->slug() ] = $term;
188
+				}
189
+			}
190
+		}
191
+	}
192
+
193
+
194
+	/**
195
+	 * @param WP_Query $wp_query
196
+	 * @return void
197
+	 * @throws EE_Error
198
+	 * @throws ReflectionException
199
+	 */
200
+	private function _set_post_type_for_terms(WP_Query $wp_query)
201
+	{
202
+		// is a tag set ?
203
+		if (! isset($wp_query->query['tag'])) {
204
+			return;
205
+		}
206
+		// get term for tag
207
+		$term = EEM_Term::instance()->get_post_tag_for_event_or_venue($wp_query->query['tag']);
208
+		// verify the term
209
+		if (! $term instanceof EE_Term) {
210
+			return;
211
+		}
212
+		// set post_type from term
213
+		$term->post_type = (array) apply_filters(
214
+			'FHEE__EE_CPT_Strategy___set_post_type_for_terms__term_post_type',
215
+			array_merge(['post', 'page'], $term->post_type),
216
+			$term
217
+		);
218
+		// if a post type is already set
219
+		if (isset($wp_query->query_vars['post_type'])) {
220
+			// add to existing array
221
+			$term->post_type = array_merge((array) $wp_query->query_vars['post_type'], $term->post_type);
222
+		}
223
+		// just set post_type to our CPT
224
+		$wp_query->set('post_type', array_unique($term->post_type));
225
+	}
226
+
227
+
228
+	/**
229
+	 * @param WP_Query $wp_query
230
+	 * @return void
231
+	 */
232
+	public function _set_paging(WP_Query $wp_query)
233
+	{
234
+		if ($wp_query->is_main_query() && apply_filters('FHEE__EE_CPT_Strategy___set_paging', true)) {
235
+			$page  = get_query_var('page') ? get_query_var('page') : null;
236
+			$paged = get_query_var('paged') ? get_query_var('paged') : $page;
237
+			$wp_query->set('paged', $paged);
238
+		}
239
+	}
240
+
241
+
242
+	/**
243
+	 * @param WP_Query $wp_query
244
+	 */
245
+	protected function _set_CPT_taxonomies_on_WP_Query(WP_Query $wp_query)
246
+	{
247
+		// is a taxonomy set ?
248
+		if (! $wp_query->is_tax) {
249
+			return;
250
+		}
251
+		// loop thru our taxonomies
252
+		foreach ($this->_CPT_taxonomies as $CPT_taxonomy => $CPT_taxonomy_details) {
253
+			// check if one of our taxonomies is set as a query var
254
+			if (! isset($wp_query->query[ $CPT_taxonomy ])) {
255
+				continue;
256
+			}
257
+			// but which CPT does that correspond to??? hmmm... guess we gotta go looping
258
+			foreach ($this->_CPTs as $post_type => $CPT) {
259
+				// verify our CPT has args, is public and has taxonomies set
260
+				if (
261
+					! isset($CPT['args']['public'])
262
+					|| ! $CPT['args']['public']
263
+					|| empty($CPT['args']['taxonomies'])
264
+					|| ! in_array($CPT_taxonomy, $CPT['args']['taxonomies'], true)
265
+				) {
266
+					continue;
267
+				}
268
+				// if so, then add this CPT post_type to the current query's array of post_types'
269
+				$wp_query->query_vars['post_type']   = isset($wp_query->query_vars['post_type'])
270
+					? (array) $wp_query->query_vars['post_type']
271
+					: [];
272
+				$wp_query->query_vars['post_type'][] = $post_type;
273
+				switch ($post_type) {
274
+					case EspressoPostType::EVENTS:
275
+						$wp_query->is_espresso_event_taxonomy = true;
276
+						break;
277
+
278
+					case EspressoPostType::VENUES:
279
+						$wp_query->is_espresso_venue_taxonomy = true;
280
+						break;
281
+
282
+					default:
283
+						do_action(
284
+							'AHEE__EE_CPT_Strategy___set_CPT_taxonomies_on_WP_Query__for_' . $post_type . '_post_type',
285
+							$wp_query,
286
+							$this
287
+						);
288
+				}
289
+			}
290
+		}
291
+	}
292
+
293
+
294
+	/**
295
+	 * @param WP_Query $wp_query
296
+	 */
297
+	protected function _process_WP_Query_post_types(WP_Query $wp_query)
298
+	{
299
+		if (! isset($wp_query->query_vars['post_type'])) {
300
+			return;
301
+		}
302
+		// loop thru post_types as array
303
+		foreach ((array) $wp_query->query_vars['post_type'] as $post_type) {
304
+			// is current query for an EE CPT ?
305
+			if (! isset($this->_CPTs[ $post_type ])) {
306
+				continue;
307
+			}
308
+			// is EE on or off ?
309
+			if (MaintenanceStatus::isNotDisabled()) {
310
+				// reroute CPT template view to maintenance_mode.template.php
311
+				if (! has_filter('template_include', ['EE_Maintenance_Mode', 'template_include'])) {
312
+					add_filter('template_include', ['EE_Maintenance_Mode', 'template_include'], 99999);
313
+				}
314
+				if (has_filter('the_content', [EE_Maintenance_Mode::instance(), 'the_content'])) {
315
+					add_filter('the_content', [$this, 'inject_EE_shortcode_placeholder'], 1);
316
+				}
317
+				return;
318
+			}
319
+			$this->_generate_CptQueryModifier($wp_query, $post_type);
320
+		}
321
+	}
322
+
323
+
324
+	/**
325
+	 * @param WP_Query $wp_query
326
+	 * @param string   $post_type
327
+	 */
328
+	protected function _generate_CptQueryModifier(WP_Query $wp_query, string $post_type)
329
+	{
330
+		if (
331
+			isset($this->query_modifier[ $post_type ])
332
+			&& $this->query_modifier[ $post_type ] instanceof CptQueryModifier
333
+		) {
334
+			return;
335
+		}
336
+		$this->query_modifier[ $post_type ] = LoaderFactory::getLoader()->getShared(
337
+			CptQueryModifier::class,
338
+			[
339
+				$post_type,
340
+				$this->_CPTs[ $post_type ],
341
+				$wp_query,
342
+			]
343
+		);
344
+		$this->_CPT_taxonomies              = $this->query_modifier[ $post_type ]->taxonomies();
345
+	}
346
+
347
+
348
+	/**
349
+	 * inject_EE_shortcode_placeholder
350
+	 * in order to display the M-Mode notice on our CPT routes,
351
+	 * we need to first inject what looks like one of our shortcodes,
352
+	 * so that it can be replaced with the actual M-Mode notice
353
+	 *
354
+	 * @return string
355
+	 */
356
+	public function inject_EE_shortcode_placeholder(): string
357
+	{
358
+		return '[ESPRESSO_';
359
+	}
360 360
 }
Please login to merge, or discard this patch.
Spacing   +17 added lines, -17 removed lines patch added patch discarded remove patch
@@ -108,7 +108,7 @@  discard block
 block discarded – undo
108 108
         $_CPT_endpoints = [];
109 109
         foreach ($this->_CPTs as $CPT_type => $CPT) {
110 110
             if (isset($CPT['plural_slug'])) {
111
-                $_CPT_endpoints [ (string) $CPT['plural_slug'] ] = $CPT_type;
111
+                $_CPT_endpoints [(string) $CPT['plural_slug']] = $CPT_type;
112 112
             }
113 113
         }
114 114
         return $_CPT_endpoints;
@@ -117,7 +117,7 @@  discard block
 block discarded – undo
117 117
 
118 118
     public function wpQueryPostType(?WP_Query $wp_query): string
119 119
     {
120
-        if (! $wp_query instanceof WP_Query) {
120
+        if ( ! $wp_query instanceof WP_Query) {
121 121
             return '';
122 122
         }
123 123
         $post_type = $wp_query->query->post_type ?? '';
@@ -129,7 +129,7 @@  discard block
 block discarded – undo
129 129
     public function isEspressoPostType(WP_Query $wp_query): bool
130 130
     {
131 131
         $post_type = $this->wpQueryPostType($wp_query);
132
-        return isset($this->_CPTs[ $post_type ]);
132
+        return isset($this->_CPTs[$post_type]);
133 133
     }
134 134
 
135 135
 
@@ -184,7 +184,7 @@  discard block
 block discarded – undo
184 184
             $terms = EEM_Term::instance()->get_all_CPT_post_tags();
185 185
             foreach ($terms as $term) {
186 186
                 if ($term instanceof EE_Term) {
187
-                    $this->_CPT_terms[ $term->slug() ] = $term;
187
+                    $this->_CPT_terms[$term->slug()] = $term;
188 188
                 }
189 189
             }
190 190
         }
@@ -200,13 +200,13 @@  discard block
 block discarded – undo
200 200
     private function _set_post_type_for_terms(WP_Query $wp_query)
201 201
     {
202 202
         // is a tag set ?
203
-        if (! isset($wp_query->query['tag'])) {
203
+        if ( ! isset($wp_query->query['tag'])) {
204 204
             return;
205 205
         }
206 206
         // get term for tag
207 207
         $term = EEM_Term::instance()->get_post_tag_for_event_or_venue($wp_query->query['tag']);
208 208
         // verify the term
209
-        if (! $term instanceof EE_Term) {
209
+        if ( ! $term instanceof EE_Term) {
210 210
             return;
211 211
         }
212 212
         // set post_type from term
@@ -245,13 +245,13 @@  discard block
 block discarded – undo
245 245
     protected function _set_CPT_taxonomies_on_WP_Query(WP_Query $wp_query)
246 246
     {
247 247
         // is a taxonomy set ?
248
-        if (! $wp_query->is_tax) {
248
+        if ( ! $wp_query->is_tax) {
249 249
             return;
250 250
         }
251 251
         // loop thru our taxonomies
252 252
         foreach ($this->_CPT_taxonomies as $CPT_taxonomy => $CPT_taxonomy_details) {
253 253
             // check if one of our taxonomies is set as a query var
254
-            if (! isset($wp_query->query[ $CPT_taxonomy ])) {
254
+            if ( ! isset($wp_query->query[$CPT_taxonomy])) {
255 255
                 continue;
256 256
             }
257 257
             // but which CPT does that correspond to??? hmmm... guess we gotta go looping
@@ -281,7 +281,7 @@  discard block
 block discarded – undo
281 281
 
282 282
                     default:
283 283
                         do_action(
284
-                            'AHEE__EE_CPT_Strategy___set_CPT_taxonomies_on_WP_Query__for_' . $post_type . '_post_type',
284
+                            'AHEE__EE_CPT_Strategy___set_CPT_taxonomies_on_WP_Query__for_'.$post_type.'_post_type',
285 285
                             $wp_query,
286 286
                             $this
287 287
                         );
@@ -296,19 +296,19 @@  discard block
 block discarded – undo
296 296
      */
297 297
     protected function _process_WP_Query_post_types(WP_Query $wp_query)
298 298
     {
299
-        if (! isset($wp_query->query_vars['post_type'])) {
299
+        if ( ! isset($wp_query->query_vars['post_type'])) {
300 300
             return;
301 301
         }
302 302
         // loop thru post_types as array
303 303
         foreach ((array) $wp_query->query_vars['post_type'] as $post_type) {
304 304
             // is current query for an EE CPT ?
305
-            if (! isset($this->_CPTs[ $post_type ])) {
305
+            if ( ! isset($this->_CPTs[$post_type])) {
306 306
                 continue;
307 307
             }
308 308
             // is EE on or off ?
309 309
             if (MaintenanceStatus::isNotDisabled()) {
310 310
                 // reroute CPT template view to maintenance_mode.template.php
311
-                if (! has_filter('template_include', ['EE_Maintenance_Mode', 'template_include'])) {
311
+                if ( ! has_filter('template_include', ['EE_Maintenance_Mode', 'template_include'])) {
312 312
                     add_filter('template_include', ['EE_Maintenance_Mode', 'template_include'], 99999);
313 313
                 }
314 314
                 if (has_filter('the_content', [EE_Maintenance_Mode::instance(), 'the_content'])) {
@@ -328,20 +328,20 @@  discard block
 block discarded – undo
328 328
     protected function _generate_CptQueryModifier(WP_Query $wp_query, string $post_type)
329 329
     {
330 330
         if (
331
-            isset($this->query_modifier[ $post_type ])
332
-            && $this->query_modifier[ $post_type ] instanceof CptQueryModifier
331
+            isset($this->query_modifier[$post_type])
332
+            && $this->query_modifier[$post_type] instanceof CptQueryModifier
333 333
         ) {
334 334
             return;
335 335
         }
336
-        $this->query_modifier[ $post_type ] = LoaderFactory::getLoader()->getShared(
336
+        $this->query_modifier[$post_type] = LoaderFactory::getLoader()->getShared(
337 337
             CptQueryModifier::class,
338 338
             [
339 339
                 $post_type,
340
-                $this->_CPTs[ $post_type ],
340
+                $this->_CPTs[$post_type],
341 341
                 $wp_query,
342 342
             ]
343 343
         );
344
-        $this->_CPT_taxonomies              = $this->query_modifier[ $post_type ]->taxonomies();
344
+        $this->_CPT_taxonomies = $this->query_modifier[$post_type]->taxonomies();
345 345
     }
346 346
 
347 347
 
Please login to merge, or discard this patch.