Completed
Branch more-edtr-ui-fixes (2c574f)
by
unknown
35:48 queued 28:48
created
caffeinated/admin/extend/about/templates/ee4-overview.template.php 1 patch
Indentation   +117 added lines, -117 removed lines patch added patch discarded remove patch
@@ -2,75 +2,75 @@  discard block
 block discarded – undo
2 2
     <div class='padding'>
3 3
         <div class="headline-feature">
4 4
             <h2 class="about-headline-callout"><?php
5
-                esc_html_e('Welcome to Event Espresso 4!', 'event_espresso'); ?></h2>
5
+				esc_html_e('Welcome to Event Espresso 4!', 'event_espresso'); ?></h2>
6 6
             <p>
7 7
                 <?php
8
-                printf(
9
-                    esc_html__(
10
-                        'Manage your events from your WordPress dashboard. Reduce your admin, reduce your costs, make your life easier! This is the Caffeinated/Regular version of Event Espresso, but we also have a hosted version called %sEvent Smart%s for customers that want to cut back on their hosting and security expenses.',
11
-                        'event_espresso'
12
-                    ),
13
-                    '<a href="https://eventsmart.com//?utm_source=ee4_decaf&amp;utm_medium=link&amp;utm_campaign=espresso_about_tab&amp;utm_content=EE4+Caffeinated">',
14
-                    '</a>'
15
-                ); ?>
8
+				printf(
9
+					esc_html__(
10
+						'Manage your events from your WordPress dashboard. Reduce your admin, reduce your costs, make your life easier! This is the Caffeinated/Regular version of Event Espresso, but we also have a hosted version called %sEvent Smart%s for customers that want to cut back on their hosting and security expenses.',
11
+						'event_espresso'
12
+					),
13
+					'<a href="https://eventsmart.com//?utm_source=ee4_decaf&amp;utm_medium=link&amp;utm_campaign=espresso_about_tab&amp;utm_content=EE4+Caffeinated">',
14
+					'</a>'
15
+				); ?>
16 16
             </p>
17 17
             <h2>
18 18
                 <?php
19
-                esc_html_e(
20
-                    'Powering 40,000+ event websites; $100 million in ticket sales per year!',
21
-                    'event_espresso'
22
-                ); ?>
19
+				esc_html_e(
20
+					'Powering 40,000+ event websites; $100 million in ticket sales per year!',
21
+					'event_espresso'
22
+				); ?>
23 23
             </h2>
24 24
             <p>
25 25
                 <?php
26
-                printf(
27
-                    esc_html__(
28
-                        'Event Espresso is a %sWordPress event manager%s which makes it easy for you to register attendees for classes, workshops, events, trainings, conferences or concerts, all from your WordPress website. Event Espresso events are created from the WordPress admin area. You can create signup forms to collect information about your attendees, accept payments, and create reports. The lite version of the plugin provides everything that you need to manage your event using WordPress.',
29
-                        'event_espresso'
30
-                    ),
31
-                    '<a href="https://eventespresso.com/?utm_source=wordpress_org&amp;utm_medium=link&amp;utm_campaign=espresso_about_tab&amp;utm_content=EE4+Decaf" rel="nofollow">',
32
-                    '</a>'
33
-                ); ?>
26
+				printf(
27
+					esc_html__(
28
+						'Event Espresso is a %sWordPress event manager%s which makes it easy for you to register attendees for classes, workshops, events, trainings, conferences or concerts, all from your WordPress website. Event Espresso events are created from the WordPress admin area. You can create signup forms to collect information about your attendees, accept payments, and create reports. The lite version of the plugin provides everything that you need to manage your event using WordPress.',
29
+						'event_espresso'
30
+					),
31
+					'<a href="https://eventespresso.com/?utm_source=wordpress_org&amp;utm_medium=link&amp;utm_campaign=espresso_about_tab&amp;utm_content=EE4+Decaf" rel="nofollow">',
32
+					'</a>'
33
+				); ?>
34 34
             </p>
35 35
         </div>
36 36
         <div class="feature-section has-3-columns is-fullwidth three-col">
37 37
             <div class="column col">
38 38
                 <img src="<?php
39
-                echo EEH_Template::getScreenshotUrl('publish_meta_box'); ?>">
39
+				echo EEH_Template::getScreenshotUrl('publish_meta_box'); ?>">
40 40
                 <h3><?php
41
-                    esc_html_e('Optimized aesthetic', 'event_espresso'); ?></h3>
41
+					esc_html_e('Optimized aesthetic', 'event_espresso'); ?></h3>
42 42
                 <p>
43 43
                     <?php
44
-                    esc_html_e(
45
-                        'The Event Espresso 4 dashboard has a fresh, uncluttered design that embraces clarity and simplicity.',
46
-                        'event_espresso'
47
-                    ); ?>
44
+					esc_html_e(
45
+						'The Event Espresso 4 dashboard has a fresh, uncluttered design that embraces clarity and simplicity.',
46
+						'event_espresso'
47
+					); ?>
48 48
                 </p>
49 49
             </div>
50 50
             <div class="column col">
51 51
                 <img src="<?php
52
-                echo EEH_Template::getScreenshotUrl('registrations-overview'); ?>">
52
+				echo EEH_Template::getScreenshotUrl('registrations-overview'); ?>">
53 53
                 <h3><?php
54
-                    esc_html_e('Integrated management', 'event_espresso'); ?></h3>
54
+					esc_html_e('Integrated management', 'event_espresso'); ?></h3>
55 55
                 <p>
56 56
                     <?php
57
-                    esc_html_e(
58
-                        'We’ve made it easier to know who your customers are and how they’ve done business with you over time.',
59
-                        'event_espresso'
60
-                    ); ?>
57
+					esc_html_e(
58
+						'We’ve made it easier to know who your customers are and how they’ve done business with you over time.',
59
+						'event_espresso'
60
+					); ?>
61 61
                 </p>
62 62
             </div>
63 63
             <div class="column col last-feature">
64 64
                 <img src="<?php
65
-                echo EEH_Template::getScreenshotUrl('refined-bookkeeping'); ?>">
65
+				echo EEH_Template::getScreenshotUrl('refined-bookkeeping'); ?>">
66 66
                 <h3><?php
67
-                    esc_html_e('Easy bookkeeping', 'event_espresso'); ?></h3>
67
+					esc_html_e('Easy bookkeeping', 'event_espresso'); ?></h3>
68 68
                 <p>
69 69
                     <?php
70
-                    esc_html_e(
71
-                        'Registrations, payment, and transactions have been substantially improved in Event Espresso 4.',
72
-                        'event_espresso'
73
-                    ); ?>
70
+					esc_html_e(
71
+						'Registrations, payment, and transactions have been substantially improved in Event Espresso 4.',
72
+						'event_espresso'
73
+					); ?>
74 74
                 </p>
75 75
             </div>
76 76
         </div>
@@ -80,61 +80,61 @@  discard block
 block discarded – undo
80 80
         <div class="feature-section has-2-columns is-fullwidth two-col">
81 81
             <div class="column col">
82 82
                 <h3><?php
83
-                    esc_html_e('Higher customer retention', 'event_espresso'); ?></h3>
83
+					esc_html_e('Higher customer retention', 'event_espresso'); ?></h3>
84 84
                 <p>
85 85
                     <?php
86
-                    esc_html_e(
87
-                        'The Event Espresso 4 registration process is faster than ever. With quick ticket selections, single page check-out, and customizable notifications! Registration that can scale to your business needs.',
88
-                        'event_espresso'
89
-                    ); ?>
86
+					esc_html_e(
87
+						'The Event Espresso 4 registration process is faster than ever. With quick ticket selections, single page check-out, and customizable notifications! Registration that can scale to your business needs.',
88
+						'event_espresso'
89
+					); ?>
90 90
                 </p>
91 91
                 <h4><?php
92
-                    esc_html_e('Ticket selection boxes on any post page or post', 'event_espresso'); ?></h4>
92
+					esc_html_e('Ticket selection boxes on any post page or post', 'event_espresso'); ?></h4>
93 93
                 <p>
94 94
                     <?php
95
-                    esc_html_e(
96
-                        'Customers can easily register for classes, events, or conferences, in just a few simple steps. No matter how you use it, Event Espresso 4 will adapt to a multitude of different ticketing and pricing scenarios.',
97
-                        'event_espresso'
98
-                    ); ?>
95
+					esc_html_e(
96
+						'Customers can easily register for classes, events, or conferences, in just a few simple steps. No matter how you use it, Event Espresso 4 will adapt to a multitude of different ticketing and pricing scenarios.',
97
+						'event_espresso'
98
+					); ?>
99 99
                 </p>
100 100
             </div>
101 101
             <div class="column col last-feature about-colors-img">
102 102
                 <img src="<?php
103
-                echo EEH_Template::getScreenshotUrl('registration-page-large'); ?>">
103
+				echo EEH_Template::getScreenshotUrl('registration-page-large'); ?>">
104 104
             </div>
105 105
         </div>
106 106
 
107 107
         <div class="feature-section has-2-columns is-fullwidth two-col">
108 108
             <div class="column col">
109 109
                 <h3><?php
110
-                    esc_html_e('Refined event management', 'event_espresso'); ?></h3>
110
+					esc_html_e('Refined event management', 'event_espresso'); ?></h3>
111 111
                 <p>
112 112
                     <?php
113
-                    esc_html_e(
114
-                        'The new event management screen lets you survey your events at a glance. Want more information? Click to view more. Quickly add/edit prices, dates, or information in any event.',
115
-                        'event_espresso'
116
-                    ); ?>
113
+					esc_html_e(
114
+						'The new event management screen lets you survey your events at a glance. Want more information? Click to view more. Quickly add/edit prices, dates, or information in any event.',
115
+						'event_espresso'
116
+					); ?>
117 117
                 </p>
118 118
                 <h4><?php
119
-                    esc_html_e('Smoother price types, taxes, and price modifiers', 'event_espresso'); ?></h4>
119
+					esc_html_e('Smoother price types, taxes, and price modifiers', 'event_espresso'); ?></h4>
120 120
                 <p>
121 121
                     <?php
122
-                    esc_html_e(
123
-                        'Price Types allow you to create new prices that adjust the default ticket (base) price for your system-default ticket',
124
-                        'event_espresso'
125
-                    ); ?>.
122
+					esc_html_e(
123
+						'Price Types allow you to create new prices that adjust the default ticket (base) price for your system-default ticket',
124
+						'event_espresso'
125
+					); ?>.
126 126
                 </p>
127 127
                 <p>
128 128
                     <?php
129
-                    esc_html_e(
130
-                        'Easily categorize a price modifier and indicate how that price gets applied to the running total when a transaction occurs.',
131
-                        'event_espresso'
132
-                    ); ?>
129
+					esc_html_e(
130
+						'Easily categorize a price modifier and indicate how that price gets applied to the running total when a transaction occurs.',
131
+						'event_espresso'
132
+					); ?>
133 133
                 </p>
134 134
             </div>
135 135
             <div class="column col last-feature about-themes-img">
136 136
                 <img src="<?php
137
-                echo EEH_Template::getScreenshotUrl('event-management'); ?>">
137
+				echo EEH_Template::getScreenshotUrl('event-management'); ?>">
138 138
             </div>
139 139
         </div>
140 140
 
@@ -142,50 +142,50 @@  discard block
 block discarded – undo
142 142
 
143 143
         <h2 class="about-headline-callout">
144 144
             <?php
145
-            esc_html_e(
146
-                'People Like You Manage Event Registration with WordPress',
147
-                'event_espresso'
148
-            ); ?>
145
+			esc_html_e(
146
+				'People Like You Manage Event Registration with WordPress',
147
+				'event_espresso'
148
+			); ?>
149 149
         </h2>
150 150
         <div class="feature-section has-2-columns is-fullwidth two-col">
151 151
             <div class="column col">
152 152
                 <p>
153 153
                     <?php
154
-                    printf(
155
-                        esc_html__(
156
-                            'Trusted by thousands, Event Espresso is the best WordPress event online registration and ticketing manager plugin–and the best supported with full-time support. Turn your existing blog or website into a %sfully-featured event management website%s and a new way to make money. With Event Espresso you get it all; everything from custom registration forms and emails, seating limits, multiple price options, and discount codes to printable tickets.',
157
-                            'event_espresso'
158
-                        ),
159
-                        '<strong>',
160
-                        '</strong>'
161
-                    ); ?>
154
+					printf(
155
+						esc_html__(
156
+							'Trusted by thousands, Event Espresso is the best WordPress event online registration and ticketing manager plugin–and the best supported with full-time support. Turn your existing blog or website into a %sfully-featured event management website%s and a new way to make money. With Event Espresso you get it all; everything from custom registration forms and emails, seating limits, multiple price options, and discount codes to printable tickets.',
157
+							'event_espresso'
158
+						),
159
+						'<strong>',
160
+						'</strong>'
161
+					); ?>
162 162
                 </p>
163 163
                 <p>
164 164
                     <?php
165
-                    esc_html_e(
166
-                        'Event Espresso works perfectly for classes, workshops, fundraisers, sporting, trainings, conferences, networking, religion, social, non-profit, and nearly any other type of event.',
167
-                        'event_espresso'
168
-                    ); ?>
165
+					esc_html_e(
166
+						'Event Espresso works perfectly for classes, workshops, fundraisers, sporting, trainings, conferences, networking, religion, social, non-profit, and nearly any other type of event.',
167
+						'event_espresso'
168
+					); ?>
169 169
                 </p>
170 170
             </div>
171 171
             <div class="column col">
172 172
                 <p>
173 173
                     <?php
174
-                    printf(
175
-                        esc_html__(
176
-                            'Our online event registration software can %smake your organization more profitable and efficient%s by helping you save money on registration and ticketing fees, reduce the countless hours of time you spend manually processing registrations, create a “green” and paperless event registration process and you will be open for business to accept registrations and payment 24/7.',
177
-                            'event_espresso'
178
-                        ),
179
-                        '<strong>',
180
-                        '</strong>'
181
-                    ); ?>
174
+					printf(
175
+						esc_html__(
176
+							'Our online event registration software can %smake your organization more profitable and efficient%s by helping you save money on registration and ticketing fees, reduce the countless hours of time you spend manually processing registrations, create a “green” and paperless event registration process and you will be open for business to accept registrations and payment 24/7.',
177
+							'event_espresso'
178
+						),
179
+						'<strong>',
180
+						'</strong>'
181
+					); ?>
182 182
                 </p>
183 183
                 <p>
184 184
                     <?php
185
-                    esc_html_e(
186
-                        'If you\'re doing event registration and ticketing any other way, then you’re wasting time and money. We offer packages and prices to fit any budget, so get started with your online event registration and ticketing management system today.',
187
-                        'event_espresso'
188
-                    ); ?>
185
+					esc_html_e(
186
+						'If you\'re doing event registration and ticketing any other way, then you’re wasting time and money. We offer packages and prices to fit any budget, so get started with your online event registration and ticketing management system today.',
187
+						'event_espresso'
188
+					); ?>
189 189
                 </p>
190 190
             </div>
191 191
         </div>
@@ -194,37 +194,37 @@  discard block
 block discarded – undo
194 194
             <div class="column col">
195 195
                 <h3>
196 196
                     <?php
197
-                    esc_html_e(
198
-                        'Turn your blog into a complete event registration and management system',
199
-                        'event_espresso'
200
-                    ); ?>
197
+					esc_html_e(
198
+						'Turn your blog into a complete event registration and management system',
199
+						'event_espresso'
200
+					); ?>
201 201
                 </h3>
202 202
                 <p>
203 203
                     <?php
204
-                    esc_html_e(
205
-                        'Create a beautiful event page with ticket selection, venue details, and an integrated single page checkout system. With WordPress, Event Espresso, and Espresso Arabica 2014 (based on the "Twenty Fourteen" theme by WordPress), your events will certainly sell out faster than ever!',
206
-                        'event_espresso'
207
-                    ); ?>
204
+					esc_html_e(
205
+						'Create a beautiful event page with ticket selection, venue details, and an integrated single page checkout system. With WordPress, Event Espresso, and Espresso Arabica 2014 (based on the "Twenty Fourteen" theme by WordPress), your events will certainly sell out faster than ever!',
206
+						'event_espresso'
207
+					); ?>
208 208
                 </p>
209 209
                 <p>
210 210
                     <?php
211
-                    esc_html_e(
212
-                        'With a striking design that does not compromise the simplicity of WordPress and Event Espresso 4, Espresso Arabica 2014 will be the best event theme on the market.',
213
-                        'event_espresso'
214
-                    ); ?>
211
+					esc_html_e(
212
+						'With a striking design that does not compromise the simplicity of WordPress and Event Espresso 4, Espresso Arabica 2014 will be the best event theme on the market.',
213
+						'event_espresso'
214
+					); ?>
215 215
                 </p>
216 216
                 <p>
217 217
                     <?php
218
-                    echo sprintf(
219
-                        esc_html__('%sLearn more >>%s', 'event_espresso'),
220
-                        '<a href="https://eventespresso.com/wiki/setup-event-espresso-arabica-theme/">',
221
-                        '</a>'
222
-                    ); ?>
218
+					echo sprintf(
219
+						esc_html__('%sLearn more >>%s', 'event_espresso'),
220
+						'<a href="https://eventespresso.com/wiki/setup-event-espresso-arabica-theme/">',
221
+						'</a>'
222
+					); ?>
223 223
                 </p>
224 224
             </div>
225 225
             <div class="column col">
226 226
                 <img src="<?php
227
-                echo EEH_Template::getScreenshotUrl('single-event-page'); ?>">
227
+				echo EEH_Template::getScreenshotUrl('single-event-page'); ?>">
228 228
             </div>
229 229
         </div>
230 230
 
@@ -232,19 +232,19 @@  discard block
 block discarded – undo
232 232
 
233 233
         <h2 class="about-headline-callout">
234 234
             <?php
235
-            esc_html_e('Pick a theme, any theme', 'event_espresso'); ?>
235
+			esc_html_e('Pick a theme, any theme', 'event_espresso'); ?>
236 236
         </h2>
237 237
         <div class="feature-section has-1-column is-fullwidth is-wide one-col">
238 238
             <div class="column col">
239 239
                 <p><?php
240
-                    esc_html_e(
241
-                        'We’ve made it super easy to integrate Event Espresso with almost any properly coded WordPress theme, including many of the thousands of themes available on WordPress.org. The image below shows the same Event Espresso ticketing page across three diffrent WordPress themes.',
242
-                        'event_espresso'
243
-                    ); ?></p>
240
+					esc_html_e(
241
+						'We’ve made it super easy to integrate Event Espresso with almost any properly coded WordPress theme, including many of the thousands of themes available on WordPress.org. The image below shows the same Event Espresso ticketing page across three diffrent WordPress themes.',
242
+						'event_espresso'
243
+					); ?></p>
244 244
                 <p>
245 245
                     <img class="about-overview-img"
246 246
                          src="<?php
247
-                         echo EEH_Template::getScreenshotUrl('multiple-themes'); ?>"
247
+						 echo EEH_Template::getScreenshotUrl('multiple-themes'); ?>"
248 248
                     />
249 249
                 </p>
250 250
             </div>
Please login to merge, or discard this patch.
caffeinated/admin/extend/support/Extend_Support_Admin_Page.core.php 1 patch
Indentation   +62 added lines, -62 removed lines patch added patch discarded remove patch
@@ -11,72 +11,72 @@
 block discarded – undo
11 11
  */
12 12
 class Extend_Support_Admin_Page extends Support_Admin_Page
13 13
 {
14
-    public function __construct($routing = true)
15
-    {
16
-        parent::__construct($routing);
17
-        define('EE_SUPPORT_CAF_ADMIN_TEMPLATE_PATH', EE_CORE_CAF_ADMIN_EXTEND . 'support/templates/');
18
-    }
14
+	public function __construct($routing = true)
15
+	{
16
+		parent::__construct($routing);
17
+		define('EE_SUPPORT_CAF_ADMIN_TEMPLATE_PATH', EE_CORE_CAF_ADMIN_EXTEND . 'support/templates/');
18
+	}
19 19
 
20 20
 
21
-    protected function _extend_page_config()
22
-    {
23
-        $this->_admin_base_path = EE_CORE_CAF_ADMIN_EXTEND . 'support';
24
-        // new routes and new configs (or overrides )
25
-        $new_page_routes = array(
26
-            'faq' => array(
27
-                'func'       => '_faq',
28
-                'capability' => 'ee_read_ee',
29
-            ),
30
-        );
31
-        $this->_page_routes = array_merge($this->_page_routes, $new_page_routes);
32
-        $new_page_config = array(
33
-            'faq' => array(
34
-                'nav'           => array(
35
-                    'label' => esc_html__('FAQ', 'event_espresso'),
36
-                    'icon' => 'dashicons-editor-help',
37
-                    'order' => 40,
38
-                ),
39
-                'metaboxes'     => array('_espresso_news_post_box', '_espresso_links_post_box'),
40
-                'require_nonce' => false,
41
-            ),
42
-        );
43
-        $this->_page_config = array_merge($this->_page_config, $new_page_config);
44
-        $this->_page_config['default']['metaboxes'][] = '_installation_boxes';
45
-    }
21
+	protected function _extend_page_config()
22
+	{
23
+		$this->_admin_base_path = EE_CORE_CAF_ADMIN_EXTEND . 'support';
24
+		// new routes and new configs (or overrides )
25
+		$new_page_routes = array(
26
+			'faq' => array(
27
+				'func'       => '_faq',
28
+				'capability' => 'ee_read_ee',
29
+			),
30
+		);
31
+		$this->_page_routes = array_merge($this->_page_routes, $new_page_routes);
32
+		$new_page_config = array(
33
+			'faq' => array(
34
+				'nav'           => array(
35
+					'label' => esc_html__('FAQ', 'event_espresso'),
36
+					'icon' => 'dashicons-editor-help',
37
+					'order' => 40,
38
+				),
39
+				'metaboxes'     => array('_espresso_news_post_box', '_espresso_links_post_box'),
40
+				'require_nonce' => false,
41
+			),
42
+		);
43
+		$this->_page_config = array_merge($this->_page_config, $new_page_config);
44
+		$this->_page_config['default']['metaboxes'][] = '_installation_boxes';
45
+	}
46 46
 
47 47
 
48
-    protected function _faq()
49
-    {
50
-        $template_path = EE_SUPPORT_CAF_ADMIN_TEMPLATE_PATH . 'support_admin_details_faq.template.php';
51
-        $this->_template_args['admin_page_content'] = EEH_Template::display_template(
52
-            $template_path,
53
-            '',
54
-            true
55
-        );
56
-        $this->display_admin_page_with_sidebar();
57
-    }
48
+	protected function _faq()
49
+	{
50
+		$template_path = EE_SUPPORT_CAF_ADMIN_TEMPLATE_PATH . 'support_admin_details_faq.template.php';
51
+		$this->_template_args['admin_page_content'] = EEH_Template::display_template(
52
+			$template_path,
53
+			'',
54
+			true
55
+		);
56
+		$this->display_admin_page_with_sidebar();
57
+	}
58 58
 
59 59
 
60
-    protected function _installation_boxes()
61
-    {
62
-        $callback_args = array(
63
-            'template_path' => EE_SUPPORT_CAF_ADMIN_TEMPLATE_PATH
64
-                               . 'support_admin_details_additional_information.template.php',
65
-        );
66
-        $this->addMetaBox(
67
-            'espresso_additional_information_support',
68
-            esc_html__('Additional Information', 'event_espresso'),
69
-            function ($post, $metabox) {
70
-                echo EEH_Template::display_template(
71
-                    $metabox['args']['template_path'],
72
-                    '',
73
-                    true
74
-                );
75
-            },
76
-            $this->_current_screen->id,
77
-            'normal',
78
-            'high',
79
-            $callback_args
80
-        );
81
-    }
60
+	protected function _installation_boxes()
61
+	{
62
+		$callback_args = array(
63
+			'template_path' => EE_SUPPORT_CAF_ADMIN_TEMPLATE_PATH
64
+							   . 'support_admin_details_additional_information.template.php',
65
+		);
66
+		$this->addMetaBox(
67
+			'espresso_additional_information_support',
68
+			esc_html__('Additional Information', 'event_espresso'),
69
+			function ($post, $metabox) {
70
+				echo EEH_Template::display_template(
71
+					$metabox['args']['template_path'],
72
+					'',
73
+					true
74
+				);
75
+			},
76
+			$this->_current_screen->id,
77
+			'normal',
78
+			'high',
79
+			$callback_args
80
+		);
81
+	}
82 82
 }
Please login to merge, or discard this patch.
caffeinated/admin/new/pricing/Pricing_Admin_Page.core.php 1 patch
Indentation   +1278 added lines, -1278 removed lines patch added patch discarded remove patch
@@ -11,1289 +11,1289 @@
 block discarded – undo
11 11
  */
12 12
 class Pricing_Admin_Page extends EE_Admin_Page
13 13
 {
14
-    protected function _init_page_props()
15
-    {
16
-        $this->page_slug        = PRICING_PG_SLUG;
17
-        $this->page_label       = PRICING_LABEL;
18
-        $this->_admin_base_url  = PRICING_ADMIN_URL;
19
-        $this->_admin_base_path = PRICING_ADMIN;
20
-    }
21
-
22
-
23
-    protected function _ajax_hooks()
24
-    {
25
-        add_action('wp_ajax_espresso_update_prices_order', [$this, 'update_price_order']);
26
-    }
27
-
28
-
29
-    protected function _define_page_props()
30
-    {
31
-        $this->_admin_page_title = PRICING_LABEL;
32
-        $this->_labels           = [
33
-            'buttons' => [
34
-                'add'         => esc_html__('Add New Default Price', 'event_espresso'),
35
-                'edit'        => esc_html__('Edit Default Price', 'event_espresso'),
36
-                'delete'      => esc_html__('Delete Default Price', 'event_espresso'),
37
-                'add_type'    => esc_html__('Add New Default Price Type', 'event_espresso'),
38
-                'edit_type'   => esc_html__('Edit Price Type', 'event_espresso'),
39
-                'delete_type' => esc_html__('Delete Price Type', 'event_espresso'),
40
-            ],
41
-        ];
42
-    }
43
-
44
-
45
-    /**
46
-     * an array for storing request actions and their corresponding methods
47
-     *
48
-     * @return void
49
-     */
50
-    protected function _set_page_routes()
51
-    {
52
-        $PRC_ID             = $this->request->getRequestParam('PRC_ID', 0, DataType::INTEGER);
53
-        $PRT_ID             = $this->request->getRequestParam('PRT_ID', 0, DataType::INTEGER);
54
-        $this->_page_routes = [
55
-            'default'                     => [
56
-                'func'       => '_price_overview_list_table',
57
-                'capability' => 'ee_read_default_prices',
58
-            ],
59
-            'add_new_price'               => [
60
-                'func'       => '_edit_price_details',
61
-                // 'args'       => ['new_price' => true],
62
-                'capability' => 'ee_edit_default_prices',
63
-            ],
64
-            'edit_price'                  => [
65
-                'func'       => '_edit_price_details',
66
-                // 'args'       => ['new_price' => false],
67
-                'capability' => 'ee_edit_default_price',
68
-                'obj_id'     => $PRC_ID,
69
-            ],
70
-            'insert_price'                => [
71
-                'func'       => '_insert_or_update_price',
72
-                'args'       => ['insert' => true],
73
-                'noheader'   => true,
74
-                'capability' => 'ee_edit_default_prices',
75
-            ],
76
-            'update_price'                => [
77
-                'func'       => '_insert_or_update_price',
78
-                'args'       => ['insert' => false],
79
-                'noheader'   => true,
80
-                'capability' => 'ee_edit_default_price',
81
-                'obj_id'     => $PRC_ID,
82
-            ],
83
-            'trash_price'                 => [
84
-                'func'       => '_trash_or_restore_price',
85
-                'args'       => ['trash' => true],
86
-                'noheader'   => true,
87
-                'capability' => 'ee_delete_default_price',
88
-                'obj_id'     => $PRC_ID,
89
-            ],
90
-            'restore_price'               => [
91
-                'func'       => '_trash_or_restore_price',
92
-                'args'       => ['trash' => false],
93
-                'noheader'   => true,
94
-                'capability' => 'ee_delete_default_price',
95
-                'obj_id'     => $PRC_ID,
96
-            ],
97
-            'delete_price'                => [
98
-                'func'       => '_delete_price',
99
-                'noheader'   => true,
100
-                'capability' => 'ee_delete_default_price',
101
-                'obj_id'     => $PRC_ID,
102
-            ],
103
-            'espresso_update_price_order' => [
104
-                'func'       => 'update_price_order',
105
-                'noheader'   => true,
106
-                'capability' => 'ee_edit_default_prices',
107
-            ],
108
-            // price types
109
-            'price_types'                 => [
110
-                'func'       => '_price_types_overview_list_table',
111
-                'capability' => 'ee_read_default_price_types',
112
-            ],
113
-            'add_new_price_type'          => [
114
-                'func'       => '_edit_price_type_details',
115
-                'capability' => 'ee_edit_default_price_types',
116
-            ],
117
-            'edit_price_type'             => [
118
-                'func'       => '_edit_price_type_details',
119
-                'capability' => 'ee_edit_default_price_type',
120
-                'obj_id'     => $PRT_ID,
121
-            ],
122
-            'insert_price_type'           => [
123
-                'func'       => '_insert_or_update_price_type',
124
-                'args'       => ['new_price_type' => true],
125
-                'noheader'   => true,
126
-                'capability' => 'ee_edit_default_price_types',
127
-            ],
128
-            'update_price_type'           => [
129
-                'func'       => '_insert_or_update_price_type',
130
-                'args'       => ['new_price_type' => false],
131
-                'noheader'   => true,
132
-                'capability' => 'ee_edit_default_price_type',
133
-                'obj_id'     => $PRT_ID,
134
-            ],
135
-            'trash_price_type'            => [
136
-                'func'       => '_trash_or_restore_price_type',
137
-                'args'       => ['trash' => true],
138
-                'noheader'   => true,
139
-                'capability' => 'ee_delete_default_price_type',
140
-                'obj_id'     => $PRT_ID,
141
-            ],
142
-            'restore_price_type'          => [
143
-                'func'       => '_trash_or_restore_price_type',
144
-                'args'       => ['trash' => false],
145
-                'noheader'   => true,
146
-                'capability' => 'ee_delete_default_price_type',
147
-                'obj_id'     => $PRT_ID,
148
-            ],
149
-            'delete_price_type'           => [
150
-                'func'       => '_delete_price_type',
151
-                'noheader'   => true,
152
-                'capability' => 'ee_delete_default_price_type',
153
-                'obj_id'     => $PRT_ID,
154
-            ],
155
-            'tax_settings'                => [
156
-                'func'       => '_tax_settings',
157
-                'capability' => 'manage_options',
158
-            ],
159
-            'update_tax_settings'         => [
160
-                'func'       => '_update_tax_settings',
161
-                'capability' => 'manage_options',
162
-                'noheader'   => true,
163
-            ],
164
-        ];
165
-    }
166
-
167
-
168
-    protected function _set_page_config()
169
-    {
170
-        $PRC_ID             = $this->request->getRequestParam('id', 0, DataType::INTEGER);
171
-        $this->_page_config = [
172
-            'default'            => [
173
-                'nav'           => [
174
-                    'label' => esc_html__('Default Pricing', 'event_espresso'),
175
-                    'icon' => 'dashicons-money-alt',
176
-                    'order' => 10,
177
-                ],
178
-                'list_table'    => 'Prices_List_Table',
179
-                'metaboxes'     => $this->_default_espresso_metaboxes,
180
-                'help_tabs'     => [
181
-                    'pricing_default_pricing_help_tab'                           => [
182
-                        'title'    => esc_html__('Default Pricing', 'event_espresso'),
183
-                        'filename' => 'pricing_default_pricing',
184
-                    ],
185
-                    'pricing_default_pricing_table_column_headings_help_tab'     => [
186
-                        'title'    => esc_html__('Default Pricing Table Column Headings', 'event_espresso'),
187
-                        'filename' => 'pricing_default_pricing_table_column_headings',
188
-                    ],
189
-                    'pricing_default_pricing_views_bulk_actions_search_help_tab' => [
190
-                        'title'    => esc_html__('Default Pricing Views & Bulk Actions & Search', 'event_espresso'),
191
-                        'filename' => 'pricing_default_pricing_views_bulk_actions_search',
192
-                    ],
193
-                ],
194
-                'require_nonce' => false,
195
-            ],
196
-            'add_new_price'      => [
197
-                'nav'           => [
198
-                    'label'      => esc_html__('Add New Default Price', 'event_espresso'),
199
-                    'icon' => 'dashicons-plus-alt',
200
-                    'order'      => 20,
201
-                    'persistent' => false,
202
-                ],
203
-                'help_tabs'     => [
204
-                    'add_new_default_price_help_tab' => [
205
-                        'title'    => esc_html__('Add New Default Price', 'event_espresso'),
206
-                        'filename' => 'pricing_add_new_default_price',
207
-                    ],
208
-                ],
209
-                'metaboxes'     => array_merge(
210
-                    ['_publish_post_box'],
211
-                    $this->_default_espresso_metaboxes
212
-                ),
213
-                'require_nonce' => false,
214
-            ],
215
-            'edit_price'         => [
216
-                'nav'           => [
217
-                    'label'      => esc_html__('Edit Default Price', 'event_espresso'),
218
-                    'icon' => 'dashicons-edit-large',
219
-                    'order'      => 20,
220
-                    'url'        => $PRC_ID
221
-                        ? add_query_arg(['id' => $PRC_ID], $this->_current_page_view_url)
222
-                        : $this->_admin_base_url,
223
-                    'persistent' => false,
224
-                ],
225
-                'metaboxes'     => array_merge(
226
-                    ['_publish_post_box'],
227
-                    $this->_default_espresso_metaboxes
228
-                ),
229
-                'help_tabs'     => [
230
-                    'edit_default_price_help_tab' => [
231
-                        'title'    => esc_html__('Edit Default Price', 'event_espresso'),
232
-                        'filename' => 'pricing_edit_default_price',
233
-                    ],
234
-                ],
235
-                'require_nonce' => false,
236
-            ],
237
-            'price_types'        => [
238
-                'nav'           => [
239
-                    'label' => esc_html__('Price Types', 'event_espresso'),
240
-                    'icon' => 'dashicons-networking',
241
-                    'order' => 30,
242
-                ],
243
-                'list_table'    => 'Price_Types_List_Table',
244
-                'help_tabs'     => [
245
-                    'pricing_price_types_help_tab'                           => [
246
-                        'title'    => esc_html__('Price Types', 'event_espresso'),
247
-                        'filename' => 'pricing_price_types',
248
-                    ],
249
-                    'pricing_price_types_table_column_headings_help_tab'     => [
250
-                        'title'    => esc_html__('Price Types Table Column Headings', 'event_espresso'),
251
-                        'filename' => 'pricing_price_types_table_column_headings',
252
-                    ],
253
-                    'pricing_price_types_views_bulk_actions_search_help_tab' => [
254
-                        'title'    => esc_html__('Price Types Views & Bulk Actions & Search', 'event_espresso'),
255
-                        'filename' => 'pricing_price_types_views_bulk_actions_search',
256
-                    ],
257
-                ],
258
-                'metaboxes'     => $this->_default_espresso_metaboxes,
259
-                'require_nonce' => false,
260
-            ],
261
-            'add_new_price_type' => [
262
-                'nav'           => [
263
-                    'label'      => esc_html__('Add New Price Type', 'event_espresso'),
264
-                    'icon' => 'dashicons-plus-alt',
265
-                    'order'      => 40,
266
-                    'persistent' => false,
267
-                ],
268
-                'help_tabs'     => [
269
-                    'add_new_price_type_help_tab' => [
270
-                        'title'    => esc_html__('Add New Price Type', 'event_espresso'),
271
-                        'filename' => 'pricing_add_new_price_type',
272
-                    ],
273
-                ],
274
-                'metaboxes'     => array_merge(
275
-                    ['_publish_post_box'],
276
-                    $this->_default_espresso_metaboxes
277
-                ),
278
-                'require_nonce' => false,
279
-            ],
280
-            'edit_price_type'    => [
281
-                'nav'           => [
282
-                    'label'      => esc_html__('Edit Price Type', 'event_espresso'),
283
-                    'icon' => 'dashicons-edit-large',
284
-                    'order'      => 40,
285
-                    'persistent' => false,
286
-                ],
287
-                'help_tabs'     => [
288
-                    'edit_price_type_help_tab' => [
289
-                        'title'    => esc_html__('Edit Price Type', 'event_espresso'),
290
-                        'filename' => 'pricing_edit_price_type',
291
-                    ],
292
-                ],
293
-                'metaboxes'     => array_merge(
294
-                    ['_publish_post_box'],
295
-                    $this->_default_espresso_metaboxes
296
-                ),
297
-                'require_nonce' => false,
298
-            ],
299
-            'tax_settings'       => [
300
-                'nav' => [
301
-                    'label' => esc_html__('Tax Settings', 'event_espresso'),
302
-                    'icon' => 'dashicons-sticky',
303
-                    'order' => 50,
304
-                ],
305
-                'labels'        => [
306
-                    'publishbox' => esc_html__('Update Tax Settings', 'event_espresso'),
307
-                ],
308
-                'metaboxes'     => array_merge(
309
-                    ['_publish_post_box'],
310
-                    $this->_default_espresso_metaboxes
311
-                ),
312
-                'require_nonce' => true,
313
-            ],
314
-        ];
315
-    }
316
-
317
-
318
-    protected function _add_screen_options()
319
-    {
320
-        // todo
321
-    }
322
-
323
-
324
-    protected function _add_screen_options_default()
325
-    {
326
-        $this->_per_page_screen_option();
327
-    }
328
-
329
-
330
-    protected function _add_screen_options_price_types()
331
-    {
332
-        $page_title              = $this->_admin_page_title;
333
-        $this->_admin_page_title = esc_html__('Price Types', 'event_espresso');
334
-        $this->_per_page_screen_option();
335
-        $this->_admin_page_title = $page_title;
336
-    }
337
-
338
-
339
-    protected function _add_feature_pointers()
340
-    {
341
-    }
342
-
343
-
344
-    public function load_scripts_styles()
345
-    {
346
-        // styles
347
-        wp_enqueue_style('espresso-ui-theme');
348
-        wp_register_style(
349
-            'espresso_PRICING',
350
-            PRICING_ASSETS_URL . 'espresso_pricing_admin.css',
351
-            [],
352
-            EVENT_ESPRESSO_VERSION
353
-        );
354
-        wp_enqueue_style('espresso_PRICING');
355
-
356
-        // scripts
357
-        wp_enqueue_script('ee_admin_js');
358
-        wp_enqueue_script('jquery-ui-position');
359
-        wp_enqueue_script('jquery-ui-widget');
360
-        wp_register_script(
361
-            'espresso_PRICING',
362
-            PRICING_ASSETS_URL . 'espresso_pricing_admin.js',
363
-            ['jquery'],
364
-            EVENT_ESPRESSO_VERSION,
365
-            true
366
-        );
367
-        wp_enqueue_script('espresso_PRICING');
368
-    }
369
-
370
-
371
-    public function load_scripts_styles_default()
372
-    {
373
-        wp_enqueue_script('espresso_ajax_table_sorting');
374
-    }
375
-
376
-
377
-    public function admin_footer_scripts()
378
-    {
379
-    }
380
-
381
-
382
-    public function admin_init()
383
-    {
384
-    }
385
-
386
-
387
-    public function admin_notices()
388
-    {
389
-    }
390
-
391
-
392
-    protected function _set_list_table_views_default()
393
-    {
394
-        $this->_views = [
395
-            'all' => [
396
-                'slug'        => 'all',
397
-                'label'       => esc_html__('View All Default Pricing', 'event_espresso'),
398
-                'count'       => 0,
399
-                'bulk_action' => [
400
-                    'trash_price' => esc_html__('Move to Trash', 'event_espresso'),
401
-                ],
402
-            ],
403
-        ];
404
-
405
-        if (EE_Registry::instance()->CAP->current_user_can('ee_delete_default_prices', 'pricing_trash_price')) {
406
-            $this->_views['trashed'] = [
407
-                'slug'        => 'trashed',
408
-                'label'       => esc_html__('Trash', 'event_espresso'),
409
-                'count'       => 0,
410
-                'bulk_action' => [
411
-                    'restore_price' => esc_html__('Restore from Trash', 'event_espresso'),
412
-                    'delete_price'  => esc_html__('Delete Permanently', 'event_espresso'),
413
-                ],
414
-            ];
415
-        }
416
-    }
417
-
418
-
419
-    protected function _set_list_table_views_price_types()
420
-    {
421
-        $this->_views = [
422
-            'all' => [
423
-                'slug'        => 'all',
424
-                'label'       => esc_html__('All', 'event_espresso'),
425
-                'count'       => 0,
426
-                'bulk_action' => [
427
-                    'trash_price_type' => esc_html__('Move to Trash', 'event_espresso'),
428
-                ],
429
-            ],
430
-        ];
431
-
432
-        if (
433
-            EE_Registry::instance()->CAP->current_user_can(
434
-                'ee_delete_default_price_types',
435
-                'pricing_trash_price_type'
436
-            )
437
-        ) {
438
-            $this->_views['trashed'] = [
439
-                'slug'        => 'trashed',
440
-                'label'       => esc_html__('Trash', 'event_espresso'),
441
-                'count'       => 0,
442
-                'bulk_action' => [
443
-                    'restore_price_type' => esc_html__('Restore from Trash', 'event_espresso'),
444
-                    'delete_price_type'  => esc_html__('Delete Permanently', 'event_espresso'),
445
-                ],
446
-            ];
447
-        }
448
-    }
449
-
450
-
451
-    /**
452
-     * generates HTML for main Prices Admin page
453
-     *
454
-     * @return void
455
-     * @throws EE_Error
456
-     */
457
-    protected function _price_overview_list_table()
458
-    {
459
-        $this->_admin_page_title .= ' ' . $this->get_action_link_or_button(
460
-            'add_new_price',
461
-            'add',
462
-            [],
463
-            'add-new-h2'
464
-        );
465
-        $this->_admin_page_title .= $this->_learn_more_about_pricing_link();
466
-        $this->_search_btn_label = esc_html__('Default Prices', 'event_espresso');
467
-        $this->display_admin_list_table_page_with_sidebar();
468
-    }
469
-
470
-
471
-    /**
472
-     * retrieve data for Prices List table
473
-     *
474
-     * @param int  $per_page how many prices displayed per page
475
-     * @param bool $count    return the count or objects
476
-     * @param bool $trashed  whether the current view is of the trash can - eww yuck!
477
-     * @return EE_Soft_Delete_Base_Class[]|int int = count || array of price objects
478
-     * @throws EE_Error
479
-     * @throws ReflectionException
480
-     */
481
-    public function get_prices_overview_data(int $per_page = 10, bool $count = false, bool $trashed = false)
482
-    {
483
-        // start with an empty array
484
-        $event_pricing = [];
485
-
486
-        require_once(PRICING_ADMIN . 'Prices_List_Table.class.php');
487
-
488
-        $orderby = $this->request->getRequestParam('orderby', '');
489
-        $order   = $this->request->getRequestParam('order', 'ASC');
490
-
491
-        switch ($orderby) {
492
-            case 'name':
493
-                $orderby = ['PRC_name' => $order];
494
-                break;
495
-            case 'type':
496
-                $orderby = ['Price_Type.PRT_name' => $order];
497
-                break;
498
-            case 'amount':
499
-                $orderby = ['PRC_amount' => $order];
500
-                break;
501
-            default:
502
-                $orderby = ['PRC_order' => $order, 'Price_Type.PRT_order' => $order, 'PRC_ID' => $order];
503
-        }
504
-
505
-        $current_page = $this->request->getRequestParam('paged', 1, DataType::INTEGER);
506
-        $per_page     = $this->request->getRequestParam('perpage', $per_page, DataType::INTEGER);
507
-
508
-        $where = [
509
-            'PRC_is_default' => 1,
510
-            'PRC_deleted'    => $trashed,
511
-        ];
512
-
513
-        $offset = ($current_page - 1) * $per_page;
514
-        $limit  = [$offset, $per_page];
515
-
516
-        $search_term = $this->request->getRequestParam('s');
517
-        if ($search_term) {
518
-            $search_term = "%{$search_term}%";
519
-            $where['OR'] = [
520
-                'PRC_name'            => ['LIKE', $search_term],
521
-                'PRC_desc'            => ['LIKE', $search_term],
522
-                'PRC_amount'          => ['LIKE', $search_term],
523
-                'Price_Type.PRT_name' => ['LIKE', $search_term],
524
-            ];
525
-        }
526
-
527
-        $query_params = [
528
-            $where,
529
-            'order_by' => $orderby,
530
-            'limit'    => $limit,
531
-            'group_by' => 'PRC_ID',
532
-        ];
533
-
534
-        if ($count) {
535
-            return $trashed
536
-                ? EEM_Price::instance()->count([$where])
537
-                : EEM_Price::instance()->count_deleted_and_undeleted([$where]);
538
-        }
539
-        return EEM_Price::instance()->get_all_deleted_and_undeleted($query_params);
540
-    }
541
-
542
-
543
-    /**
544
-     * @return void
545
-     * @throws EE_Error
546
-     * @throws ReflectionException
547
-     */
548
-    protected function _edit_price_details()
549
-    {
550
-        // grab price ID
551
-        $PRC_ID = $this->request->getRequestParam('id', 0, DataType::INTEGER);
552
-        // change page title based on request action
553
-        switch ($this->_req_action) {
554
-            case 'add_new_price':
555
-                $this->_admin_page_title = esc_html__('Add New Price', 'event_espresso');
556
-                break;
557
-            case 'edit_price':
558
-                $this->_admin_page_title = esc_html__('Edit Price', 'event_espresso');
559
-                break;
560
-            default:
561
-                $this->_admin_page_title = ucwords(str_replace('_', ' ', $this->_req_action));
562
-        }
563
-        // add PRC_ID to title if editing
564
-        $this->_admin_page_title = $PRC_ID ? $this->_admin_page_title . ' # ' . $PRC_ID : $this->_admin_page_title;
565
-
566
-        if ($PRC_ID) {
567
-            $price                    = EEM_Price::instance()->get_one_by_ID($PRC_ID);
568
-            $additional_hidden_fields = [
569
-                'PRC_ID' => ['type' => 'hidden', 'value' => $PRC_ID],
570
-            ];
571
-            $this->_set_add_edit_form_tags('update_price', $additional_hidden_fields);
572
-        } else {
573
-            $price = EEM_Price::instance()->get_new_price();
574
-            $this->_set_add_edit_form_tags('insert_price');
575
-        }
576
-
577
-        if (! $price instanceof EE_Price) {
578
-            throw new RuntimeException(
579
-                sprintf(
580
-                    esc_html__(
581
-                        'A valid Price could not be retrieved from the database with ID: %1$s',
582
-                        'event_espresso'
583
-                    ),
584
-                    $PRC_ID
585
-                )
586
-            );
587
-        }
588
-
589
-        $this->_template_args['PRC_ID'] = $PRC_ID;
590
-        $this->_template_args['price']  = $price;
591
-
592
-        $default_base_price = $price->type_obj() && $price->type_obj()->base_type() === 1;
593
-
594
-        $this->_template_args['default_base_price'] = $default_base_price;
595
-
596
-        // get price types
597
-        $price_types = EEM_Price_Type::instance()->get_all([['PBT_ID' => ['!=', 1]]]);
598
-        if (empty($price_types)) {
599
-            $msg = esc_html__(
600
-                'You have no price types defined. Please add a price type before adding a price.',
601
-                'event_espresso'
602
-            );
603
-            EE_Error::add_error($msg, __FILE__, __FUNCTION__, __LINE__);
604
-            $this->display_admin_page_with_sidebar();
605
-        }
606
-        $attributes       = [];
607
-        $price_type_names = [];
608
-        $attributes[]     = 'id="PRT_ID"';
609
-        if ($default_base_price) {
610
-            $attributes[]       = 'disabled="disabled"';
611
-            $price_type_names[] = ['id' => 1, 'text' => esc_html__('Base Price', 'event_espresso')];
612
-        }
613
-        foreach ($price_types as $type) {
614
-            $price_type_names[] = ['id' => $type->ID(), 'text' => $type->name()];
615
-        }
616
-        $this->_template_args['attributes']  = implode(' ', $attributes);
617
-        $this->_template_args['price_types'] = $price_type_names;
618
-
619
-        $this->_template_args['learn_more_about_pricing_link'] = $this->_learn_more_about_pricing_link();
620
-        $this->_template_args['admin_page_content']            = $this->_edit_price_details_meta_box();
621
-
622
-        $this->_set_publish_post_box_vars('id', $PRC_ID);
623
-        // the details template wrapper
624
-        $this->display_admin_page_with_sidebar();
625
-    }
626
-
627
-
628
-    /**
629
-     *
630
-     * @return string
631
-     */
632
-    public function _edit_price_details_meta_box(): string
633
-    {
634
-        return EEH_Template::display_template(
635
-            PRICING_TEMPLATE_PATH . 'pricing_details_main_meta_box.template.php',
636
-            $this->_template_args,
637
-            true
638
-        );
639
-    }
640
-
641
-
642
-    /**
643
-     * @return array
644
-     * @throws EE_Error
645
-     * @throws ReflectionException
646
-     */
647
-    protected function set_price_column_values(): array
648
-    {
649
-        $PRC_order = 0;
650
-        $PRT_ID    = $this->request->getRequestParam('PRT_ID', 0, DataType::INTEGER);
651
-        if ($PRT_ID) {
652
-            /** @var EE_Price_Type $price_type */
653
-            $price_type = EEM_Price_Type::instance()->get_one_by_ID($PRT_ID);
654
-            if ($price_type instanceof EE_Price_Type) {
655
-                $PRC_order = $price_type->order();
656
-            }
657
-        }
658
-        return [
659
-            'PRT_ID'         => $PRT_ID,
660
-            'PRC_amount'     => $this->request->getRequestParam('PRC_amount', 0, DataType::FLOAT),
661
-            'PRC_name'       => $this->request->getRequestParam('PRC_name'),
662
-            'PRC_desc'       => $this->request->getRequestParam('PRC_desc'),
663
-            'PRC_is_default' => 1,
664
-            'PRC_overrides'  => null,
665
-            'PRC_order'      => $PRC_order,
666
-            'PRC_deleted'    => 0,
667
-            'PRC_parent'     => 0,
668
-        ];
669
-    }
670
-
671
-
672
-    /**
673
-     * @param bool $insert - whether to insert or update
674
-     * @return void
675
-     * @throws EE_Error
676
-     * @throws ReflectionException
677
-     */
678
-    protected function _insert_or_update_price(bool $insert = false)
679
-    {
680
-        // why be so pessimistic ???  : (
681
-        $updated = 0;
682
-
683
-        $set_column_values = $this->set_price_column_values();
684
-        // is this a new Price ?
685
-        if ($insert) {
686
-            // run the insert
687
-            $PRC_ID = EEM_Price::instance()->insert($set_column_values);
688
-            if ($PRC_ID) {
689
-                // make sure this new price modifier is attached to the ticket but ONLY if it is not a tax type
690
-                $price = EEM_price::instance()->get_one_by_ID($PRC_ID);
691
-                if (
692
-                    $price instanceof EE_Price
693
-                    && $price->type_obj() instanceof EE_Price_type
694
-                    && $price->type_obj()->base_type() !== EEM_Price_Type::base_type_tax
695
-                ) {
696
-                    $ticket = EEM_Ticket::instance()->get_one_by_ID(1);
697
-                    $ticket->_add_relation_to($price, 'Price');
698
-                    $ticket->save();
699
-                }
700
-                $updated = 1;
701
-            }
702
-            $action_desc = 'created';
703
-        } else {
704
-            $PRC_ID = $this->request->getRequestParam('PRC_ID', 0, DataType::INTEGER);
705
-            // run the update
706
-            $where_cols_n_values = ['PRC_ID' => $PRC_ID];
707
-            $updated             = EEM_Price::instance()->update($set_column_values, [$where_cols_n_values]);
708
-
709
-            $price = EEM_Price::instance()->get_one_by_ID($PRC_ID);
710
-            if ($price instanceof EE_Price && $price->type_obj()->base_type() !== EEM_Price_Type::base_type_tax) {
711
-                // if this is $PRC_ID == 1,
712
-                // then we need to update the default ticket attached to this price so the TKT_price value is updated.
713
-                if ($PRC_ID === 1) {
714
-                    $ticket = $price->get_first_related('Ticket');
715
-                    if ($ticket) {
716
-                        $ticket->set('TKT_price', $price->get('PRC_amount'));
717
-                        $ticket->set('TKT_name', $price->get('PRC_name'));
718
-                        $ticket->set('TKT_description', $price->get('PRC_desc'));
719
-                        $ticket->save();
720
-                    }
721
-                } else {
722
-                    // we make sure this price is attached to base ticket. but ONLY if its not a tax ticket type.
723
-                    $ticket = EEM_Ticket::instance()->get_one_by_ID(1);
724
-                    $ticket->_add_relation_to($PRC_ID, 'Price');
725
-                    $ticket->save();
726
-                }
727
-            }
728
-
729
-            $action_desc = 'updated';
730
-        }
731
-
732
-        $query_args = ['action' => 'edit_price', 'id' => $PRC_ID];
733
-
734
-        $this->_redirect_after_action($updated, 'Prices', $action_desc, $query_args);
735
-    }
736
-
737
-
738
-    /**
739
-     * @param bool $trash - whether to move item to trash (TRUE) or restore it (FALSE)
740
-     * @return void
741
-     * @throws EE_Error
742
-     * @throws ReflectionException
743
-     */
744
-    protected function _trash_or_restore_price(bool $trash = true)
745
-    {
746
-        $entity_model = EEM_Price::instance();
747
-        $action       = $trash ? EE_Admin_List_Table::ACTION_TRASH : EE_Admin_List_Table::ACTION_RESTORE;
748
-        $result       = $this->trashRestoreDeleteEntities(
749
-            $entity_model,
750
-            'id',
751
-            $action,
752
-            'PRC_deleted',
753
-            [$this, 'adjustTicketRelations']
754
-        );
755
-
756
-        if ($result) {
757
-            $msg = $trash
758
-                ? esc_html(
759
-                    _n(
760
-                        'The Price has been trashed',
761
-                        'The Prices have been trashed',
762
-                        $result,
763
-                        'event_espresso'
764
-                    )
765
-                )
766
-                : esc_html(
767
-                    _n(
768
-                        'The Price has been restored',
769
-                        'The Prices have been restored',
770
-                        $result,
771
-                        'event_espresso'
772
-                    )
773
-                );
774
-            EE_Error::add_success($msg);
775
-        }
776
-
777
-        $this->_redirect_after_action(
778
-            $result,
779
-            _n('Price', 'Prices', $result, 'event_espresso'),
780
-            $trash ? 'trashed' : 'restored',
781
-            ['action' => 'default'],
782
-            true
783
-        );
784
-    }
785
-
786
-
787
-    /**
788
-     * @param EEM_Base   $entity_model
789
-     * @param int|string $entity_ID
790
-     * @param string     $action
791
-     * @param int        $result
792
-     * @throws EE_Error
793
-     * @throws ReflectionException
794
-     * @since 4.10.30.p
795
-     */
796
-    public function adjustTicketRelations(
797
-        EEM_Base $entity_model,
798
-        $entity_ID,
799
-        string $action,
800
-        int $result
801
-    ) {
802
-        if (! $entity_ID || (float) $result < 1) {
803
-            return;
804
-        }
805
-
806
-        $entity = $entity_model->get_one_by_ID($entity_ID);
807
-        if (! $entity instanceof EE_Price || $entity->type_obj()->base_type() === EEM_Price_Type::base_type_tax) {
808
-            return;
809
-        }
810
-
811
-        // get default tickets for updating
812
-        $default_tickets = EEM_Ticket::instance()->get_all_default_tickets();
813
-        foreach ($default_tickets as $default_ticket) {
814
-            if (! $default_ticket instanceof EE_Ticket) {
815
-                continue;
816
-            }
817
-            switch ($action) {
818
-                case EE_Admin_List_Table::ACTION_DELETE:
819
-                case EE_Admin_List_Table::ACTION_TRASH:
820
-                    // if trashing then remove relations to base default ticket.
821
-                    $default_ticket->_remove_relation_to($entity_ID, 'Price');
822
-                    break;
823
-                case EE_Admin_List_Table::ACTION_RESTORE:
824
-                    // if restoring then add back to base default ticket
825
-                    $default_ticket->_add_relation_to($entity_ID, 'Price');
826
-                    break;
827
-            }
828
-            $default_ticket->save();
829
-        }
830
-    }
831
-
832
-
833
-    /**
834
-     * @return void
835
-     * @throws EE_Error
836
-     * @throws ReflectionException
837
-     */
838
-    protected function _delete_price()
839
-    {
840
-        $entity_model = EEM_Price::instance();
841
-        $deleted      = $this->trashRestoreDeleteEntities($entity_model, 'id');
842
-        $entity       = $entity_model->item_name($deleted);
843
-        $this->_redirect_after_action(
844
-            $deleted,
845
-            $entity,
846
-            'deleted',
847
-            ['action' => 'default']
848
-        );
849
-    }
850
-
851
-
852
-    /**
853
-     * @throws EE_Error
854
-     * @throws ReflectionException
855
-     */
856
-    public function update_price_order()
857
-    {
858
-        // grab our row IDs
859
-        $row_ids = $this->request->getRequestParam('row_ids', '');
860
-        $row_ids = explode(',', rtrim($row_ids, ','));
861
-
862
-        $all_updated = true;
863
-        foreach ($row_ids as $i => $row_id) {
864
-            // Update the prices when re-ordering
865
-            $fields_n_values = ['PRC_order' => $i + 1];
866
-            $query_params    = [['PRC_ID' => absint($row_id)]];
867
-            // any failure will toggle $all_updated to false
868
-            $all_updated = $row_id && EEM_Price::instance()->update($fields_n_values, $query_params) !== false
869
-                ? $all_updated
870
-                : false;
871
-        }
872
-        $success = $all_updated
873
-            ? esc_html__('Price order was updated successfully.', 'event_espresso')
874
-            : false;
875
-        $errors  = ! $all_updated
876
-            ? esc_html__('An error occurred. The price order was not updated.', 'event_espresso')
877
-            : false;
878
-
879
-        echo wp_json_encode(['return_data' => false, 'success' => $success, 'errors' => $errors]);
880
-        die();
881
-    }
882
-
883
-
884
-    /******************************************************************************************************************
14
+	protected function _init_page_props()
15
+	{
16
+		$this->page_slug        = PRICING_PG_SLUG;
17
+		$this->page_label       = PRICING_LABEL;
18
+		$this->_admin_base_url  = PRICING_ADMIN_URL;
19
+		$this->_admin_base_path = PRICING_ADMIN;
20
+	}
21
+
22
+
23
+	protected function _ajax_hooks()
24
+	{
25
+		add_action('wp_ajax_espresso_update_prices_order', [$this, 'update_price_order']);
26
+	}
27
+
28
+
29
+	protected function _define_page_props()
30
+	{
31
+		$this->_admin_page_title = PRICING_LABEL;
32
+		$this->_labels           = [
33
+			'buttons' => [
34
+				'add'         => esc_html__('Add New Default Price', 'event_espresso'),
35
+				'edit'        => esc_html__('Edit Default Price', 'event_espresso'),
36
+				'delete'      => esc_html__('Delete Default Price', 'event_espresso'),
37
+				'add_type'    => esc_html__('Add New Default Price Type', 'event_espresso'),
38
+				'edit_type'   => esc_html__('Edit Price Type', 'event_espresso'),
39
+				'delete_type' => esc_html__('Delete Price Type', 'event_espresso'),
40
+			],
41
+		];
42
+	}
43
+
44
+
45
+	/**
46
+	 * an array for storing request actions and their corresponding methods
47
+	 *
48
+	 * @return void
49
+	 */
50
+	protected function _set_page_routes()
51
+	{
52
+		$PRC_ID             = $this->request->getRequestParam('PRC_ID', 0, DataType::INTEGER);
53
+		$PRT_ID             = $this->request->getRequestParam('PRT_ID', 0, DataType::INTEGER);
54
+		$this->_page_routes = [
55
+			'default'                     => [
56
+				'func'       => '_price_overview_list_table',
57
+				'capability' => 'ee_read_default_prices',
58
+			],
59
+			'add_new_price'               => [
60
+				'func'       => '_edit_price_details',
61
+				// 'args'       => ['new_price' => true],
62
+				'capability' => 'ee_edit_default_prices',
63
+			],
64
+			'edit_price'                  => [
65
+				'func'       => '_edit_price_details',
66
+				// 'args'       => ['new_price' => false],
67
+				'capability' => 'ee_edit_default_price',
68
+				'obj_id'     => $PRC_ID,
69
+			],
70
+			'insert_price'                => [
71
+				'func'       => '_insert_or_update_price',
72
+				'args'       => ['insert' => true],
73
+				'noheader'   => true,
74
+				'capability' => 'ee_edit_default_prices',
75
+			],
76
+			'update_price'                => [
77
+				'func'       => '_insert_or_update_price',
78
+				'args'       => ['insert' => false],
79
+				'noheader'   => true,
80
+				'capability' => 'ee_edit_default_price',
81
+				'obj_id'     => $PRC_ID,
82
+			],
83
+			'trash_price'                 => [
84
+				'func'       => '_trash_or_restore_price',
85
+				'args'       => ['trash' => true],
86
+				'noheader'   => true,
87
+				'capability' => 'ee_delete_default_price',
88
+				'obj_id'     => $PRC_ID,
89
+			],
90
+			'restore_price'               => [
91
+				'func'       => '_trash_or_restore_price',
92
+				'args'       => ['trash' => false],
93
+				'noheader'   => true,
94
+				'capability' => 'ee_delete_default_price',
95
+				'obj_id'     => $PRC_ID,
96
+			],
97
+			'delete_price'                => [
98
+				'func'       => '_delete_price',
99
+				'noheader'   => true,
100
+				'capability' => 'ee_delete_default_price',
101
+				'obj_id'     => $PRC_ID,
102
+			],
103
+			'espresso_update_price_order' => [
104
+				'func'       => 'update_price_order',
105
+				'noheader'   => true,
106
+				'capability' => 'ee_edit_default_prices',
107
+			],
108
+			// price types
109
+			'price_types'                 => [
110
+				'func'       => '_price_types_overview_list_table',
111
+				'capability' => 'ee_read_default_price_types',
112
+			],
113
+			'add_new_price_type'          => [
114
+				'func'       => '_edit_price_type_details',
115
+				'capability' => 'ee_edit_default_price_types',
116
+			],
117
+			'edit_price_type'             => [
118
+				'func'       => '_edit_price_type_details',
119
+				'capability' => 'ee_edit_default_price_type',
120
+				'obj_id'     => $PRT_ID,
121
+			],
122
+			'insert_price_type'           => [
123
+				'func'       => '_insert_or_update_price_type',
124
+				'args'       => ['new_price_type' => true],
125
+				'noheader'   => true,
126
+				'capability' => 'ee_edit_default_price_types',
127
+			],
128
+			'update_price_type'           => [
129
+				'func'       => '_insert_or_update_price_type',
130
+				'args'       => ['new_price_type' => false],
131
+				'noheader'   => true,
132
+				'capability' => 'ee_edit_default_price_type',
133
+				'obj_id'     => $PRT_ID,
134
+			],
135
+			'trash_price_type'            => [
136
+				'func'       => '_trash_or_restore_price_type',
137
+				'args'       => ['trash' => true],
138
+				'noheader'   => true,
139
+				'capability' => 'ee_delete_default_price_type',
140
+				'obj_id'     => $PRT_ID,
141
+			],
142
+			'restore_price_type'          => [
143
+				'func'       => '_trash_or_restore_price_type',
144
+				'args'       => ['trash' => false],
145
+				'noheader'   => true,
146
+				'capability' => 'ee_delete_default_price_type',
147
+				'obj_id'     => $PRT_ID,
148
+			],
149
+			'delete_price_type'           => [
150
+				'func'       => '_delete_price_type',
151
+				'noheader'   => true,
152
+				'capability' => 'ee_delete_default_price_type',
153
+				'obj_id'     => $PRT_ID,
154
+			],
155
+			'tax_settings'                => [
156
+				'func'       => '_tax_settings',
157
+				'capability' => 'manage_options',
158
+			],
159
+			'update_tax_settings'         => [
160
+				'func'       => '_update_tax_settings',
161
+				'capability' => 'manage_options',
162
+				'noheader'   => true,
163
+			],
164
+		];
165
+	}
166
+
167
+
168
+	protected function _set_page_config()
169
+	{
170
+		$PRC_ID             = $this->request->getRequestParam('id', 0, DataType::INTEGER);
171
+		$this->_page_config = [
172
+			'default'            => [
173
+				'nav'           => [
174
+					'label' => esc_html__('Default Pricing', 'event_espresso'),
175
+					'icon' => 'dashicons-money-alt',
176
+					'order' => 10,
177
+				],
178
+				'list_table'    => 'Prices_List_Table',
179
+				'metaboxes'     => $this->_default_espresso_metaboxes,
180
+				'help_tabs'     => [
181
+					'pricing_default_pricing_help_tab'                           => [
182
+						'title'    => esc_html__('Default Pricing', 'event_espresso'),
183
+						'filename' => 'pricing_default_pricing',
184
+					],
185
+					'pricing_default_pricing_table_column_headings_help_tab'     => [
186
+						'title'    => esc_html__('Default Pricing Table Column Headings', 'event_espresso'),
187
+						'filename' => 'pricing_default_pricing_table_column_headings',
188
+					],
189
+					'pricing_default_pricing_views_bulk_actions_search_help_tab' => [
190
+						'title'    => esc_html__('Default Pricing Views & Bulk Actions & Search', 'event_espresso'),
191
+						'filename' => 'pricing_default_pricing_views_bulk_actions_search',
192
+					],
193
+				],
194
+				'require_nonce' => false,
195
+			],
196
+			'add_new_price'      => [
197
+				'nav'           => [
198
+					'label'      => esc_html__('Add New Default Price', 'event_espresso'),
199
+					'icon' => 'dashicons-plus-alt',
200
+					'order'      => 20,
201
+					'persistent' => false,
202
+				],
203
+				'help_tabs'     => [
204
+					'add_new_default_price_help_tab' => [
205
+						'title'    => esc_html__('Add New Default Price', 'event_espresso'),
206
+						'filename' => 'pricing_add_new_default_price',
207
+					],
208
+				],
209
+				'metaboxes'     => array_merge(
210
+					['_publish_post_box'],
211
+					$this->_default_espresso_metaboxes
212
+				),
213
+				'require_nonce' => false,
214
+			],
215
+			'edit_price'         => [
216
+				'nav'           => [
217
+					'label'      => esc_html__('Edit Default Price', 'event_espresso'),
218
+					'icon' => 'dashicons-edit-large',
219
+					'order'      => 20,
220
+					'url'        => $PRC_ID
221
+						? add_query_arg(['id' => $PRC_ID], $this->_current_page_view_url)
222
+						: $this->_admin_base_url,
223
+					'persistent' => false,
224
+				],
225
+				'metaboxes'     => array_merge(
226
+					['_publish_post_box'],
227
+					$this->_default_espresso_metaboxes
228
+				),
229
+				'help_tabs'     => [
230
+					'edit_default_price_help_tab' => [
231
+						'title'    => esc_html__('Edit Default Price', 'event_espresso'),
232
+						'filename' => 'pricing_edit_default_price',
233
+					],
234
+				],
235
+				'require_nonce' => false,
236
+			],
237
+			'price_types'        => [
238
+				'nav'           => [
239
+					'label' => esc_html__('Price Types', 'event_espresso'),
240
+					'icon' => 'dashicons-networking',
241
+					'order' => 30,
242
+				],
243
+				'list_table'    => 'Price_Types_List_Table',
244
+				'help_tabs'     => [
245
+					'pricing_price_types_help_tab'                           => [
246
+						'title'    => esc_html__('Price Types', 'event_espresso'),
247
+						'filename' => 'pricing_price_types',
248
+					],
249
+					'pricing_price_types_table_column_headings_help_tab'     => [
250
+						'title'    => esc_html__('Price Types Table Column Headings', 'event_espresso'),
251
+						'filename' => 'pricing_price_types_table_column_headings',
252
+					],
253
+					'pricing_price_types_views_bulk_actions_search_help_tab' => [
254
+						'title'    => esc_html__('Price Types Views & Bulk Actions & Search', 'event_espresso'),
255
+						'filename' => 'pricing_price_types_views_bulk_actions_search',
256
+					],
257
+				],
258
+				'metaboxes'     => $this->_default_espresso_metaboxes,
259
+				'require_nonce' => false,
260
+			],
261
+			'add_new_price_type' => [
262
+				'nav'           => [
263
+					'label'      => esc_html__('Add New Price Type', 'event_espresso'),
264
+					'icon' => 'dashicons-plus-alt',
265
+					'order'      => 40,
266
+					'persistent' => false,
267
+				],
268
+				'help_tabs'     => [
269
+					'add_new_price_type_help_tab' => [
270
+						'title'    => esc_html__('Add New Price Type', 'event_espresso'),
271
+						'filename' => 'pricing_add_new_price_type',
272
+					],
273
+				],
274
+				'metaboxes'     => array_merge(
275
+					['_publish_post_box'],
276
+					$this->_default_espresso_metaboxes
277
+				),
278
+				'require_nonce' => false,
279
+			],
280
+			'edit_price_type'    => [
281
+				'nav'           => [
282
+					'label'      => esc_html__('Edit Price Type', 'event_espresso'),
283
+					'icon' => 'dashicons-edit-large',
284
+					'order'      => 40,
285
+					'persistent' => false,
286
+				],
287
+				'help_tabs'     => [
288
+					'edit_price_type_help_tab' => [
289
+						'title'    => esc_html__('Edit Price Type', 'event_espresso'),
290
+						'filename' => 'pricing_edit_price_type',
291
+					],
292
+				],
293
+				'metaboxes'     => array_merge(
294
+					['_publish_post_box'],
295
+					$this->_default_espresso_metaboxes
296
+				),
297
+				'require_nonce' => false,
298
+			],
299
+			'tax_settings'       => [
300
+				'nav' => [
301
+					'label' => esc_html__('Tax Settings', 'event_espresso'),
302
+					'icon' => 'dashicons-sticky',
303
+					'order' => 50,
304
+				],
305
+				'labels'        => [
306
+					'publishbox' => esc_html__('Update Tax Settings', 'event_espresso'),
307
+				],
308
+				'metaboxes'     => array_merge(
309
+					['_publish_post_box'],
310
+					$this->_default_espresso_metaboxes
311
+				),
312
+				'require_nonce' => true,
313
+			],
314
+		];
315
+	}
316
+
317
+
318
+	protected function _add_screen_options()
319
+	{
320
+		// todo
321
+	}
322
+
323
+
324
+	protected function _add_screen_options_default()
325
+	{
326
+		$this->_per_page_screen_option();
327
+	}
328
+
329
+
330
+	protected function _add_screen_options_price_types()
331
+	{
332
+		$page_title              = $this->_admin_page_title;
333
+		$this->_admin_page_title = esc_html__('Price Types', 'event_espresso');
334
+		$this->_per_page_screen_option();
335
+		$this->_admin_page_title = $page_title;
336
+	}
337
+
338
+
339
+	protected function _add_feature_pointers()
340
+	{
341
+	}
342
+
343
+
344
+	public function load_scripts_styles()
345
+	{
346
+		// styles
347
+		wp_enqueue_style('espresso-ui-theme');
348
+		wp_register_style(
349
+			'espresso_PRICING',
350
+			PRICING_ASSETS_URL . 'espresso_pricing_admin.css',
351
+			[],
352
+			EVENT_ESPRESSO_VERSION
353
+		);
354
+		wp_enqueue_style('espresso_PRICING');
355
+
356
+		// scripts
357
+		wp_enqueue_script('ee_admin_js');
358
+		wp_enqueue_script('jquery-ui-position');
359
+		wp_enqueue_script('jquery-ui-widget');
360
+		wp_register_script(
361
+			'espresso_PRICING',
362
+			PRICING_ASSETS_URL . 'espresso_pricing_admin.js',
363
+			['jquery'],
364
+			EVENT_ESPRESSO_VERSION,
365
+			true
366
+		);
367
+		wp_enqueue_script('espresso_PRICING');
368
+	}
369
+
370
+
371
+	public function load_scripts_styles_default()
372
+	{
373
+		wp_enqueue_script('espresso_ajax_table_sorting');
374
+	}
375
+
376
+
377
+	public function admin_footer_scripts()
378
+	{
379
+	}
380
+
381
+
382
+	public function admin_init()
383
+	{
384
+	}
385
+
386
+
387
+	public function admin_notices()
388
+	{
389
+	}
390
+
391
+
392
+	protected function _set_list_table_views_default()
393
+	{
394
+		$this->_views = [
395
+			'all' => [
396
+				'slug'        => 'all',
397
+				'label'       => esc_html__('View All Default Pricing', 'event_espresso'),
398
+				'count'       => 0,
399
+				'bulk_action' => [
400
+					'trash_price' => esc_html__('Move to Trash', 'event_espresso'),
401
+				],
402
+			],
403
+		];
404
+
405
+		if (EE_Registry::instance()->CAP->current_user_can('ee_delete_default_prices', 'pricing_trash_price')) {
406
+			$this->_views['trashed'] = [
407
+				'slug'        => 'trashed',
408
+				'label'       => esc_html__('Trash', 'event_espresso'),
409
+				'count'       => 0,
410
+				'bulk_action' => [
411
+					'restore_price' => esc_html__('Restore from Trash', 'event_espresso'),
412
+					'delete_price'  => esc_html__('Delete Permanently', 'event_espresso'),
413
+				],
414
+			];
415
+		}
416
+	}
417
+
418
+
419
+	protected function _set_list_table_views_price_types()
420
+	{
421
+		$this->_views = [
422
+			'all' => [
423
+				'slug'        => 'all',
424
+				'label'       => esc_html__('All', 'event_espresso'),
425
+				'count'       => 0,
426
+				'bulk_action' => [
427
+					'trash_price_type' => esc_html__('Move to Trash', 'event_espresso'),
428
+				],
429
+			],
430
+		];
431
+
432
+		if (
433
+			EE_Registry::instance()->CAP->current_user_can(
434
+				'ee_delete_default_price_types',
435
+				'pricing_trash_price_type'
436
+			)
437
+		) {
438
+			$this->_views['trashed'] = [
439
+				'slug'        => 'trashed',
440
+				'label'       => esc_html__('Trash', 'event_espresso'),
441
+				'count'       => 0,
442
+				'bulk_action' => [
443
+					'restore_price_type' => esc_html__('Restore from Trash', 'event_espresso'),
444
+					'delete_price_type'  => esc_html__('Delete Permanently', 'event_espresso'),
445
+				],
446
+			];
447
+		}
448
+	}
449
+
450
+
451
+	/**
452
+	 * generates HTML for main Prices Admin page
453
+	 *
454
+	 * @return void
455
+	 * @throws EE_Error
456
+	 */
457
+	protected function _price_overview_list_table()
458
+	{
459
+		$this->_admin_page_title .= ' ' . $this->get_action_link_or_button(
460
+			'add_new_price',
461
+			'add',
462
+			[],
463
+			'add-new-h2'
464
+		);
465
+		$this->_admin_page_title .= $this->_learn_more_about_pricing_link();
466
+		$this->_search_btn_label = esc_html__('Default Prices', 'event_espresso');
467
+		$this->display_admin_list_table_page_with_sidebar();
468
+	}
469
+
470
+
471
+	/**
472
+	 * retrieve data for Prices List table
473
+	 *
474
+	 * @param int  $per_page how many prices displayed per page
475
+	 * @param bool $count    return the count or objects
476
+	 * @param bool $trashed  whether the current view is of the trash can - eww yuck!
477
+	 * @return EE_Soft_Delete_Base_Class[]|int int = count || array of price objects
478
+	 * @throws EE_Error
479
+	 * @throws ReflectionException
480
+	 */
481
+	public function get_prices_overview_data(int $per_page = 10, bool $count = false, bool $trashed = false)
482
+	{
483
+		// start with an empty array
484
+		$event_pricing = [];
485
+
486
+		require_once(PRICING_ADMIN . 'Prices_List_Table.class.php');
487
+
488
+		$orderby = $this->request->getRequestParam('orderby', '');
489
+		$order   = $this->request->getRequestParam('order', 'ASC');
490
+
491
+		switch ($orderby) {
492
+			case 'name':
493
+				$orderby = ['PRC_name' => $order];
494
+				break;
495
+			case 'type':
496
+				$orderby = ['Price_Type.PRT_name' => $order];
497
+				break;
498
+			case 'amount':
499
+				$orderby = ['PRC_amount' => $order];
500
+				break;
501
+			default:
502
+				$orderby = ['PRC_order' => $order, 'Price_Type.PRT_order' => $order, 'PRC_ID' => $order];
503
+		}
504
+
505
+		$current_page = $this->request->getRequestParam('paged', 1, DataType::INTEGER);
506
+		$per_page     = $this->request->getRequestParam('perpage', $per_page, DataType::INTEGER);
507
+
508
+		$where = [
509
+			'PRC_is_default' => 1,
510
+			'PRC_deleted'    => $trashed,
511
+		];
512
+
513
+		$offset = ($current_page - 1) * $per_page;
514
+		$limit  = [$offset, $per_page];
515
+
516
+		$search_term = $this->request->getRequestParam('s');
517
+		if ($search_term) {
518
+			$search_term = "%{$search_term}%";
519
+			$where['OR'] = [
520
+				'PRC_name'            => ['LIKE', $search_term],
521
+				'PRC_desc'            => ['LIKE', $search_term],
522
+				'PRC_amount'          => ['LIKE', $search_term],
523
+				'Price_Type.PRT_name' => ['LIKE', $search_term],
524
+			];
525
+		}
526
+
527
+		$query_params = [
528
+			$where,
529
+			'order_by' => $orderby,
530
+			'limit'    => $limit,
531
+			'group_by' => 'PRC_ID',
532
+		];
533
+
534
+		if ($count) {
535
+			return $trashed
536
+				? EEM_Price::instance()->count([$where])
537
+				: EEM_Price::instance()->count_deleted_and_undeleted([$where]);
538
+		}
539
+		return EEM_Price::instance()->get_all_deleted_and_undeleted($query_params);
540
+	}
541
+
542
+
543
+	/**
544
+	 * @return void
545
+	 * @throws EE_Error
546
+	 * @throws ReflectionException
547
+	 */
548
+	protected function _edit_price_details()
549
+	{
550
+		// grab price ID
551
+		$PRC_ID = $this->request->getRequestParam('id', 0, DataType::INTEGER);
552
+		// change page title based on request action
553
+		switch ($this->_req_action) {
554
+			case 'add_new_price':
555
+				$this->_admin_page_title = esc_html__('Add New Price', 'event_espresso');
556
+				break;
557
+			case 'edit_price':
558
+				$this->_admin_page_title = esc_html__('Edit Price', 'event_espresso');
559
+				break;
560
+			default:
561
+				$this->_admin_page_title = ucwords(str_replace('_', ' ', $this->_req_action));
562
+		}
563
+		// add PRC_ID to title if editing
564
+		$this->_admin_page_title = $PRC_ID ? $this->_admin_page_title . ' # ' . $PRC_ID : $this->_admin_page_title;
565
+
566
+		if ($PRC_ID) {
567
+			$price                    = EEM_Price::instance()->get_one_by_ID($PRC_ID);
568
+			$additional_hidden_fields = [
569
+				'PRC_ID' => ['type' => 'hidden', 'value' => $PRC_ID],
570
+			];
571
+			$this->_set_add_edit_form_tags('update_price', $additional_hidden_fields);
572
+		} else {
573
+			$price = EEM_Price::instance()->get_new_price();
574
+			$this->_set_add_edit_form_tags('insert_price');
575
+		}
576
+
577
+		if (! $price instanceof EE_Price) {
578
+			throw new RuntimeException(
579
+				sprintf(
580
+					esc_html__(
581
+						'A valid Price could not be retrieved from the database with ID: %1$s',
582
+						'event_espresso'
583
+					),
584
+					$PRC_ID
585
+				)
586
+			);
587
+		}
588
+
589
+		$this->_template_args['PRC_ID'] = $PRC_ID;
590
+		$this->_template_args['price']  = $price;
591
+
592
+		$default_base_price = $price->type_obj() && $price->type_obj()->base_type() === 1;
593
+
594
+		$this->_template_args['default_base_price'] = $default_base_price;
595
+
596
+		// get price types
597
+		$price_types = EEM_Price_Type::instance()->get_all([['PBT_ID' => ['!=', 1]]]);
598
+		if (empty($price_types)) {
599
+			$msg = esc_html__(
600
+				'You have no price types defined. Please add a price type before adding a price.',
601
+				'event_espresso'
602
+			);
603
+			EE_Error::add_error($msg, __FILE__, __FUNCTION__, __LINE__);
604
+			$this->display_admin_page_with_sidebar();
605
+		}
606
+		$attributes       = [];
607
+		$price_type_names = [];
608
+		$attributes[]     = 'id="PRT_ID"';
609
+		if ($default_base_price) {
610
+			$attributes[]       = 'disabled="disabled"';
611
+			$price_type_names[] = ['id' => 1, 'text' => esc_html__('Base Price', 'event_espresso')];
612
+		}
613
+		foreach ($price_types as $type) {
614
+			$price_type_names[] = ['id' => $type->ID(), 'text' => $type->name()];
615
+		}
616
+		$this->_template_args['attributes']  = implode(' ', $attributes);
617
+		$this->_template_args['price_types'] = $price_type_names;
618
+
619
+		$this->_template_args['learn_more_about_pricing_link'] = $this->_learn_more_about_pricing_link();
620
+		$this->_template_args['admin_page_content']            = $this->_edit_price_details_meta_box();
621
+
622
+		$this->_set_publish_post_box_vars('id', $PRC_ID);
623
+		// the details template wrapper
624
+		$this->display_admin_page_with_sidebar();
625
+	}
626
+
627
+
628
+	/**
629
+	 *
630
+	 * @return string
631
+	 */
632
+	public function _edit_price_details_meta_box(): string
633
+	{
634
+		return EEH_Template::display_template(
635
+			PRICING_TEMPLATE_PATH . 'pricing_details_main_meta_box.template.php',
636
+			$this->_template_args,
637
+			true
638
+		);
639
+	}
640
+
641
+
642
+	/**
643
+	 * @return array
644
+	 * @throws EE_Error
645
+	 * @throws ReflectionException
646
+	 */
647
+	protected function set_price_column_values(): array
648
+	{
649
+		$PRC_order = 0;
650
+		$PRT_ID    = $this->request->getRequestParam('PRT_ID', 0, DataType::INTEGER);
651
+		if ($PRT_ID) {
652
+			/** @var EE_Price_Type $price_type */
653
+			$price_type = EEM_Price_Type::instance()->get_one_by_ID($PRT_ID);
654
+			if ($price_type instanceof EE_Price_Type) {
655
+				$PRC_order = $price_type->order();
656
+			}
657
+		}
658
+		return [
659
+			'PRT_ID'         => $PRT_ID,
660
+			'PRC_amount'     => $this->request->getRequestParam('PRC_amount', 0, DataType::FLOAT),
661
+			'PRC_name'       => $this->request->getRequestParam('PRC_name'),
662
+			'PRC_desc'       => $this->request->getRequestParam('PRC_desc'),
663
+			'PRC_is_default' => 1,
664
+			'PRC_overrides'  => null,
665
+			'PRC_order'      => $PRC_order,
666
+			'PRC_deleted'    => 0,
667
+			'PRC_parent'     => 0,
668
+		];
669
+	}
670
+
671
+
672
+	/**
673
+	 * @param bool $insert - whether to insert or update
674
+	 * @return void
675
+	 * @throws EE_Error
676
+	 * @throws ReflectionException
677
+	 */
678
+	protected function _insert_or_update_price(bool $insert = false)
679
+	{
680
+		// why be so pessimistic ???  : (
681
+		$updated = 0;
682
+
683
+		$set_column_values = $this->set_price_column_values();
684
+		// is this a new Price ?
685
+		if ($insert) {
686
+			// run the insert
687
+			$PRC_ID = EEM_Price::instance()->insert($set_column_values);
688
+			if ($PRC_ID) {
689
+				// make sure this new price modifier is attached to the ticket but ONLY if it is not a tax type
690
+				$price = EEM_price::instance()->get_one_by_ID($PRC_ID);
691
+				if (
692
+					$price instanceof EE_Price
693
+					&& $price->type_obj() instanceof EE_Price_type
694
+					&& $price->type_obj()->base_type() !== EEM_Price_Type::base_type_tax
695
+				) {
696
+					$ticket = EEM_Ticket::instance()->get_one_by_ID(1);
697
+					$ticket->_add_relation_to($price, 'Price');
698
+					$ticket->save();
699
+				}
700
+				$updated = 1;
701
+			}
702
+			$action_desc = 'created';
703
+		} else {
704
+			$PRC_ID = $this->request->getRequestParam('PRC_ID', 0, DataType::INTEGER);
705
+			// run the update
706
+			$where_cols_n_values = ['PRC_ID' => $PRC_ID];
707
+			$updated             = EEM_Price::instance()->update($set_column_values, [$where_cols_n_values]);
708
+
709
+			$price = EEM_Price::instance()->get_one_by_ID($PRC_ID);
710
+			if ($price instanceof EE_Price && $price->type_obj()->base_type() !== EEM_Price_Type::base_type_tax) {
711
+				// if this is $PRC_ID == 1,
712
+				// then we need to update the default ticket attached to this price so the TKT_price value is updated.
713
+				if ($PRC_ID === 1) {
714
+					$ticket = $price->get_first_related('Ticket');
715
+					if ($ticket) {
716
+						$ticket->set('TKT_price', $price->get('PRC_amount'));
717
+						$ticket->set('TKT_name', $price->get('PRC_name'));
718
+						$ticket->set('TKT_description', $price->get('PRC_desc'));
719
+						$ticket->save();
720
+					}
721
+				} else {
722
+					// we make sure this price is attached to base ticket. but ONLY if its not a tax ticket type.
723
+					$ticket = EEM_Ticket::instance()->get_one_by_ID(1);
724
+					$ticket->_add_relation_to($PRC_ID, 'Price');
725
+					$ticket->save();
726
+				}
727
+			}
728
+
729
+			$action_desc = 'updated';
730
+		}
731
+
732
+		$query_args = ['action' => 'edit_price', 'id' => $PRC_ID];
733
+
734
+		$this->_redirect_after_action($updated, 'Prices', $action_desc, $query_args);
735
+	}
736
+
737
+
738
+	/**
739
+	 * @param bool $trash - whether to move item to trash (TRUE) or restore it (FALSE)
740
+	 * @return void
741
+	 * @throws EE_Error
742
+	 * @throws ReflectionException
743
+	 */
744
+	protected function _trash_or_restore_price(bool $trash = true)
745
+	{
746
+		$entity_model = EEM_Price::instance();
747
+		$action       = $trash ? EE_Admin_List_Table::ACTION_TRASH : EE_Admin_List_Table::ACTION_RESTORE;
748
+		$result       = $this->trashRestoreDeleteEntities(
749
+			$entity_model,
750
+			'id',
751
+			$action,
752
+			'PRC_deleted',
753
+			[$this, 'adjustTicketRelations']
754
+		);
755
+
756
+		if ($result) {
757
+			$msg = $trash
758
+				? esc_html(
759
+					_n(
760
+						'The Price has been trashed',
761
+						'The Prices have been trashed',
762
+						$result,
763
+						'event_espresso'
764
+					)
765
+				)
766
+				: esc_html(
767
+					_n(
768
+						'The Price has been restored',
769
+						'The Prices have been restored',
770
+						$result,
771
+						'event_espresso'
772
+					)
773
+				);
774
+			EE_Error::add_success($msg);
775
+		}
776
+
777
+		$this->_redirect_after_action(
778
+			$result,
779
+			_n('Price', 'Prices', $result, 'event_espresso'),
780
+			$trash ? 'trashed' : 'restored',
781
+			['action' => 'default'],
782
+			true
783
+		);
784
+	}
785
+
786
+
787
+	/**
788
+	 * @param EEM_Base   $entity_model
789
+	 * @param int|string $entity_ID
790
+	 * @param string     $action
791
+	 * @param int        $result
792
+	 * @throws EE_Error
793
+	 * @throws ReflectionException
794
+	 * @since 4.10.30.p
795
+	 */
796
+	public function adjustTicketRelations(
797
+		EEM_Base $entity_model,
798
+		$entity_ID,
799
+		string $action,
800
+		int $result
801
+	) {
802
+		if (! $entity_ID || (float) $result < 1) {
803
+			return;
804
+		}
805
+
806
+		$entity = $entity_model->get_one_by_ID($entity_ID);
807
+		if (! $entity instanceof EE_Price || $entity->type_obj()->base_type() === EEM_Price_Type::base_type_tax) {
808
+			return;
809
+		}
810
+
811
+		// get default tickets for updating
812
+		$default_tickets = EEM_Ticket::instance()->get_all_default_tickets();
813
+		foreach ($default_tickets as $default_ticket) {
814
+			if (! $default_ticket instanceof EE_Ticket) {
815
+				continue;
816
+			}
817
+			switch ($action) {
818
+				case EE_Admin_List_Table::ACTION_DELETE:
819
+				case EE_Admin_List_Table::ACTION_TRASH:
820
+					// if trashing then remove relations to base default ticket.
821
+					$default_ticket->_remove_relation_to($entity_ID, 'Price');
822
+					break;
823
+				case EE_Admin_List_Table::ACTION_RESTORE:
824
+					// if restoring then add back to base default ticket
825
+					$default_ticket->_add_relation_to($entity_ID, 'Price');
826
+					break;
827
+			}
828
+			$default_ticket->save();
829
+		}
830
+	}
831
+
832
+
833
+	/**
834
+	 * @return void
835
+	 * @throws EE_Error
836
+	 * @throws ReflectionException
837
+	 */
838
+	protected function _delete_price()
839
+	{
840
+		$entity_model = EEM_Price::instance();
841
+		$deleted      = $this->trashRestoreDeleteEntities($entity_model, 'id');
842
+		$entity       = $entity_model->item_name($deleted);
843
+		$this->_redirect_after_action(
844
+			$deleted,
845
+			$entity,
846
+			'deleted',
847
+			['action' => 'default']
848
+		);
849
+	}
850
+
851
+
852
+	/**
853
+	 * @throws EE_Error
854
+	 * @throws ReflectionException
855
+	 */
856
+	public function update_price_order()
857
+	{
858
+		// grab our row IDs
859
+		$row_ids = $this->request->getRequestParam('row_ids', '');
860
+		$row_ids = explode(',', rtrim($row_ids, ','));
861
+
862
+		$all_updated = true;
863
+		foreach ($row_ids as $i => $row_id) {
864
+			// Update the prices when re-ordering
865
+			$fields_n_values = ['PRC_order' => $i + 1];
866
+			$query_params    = [['PRC_ID' => absint($row_id)]];
867
+			// any failure will toggle $all_updated to false
868
+			$all_updated = $row_id && EEM_Price::instance()->update($fields_n_values, $query_params) !== false
869
+				? $all_updated
870
+				: false;
871
+		}
872
+		$success = $all_updated
873
+			? esc_html__('Price order was updated successfully.', 'event_espresso')
874
+			: false;
875
+		$errors  = ! $all_updated
876
+			? esc_html__('An error occurred. The price order was not updated.', 'event_espresso')
877
+			: false;
878
+
879
+		echo wp_json_encode(['return_data' => false, 'success' => $success, 'errors' => $errors]);
880
+		die();
881
+	}
882
+
883
+
884
+	/******************************************************************************************************************
885 885
      ***********************************************  TICKET PRICE TYPES  *********************************************
886 886
      ******************************************************************************************************************/
887 887
 
888 888
 
889
-    /**
890
-     * generates HTML for main Prices Admin page
891
-     *
892
-     * @return void
893
-     * @throws EE_Error
894
-     */
895
-    protected function _price_types_overview_list_table()
896
-    {
897
-        $this->_admin_page_title .= ' ' . $this->get_action_link_or_button(
898
-            'add_new_price_type',
899
-            'add_type',
900
-            [],
901
-            'add-new-h2'
902
-        );
903
-        $this->_admin_page_title .= $this->_learn_more_about_pricing_link();
904
-        $this->_search_btn_label = esc_html__('Price Types', 'event_espresso');
905
-        $this->display_admin_list_table_page_with_sidebar();
906
-    }
907
-
908
-
909
-    /**
910
-     * retrieve data for Price Types List table
911
-     *
912
-     * @param int  $per_page how many prices displayed per page
913
-     * @param bool $count    return the count or objects
914
-     * @param bool $trashed  whether the current view is of the trash can - eww yuck!
915
-     * @return EE_Soft_Delete_Base_Class[]|int int = count || array of price objects
916
-     * @throws EE_Error
917
-     * @throws ReflectionException
918
-     */
919
-    public function get_price_types_overview_data(int $per_page = 10, bool $count = false, bool $trashed = false)
920
-    {
921
-        // start with an empty array
922
-        require_once(PRICING_ADMIN . 'Price_Types_List_Table.class.php');
923
-
924
-        $orderby = $this->request->getRequestParam('orderby', '');
925
-        $order   = $this->request->getRequestParam('order', 'ASC');
926
-
927
-        switch ($orderby) {
928
-            case 'name':
929
-                $orderby = ['PRT_name' => $order];
930
-                break;
931
-            default:
932
-                $orderby = ['PRT_order' => $order];
933
-        }
934
-
935
-        $current_page = $this->request->getRequestParam('paged', 1, DataType::INTEGER);
936
-        $per_page     = $this->request->getRequestParam('perpage', $per_page, DataType::INTEGER);
937
-
938
-        $offset = ($current_page - 1) * $per_page;
939
-        $limit  = [$offset, $per_page];
940
-
941
-        $where = ['PRT_deleted' => $trashed, 'PBT_ID' => ['!=', 1]];
942
-
943
-        $search_term = $this->request->getRequestParam('s');
944
-        if ($search_term) {
945
-            $where['OR'] = [
946
-                'PRT_name' => ['LIKE', "%{$search_term}%"],
947
-            ];
948
-        }
949
-        $query_params = [
950
-            $where,
951
-            'order_by' => $orderby,
952
-            'limit'    => $limit,
953
-        ];
954
-        return $count
955
-            ? EEM_Price_Type::instance()->count_deleted_and_undeleted($query_params)
956
-            : EEM_Price_Type::instance()->get_all_deleted_and_undeleted($query_params);
957
-    }
958
-
959
-
960
-    /**
961
-     * _edit_price_type_details
962
-     *
963
-     * @return void
964
-     * @throws EE_Error
965
-     * @throws ReflectionException
966
-     */
967
-    protected function _edit_price_type_details()
968
-    {
969
-        // grab price type ID
970
-        $PRT_ID = $this->request->getRequestParam('id', 0, DataType::INTEGER);
971
-        // change page title based on request action
972
-        switch ($this->_req_action) {
973
-            case 'add_new_price_type':
974
-                $this->_admin_page_title = esc_html__('Add New Price Type', 'event_espresso');
975
-                break;
976
-            case 'edit_price_type':
977
-                $this->_admin_page_title = esc_html__('Edit Price Type', 'event_espresso');
978
-                break;
979
-            default:
980
-                $this->_admin_page_title = ucwords(str_replace('_', ' ', $this->_req_action));
981
-        }
982
-        // add PRT_ID to title if editing
983
-        $this->_admin_page_title = $PRT_ID ? $this->_admin_page_title . ' # ' . $PRT_ID : $this->_admin_page_title;
984
-
985
-        if ($PRT_ID) {
986
-            $price_type               = EEM_Price_Type::instance()->get_one_by_ID($PRT_ID);
987
-            $additional_hidden_fields = ['PRT_ID' => ['type' => 'hidden', 'value' => $PRT_ID]];
988
-            $this->_set_add_edit_form_tags('update_price_type', $additional_hidden_fields);
989
-        } else {
990
-            $price_type = EEM_Price_Type::instance()->get_new_price_type();
991
-            $this->_set_add_edit_form_tags('insert_price_type');
992
-        }
993
-
994
-        if (! $price_type instanceof EE_Price_Type) {
995
-            throw new RuntimeException(
996
-                sprintf(
997
-                    esc_html__(
998
-                        'A valid Price Type could not be retrieved from the database with ID: %1$s',
999
-                        'event_espresso'
1000
-                    ),
1001
-                    $PRT_ID
1002
-                )
1003
-            );
1004
-        }
1005
-
1006
-        $this->_template_args['PRT_ID']     = $PRT_ID;
1007
-        $this->_template_args['price_type'] = $price_type;
1008
-
1009
-        $base_types    = EEM_Price_Type::instance()->get_base_types();
1010
-        $select_values = [];
1011
-        foreach ($base_types as $ref => $text) {
1012
-            if ($ref == EEM_Price_Type::base_type_base_price) {
1013
-                // do not allow creation of base_type_base_prices because that's a system only base type.
1014
-                continue;
1015
-            }
1016
-            $select_values[] = ['id' => $ref, 'text' => $text];
1017
-        }
1018
-
1019
-        $this->_template_args['base_type_select'] = EEH_Form_Fields::select_input(
1020
-            'base_type',
1021
-            $select_values,
1022
-            $price_type->base_type(),
1023
-            'id="price-type-base-type-slct"'
1024
-        );
1025
-
1026
-        $this->_template_args['learn_more_about_pricing_link'] = $this->_learn_more_about_pricing_link();
1027
-        $this->_template_args['admin_page_content']            = $this->_edit_price_type_details_meta_box();
1028
-
1029
-        $redirect_URL = add_query_arg(['action' => 'price_types'], $this->_admin_base_url);
1030
-        $this->_set_publish_post_box_vars('id', $PRT_ID, false, $redirect_URL);
1031
-        // the details template wrapper
1032
-        $this->display_admin_page_with_sidebar();
1033
-    }
1034
-
1035
-
1036
-    /**
1037
-     * _edit_price_type_details_meta_box
1038
-     *
1039
-     * @return string
1040
-     */
1041
-    public function _edit_price_type_details_meta_box(): string
1042
-    {
1043
-        return EEH_Template::display_template(
1044
-            PRICING_TEMPLATE_PATH . 'pricing_type_details_main_meta_box.template.php',
1045
-            $this->_template_args,
1046
-            true
1047
-        );
1048
-    }
1049
-
1050
-
1051
-    /**
1052
-     * @return array
1053
-     */
1054
-    protected function set_price_type_column_values(): array
1055
-    {
1056
-        $base_type  = $this->request->getRequestParam(
1057
-            'base_type',
1058
-            EEM_Price_Type::base_type_base_price,
1059
-            DataType::INTEGER
1060
-        );
1061
-        $is_percent = $this->request->getRequestParam('PRT_is_percent', 0, DataType::INTEGER);
1062
-        $order      = $this->request->getRequestParam('PRT_order', 0, DataType::INTEGER);
1063
-        switch ($base_type) {
1064
-            case EEM_Price_Type::base_type_base_price:
1065
-                $is_percent = 0;
1066
-                $order      = 0;
1067
-                break;
1068
-
1069
-            case EEM_Price_Type::base_type_discount:
1070
-            case EEM_Price_Type::base_type_surcharge:
1071
-                break;
1072
-
1073
-            case EEM_Price_Type::base_type_tax:
1074
-                $is_percent = 1;
1075
-                break;
1076
-        }
1077
-
1078
-        return [
1079
-            'PBT_ID'         => $base_type,
1080
-            'PRT_name'       => $this->request->getRequestParam('PRT_name', ''),
1081
-            'PRT_is_percent' => $is_percent,
1082
-            'PRT_order'      => $order,
1083
-            'PRT_deleted'    => 0,
1084
-        ];
1085
-    }
1086
-
1087
-
1088
-    /**
1089
-     * @param bool $new_price_type - whether to insert or update
1090
-     * @return void
1091
-     * @throws EE_Error
1092
-     * @throws ReflectionException
1093
-     */
1094
-    protected function _insert_or_update_price_type(bool $new_price_type = false)
1095
-    {
1096
-        // why be so pessimistic ???  : (
1097
-        $success = 0;
1098
-
1099
-        $set_column_values = $this->set_price_type_column_values();
1100
-        // is this a new Price ?
1101
-        if ($new_price_type) {
1102
-            // run the insert
1103
-            if ($PRT_ID = EEM_Price_Type::instance()->insert($set_column_values)) {
1104
-                $success = 1;
1105
-            }
1106
-            $action_desc = 'created';
1107
-        } else {
1108
-            $PRT_ID = $this->request->getRequestParam('PRT_ID', 0, DataType::INTEGER);
1109
-            // run the update
1110
-            $where_cols_n_values = ['PRT_ID' => $PRT_ID];
1111
-            if (EEM_Price_Type::instance()->update($set_column_values, [$where_cols_n_values])) {
1112
-                $success = 1;
1113
-            }
1114
-            $action_desc = 'updated';
1115
-        }
1116
-
1117
-        $query_args = ['action' => 'edit_price_type', 'id' => $PRT_ID];
1118
-        $this->_redirect_after_action($success, 'Price Type', $action_desc, $query_args);
1119
-    }
1120
-
1121
-
1122
-    /**
1123
-     * @param bool $trash - whether to move item to trash (TRUE) or restore it (FALSE)
1124
-     * @return void
1125
-     * @throws EE_Error
1126
-     * @throws ReflectionException
1127
-     */
1128
-    protected function _trash_or_restore_price_type(bool $trash = true)
1129
-    {
1130
-        $entity_model = EEM_Price_Type::instance();
1131
-        $action       = $trash ? EE_Admin_List_Table::ACTION_TRASH : EE_Admin_List_Table::ACTION_RESTORE;
1132
-        $success      = $this->trashRestoreDeleteEntities($entity_model, 'id', $action, 'PRT_deleted');
1133
-        if ($success) {
1134
-            $msg = $trash
1135
-                ? esc_html(
1136
-                    _n(
1137
-                        'The Price Type has been trashed',
1138
-                        'The Price Types have been trashed',
1139
-                        $success,
1140
-                        'event_espresso'
1141
-                    )
1142
-                )
1143
-                : esc_html(
1144
-                    _n(
1145
-                        'The Price Type has been restored',
1146
-                        'The Price Types have been restored',
1147
-                        $success,
1148
-                        'event_espresso'
1149
-                    )
1150
-                );
1151
-            EE_Error::add_success($msg);
1152
-        }
1153
-        $this->_redirect_after_action('', '', '', ['action' => 'price_types'], true);
1154
-    }
1155
-
1156
-
1157
-    /**
1158
-     * @return void
1159
-     * @throws EE_Error
1160
-     * @throws ReflectionException
1161
-     */
1162
-    protected function _delete_price_type()
1163
-    {
1164
-        $entity_model = EEM_Price_Type::instance();
1165
-        $deleted      = $this->trashRestoreDeleteEntities($entity_model, 'id');
1166
-        $this->_redirect_after_action(
1167
-            $deleted,
1168
-            $entity_model->item_name($deleted),
1169
-            'deleted',
1170
-            ['action' => 'price_types']
1171
-        );
1172
-    }
1173
-
1174
-
1175
-    /**
1176
-     * @return string
1177
-     */
1178
-    protected function _learn_more_about_pricing_link(): string
1179
-    {
1180
-        return '
889
+	/**
890
+	 * generates HTML for main Prices Admin page
891
+	 *
892
+	 * @return void
893
+	 * @throws EE_Error
894
+	 */
895
+	protected function _price_types_overview_list_table()
896
+	{
897
+		$this->_admin_page_title .= ' ' . $this->get_action_link_or_button(
898
+			'add_new_price_type',
899
+			'add_type',
900
+			[],
901
+			'add-new-h2'
902
+		);
903
+		$this->_admin_page_title .= $this->_learn_more_about_pricing_link();
904
+		$this->_search_btn_label = esc_html__('Price Types', 'event_espresso');
905
+		$this->display_admin_list_table_page_with_sidebar();
906
+	}
907
+
908
+
909
+	/**
910
+	 * retrieve data for Price Types List table
911
+	 *
912
+	 * @param int  $per_page how many prices displayed per page
913
+	 * @param bool $count    return the count or objects
914
+	 * @param bool $trashed  whether the current view is of the trash can - eww yuck!
915
+	 * @return EE_Soft_Delete_Base_Class[]|int int = count || array of price objects
916
+	 * @throws EE_Error
917
+	 * @throws ReflectionException
918
+	 */
919
+	public function get_price_types_overview_data(int $per_page = 10, bool $count = false, bool $trashed = false)
920
+	{
921
+		// start with an empty array
922
+		require_once(PRICING_ADMIN . 'Price_Types_List_Table.class.php');
923
+
924
+		$orderby = $this->request->getRequestParam('orderby', '');
925
+		$order   = $this->request->getRequestParam('order', 'ASC');
926
+
927
+		switch ($orderby) {
928
+			case 'name':
929
+				$orderby = ['PRT_name' => $order];
930
+				break;
931
+			default:
932
+				$orderby = ['PRT_order' => $order];
933
+		}
934
+
935
+		$current_page = $this->request->getRequestParam('paged', 1, DataType::INTEGER);
936
+		$per_page     = $this->request->getRequestParam('perpage', $per_page, DataType::INTEGER);
937
+
938
+		$offset = ($current_page - 1) * $per_page;
939
+		$limit  = [$offset, $per_page];
940
+
941
+		$where = ['PRT_deleted' => $trashed, 'PBT_ID' => ['!=', 1]];
942
+
943
+		$search_term = $this->request->getRequestParam('s');
944
+		if ($search_term) {
945
+			$where['OR'] = [
946
+				'PRT_name' => ['LIKE', "%{$search_term}%"],
947
+			];
948
+		}
949
+		$query_params = [
950
+			$where,
951
+			'order_by' => $orderby,
952
+			'limit'    => $limit,
953
+		];
954
+		return $count
955
+			? EEM_Price_Type::instance()->count_deleted_and_undeleted($query_params)
956
+			: EEM_Price_Type::instance()->get_all_deleted_and_undeleted($query_params);
957
+	}
958
+
959
+
960
+	/**
961
+	 * _edit_price_type_details
962
+	 *
963
+	 * @return void
964
+	 * @throws EE_Error
965
+	 * @throws ReflectionException
966
+	 */
967
+	protected function _edit_price_type_details()
968
+	{
969
+		// grab price type ID
970
+		$PRT_ID = $this->request->getRequestParam('id', 0, DataType::INTEGER);
971
+		// change page title based on request action
972
+		switch ($this->_req_action) {
973
+			case 'add_new_price_type':
974
+				$this->_admin_page_title = esc_html__('Add New Price Type', 'event_espresso');
975
+				break;
976
+			case 'edit_price_type':
977
+				$this->_admin_page_title = esc_html__('Edit Price Type', 'event_espresso');
978
+				break;
979
+			default:
980
+				$this->_admin_page_title = ucwords(str_replace('_', ' ', $this->_req_action));
981
+		}
982
+		// add PRT_ID to title if editing
983
+		$this->_admin_page_title = $PRT_ID ? $this->_admin_page_title . ' # ' . $PRT_ID : $this->_admin_page_title;
984
+
985
+		if ($PRT_ID) {
986
+			$price_type               = EEM_Price_Type::instance()->get_one_by_ID($PRT_ID);
987
+			$additional_hidden_fields = ['PRT_ID' => ['type' => 'hidden', 'value' => $PRT_ID]];
988
+			$this->_set_add_edit_form_tags('update_price_type', $additional_hidden_fields);
989
+		} else {
990
+			$price_type = EEM_Price_Type::instance()->get_new_price_type();
991
+			$this->_set_add_edit_form_tags('insert_price_type');
992
+		}
993
+
994
+		if (! $price_type instanceof EE_Price_Type) {
995
+			throw new RuntimeException(
996
+				sprintf(
997
+					esc_html__(
998
+						'A valid Price Type could not be retrieved from the database with ID: %1$s',
999
+						'event_espresso'
1000
+					),
1001
+					$PRT_ID
1002
+				)
1003
+			);
1004
+		}
1005
+
1006
+		$this->_template_args['PRT_ID']     = $PRT_ID;
1007
+		$this->_template_args['price_type'] = $price_type;
1008
+
1009
+		$base_types    = EEM_Price_Type::instance()->get_base_types();
1010
+		$select_values = [];
1011
+		foreach ($base_types as $ref => $text) {
1012
+			if ($ref == EEM_Price_Type::base_type_base_price) {
1013
+				// do not allow creation of base_type_base_prices because that's a system only base type.
1014
+				continue;
1015
+			}
1016
+			$select_values[] = ['id' => $ref, 'text' => $text];
1017
+		}
1018
+
1019
+		$this->_template_args['base_type_select'] = EEH_Form_Fields::select_input(
1020
+			'base_type',
1021
+			$select_values,
1022
+			$price_type->base_type(),
1023
+			'id="price-type-base-type-slct"'
1024
+		);
1025
+
1026
+		$this->_template_args['learn_more_about_pricing_link'] = $this->_learn_more_about_pricing_link();
1027
+		$this->_template_args['admin_page_content']            = $this->_edit_price_type_details_meta_box();
1028
+
1029
+		$redirect_URL = add_query_arg(['action' => 'price_types'], $this->_admin_base_url);
1030
+		$this->_set_publish_post_box_vars('id', $PRT_ID, false, $redirect_URL);
1031
+		// the details template wrapper
1032
+		$this->display_admin_page_with_sidebar();
1033
+	}
1034
+
1035
+
1036
+	/**
1037
+	 * _edit_price_type_details_meta_box
1038
+	 *
1039
+	 * @return string
1040
+	 */
1041
+	public function _edit_price_type_details_meta_box(): string
1042
+	{
1043
+		return EEH_Template::display_template(
1044
+			PRICING_TEMPLATE_PATH . 'pricing_type_details_main_meta_box.template.php',
1045
+			$this->_template_args,
1046
+			true
1047
+		);
1048
+	}
1049
+
1050
+
1051
+	/**
1052
+	 * @return array
1053
+	 */
1054
+	protected function set_price_type_column_values(): array
1055
+	{
1056
+		$base_type  = $this->request->getRequestParam(
1057
+			'base_type',
1058
+			EEM_Price_Type::base_type_base_price,
1059
+			DataType::INTEGER
1060
+		);
1061
+		$is_percent = $this->request->getRequestParam('PRT_is_percent', 0, DataType::INTEGER);
1062
+		$order      = $this->request->getRequestParam('PRT_order', 0, DataType::INTEGER);
1063
+		switch ($base_type) {
1064
+			case EEM_Price_Type::base_type_base_price:
1065
+				$is_percent = 0;
1066
+				$order      = 0;
1067
+				break;
1068
+
1069
+			case EEM_Price_Type::base_type_discount:
1070
+			case EEM_Price_Type::base_type_surcharge:
1071
+				break;
1072
+
1073
+			case EEM_Price_Type::base_type_tax:
1074
+				$is_percent = 1;
1075
+				break;
1076
+		}
1077
+
1078
+		return [
1079
+			'PBT_ID'         => $base_type,
1080
+			'PRT_name'       => $this->request->getRequestParam('PRT_name', ''),
1081
+			'PRT_is_percent' => $is_percent,
1082
+			'PRT_order'      => $order,
1083
+			'PRT_deleted'    => 0,
1084
+		];
1085
+	}
1086
+
1087
+
1088
+	/**
1089
+	 * @param bool $new_price_type - whether to insert or update
1090
+	 * @return void
1091
+	 * @throws EE_Error
1092
+	 * @throws ReflectionException
1093
+	 */
1094
+	protected function _insert_or_update_price_type(bool $new_price_type = false)
1095
+	{
1096
+		// why be so pessimistic ???  : (
1097
+		$success = 0;
1098
+
1099
+		$set_column_values = $this->set_price_type_column_values();
1100
+		// is this a new Price ?
1101
+		if ($new_price_type) {
1102
+			// run the insert
1103
+			if ($PRT_ID = EEM_Price_Type::instance()->insert($set_column_values)) {
1104
+				$success = 1;
1105
+			}
1106
+			$action_desc = 'created';
1107
+		} else {
1108
+			$PRT_ID = $this->request->getRequestParam('PRT_ID', 0, DataType::INTEGER);
1109
+			// run the update
1110
+			$where_cols_n_values = ['PRT_ID' => $PRT_ID];
1111
+			if (EEM_Price_Type::instance()->update($set_column_values, [$where_cols_n_values])) {
1112
+				$success = 1;
1113
+			}
1114
+			$action_desc = 'updated';
1115
+		}
1116
+
1117
+		$query_args = ['action' => 'edit_price_type', 'id' => $PRT_ID];
1118
+		$this->_redirect_after_action($success, 'Price Type', $action_desc, $query_args);
1119
+	}
1120
+
1121
+
1122
+	/**
1123
+	 * @param bool $trash - whether to move item to trash (TRUE) or restore it (FALSE)
1124
+	 * @return void
1125
+	 * @throws EE_Error
1126
+	 * @throws ReflectionException
1127
+	 */
1128
+	protected function _trash_or_restore_price_type(bool $trash = true)
1129
+	{
1130
+		$entity_model = EEM_Price_Type::instance();
1131
+		$action       = $trash ? EE_Admin_List_Table::ACTION_TRASH : EE_Admin_List_Table::ACTION_RESTORE;
1132
+		$success      = $this->trashRestoreDeleteEntities($entity_model, 'id', $action, 'PRT_deleted');
1133
+		if ($success) {
1134
+			$msg = $trash
1135
+				? esc_html(
1136
+					_n(
1137
+						'The Price Type has been trashed',
1138
+						'The Price Types have been trashed',
1139
+						$success,
1140
+						'event_espresso'
1141
+					)
1142
+				)
1143
+				: esc_html(
1144
+					_n(
1145
+						'The Price Type has been restored',
1146
+						'The Price Types have been restored',
1147
+						$success,
1148
+						'event_espresso'
1149
+					)
1150
+				);
1151
+			EE_Error::add_success($msg);
1152
+		}
1153
+		$this->_redirect_after_action('', '', '', ['action' => 'price_types'], true);
1154
+	}
1155
+
1156
+
1157
+	/**
1158
+	 * @return void
1159
+	 * @throws EE_Error
1160
+	 * @throws ReflectionException
1161
+	 */
1162
+	protected function _delete_price_type()
1163
+	{
1164
+		$entity_model = EEM_Price_Type::instance();
1165
+		$deleted      = $this->trashRestoreDeleteEntities($entity_model, 'id');
1166
+		$this->_redirect_after_action(
1167
+			$deleted,
1168
+			$entity_model->item_name($deleted),
1169
+			'deleted',
1170
+			['action' => 'price_types']
1171
+		);
1172
+	}
1173
+
1174
+
1175
+	/**
1176
+	 * @return string
1177
+	 */
1178
+	protected function _learn_more_about_pricing_link(): string
1179
+	{
1180
+		return '
1181 1181
             <a class="hidden" style="margin:0 20px; cursor:pointer; font-size:12px;" >
1182 1182
                 ' . esc_html__('learn more about how pricing works', 'event_espresso') . '
1183 1183
             </a>';
1184
-    }
1185
-
1186
-
1187
-    /**
1188
-     * @throws EE_Error
1189
-     */
1190
-    protected function _tax_settings()
1191
-    {
1192
-        $this->_set_add_edit_form_tags('update_tax_settings');
1193
-        $this->_set_publish_post_box_vars(null, false, false, null, false);
1194
-        $this->_template_args['admin_page_content'] = $this->tax_settings_form()->get_html();
1195
-        $this->display_admin_page_with_sidebar();
1196
-    }
1197
-
1198
-
1199
-    /**
1200
-     * @return EE_Form_Section_Proper
1201
-     * @throws EE_Error
1202
-     */
1203
-    protected function tax_settings_form(): EE_Form_Section_Proper
1204
-    {
1205
-        $tax_settings = EE_Config::instance()->tax_settings;
1206
-        return new EE_Form_Section_Proper(
1207
-            [
1208
-                'name'            => 'tax_settings_form',
1209
-                'html_id'         => 'tax_settings_form',
1210
-                'html_class'      => 'padding',
1211
-                'layout_strategy' => new EE_Div_Per_Section_Layout(),
1212
-                'subsections'     => apply_filters(
1213
-                    'FHEE__Pricing_Admin_Page__tax_settings_form__form_subsections',
1214
-                    [
1215
-                        'tax_settings' => new EE_Form_Section_Proper(
1216
-                            [
1217
-                                'name'            => 'tax_settings_tbl',
1218
-                                'html_id'         => 'tax_settings_tbl',
1219
-                                'html_class'      => 'form-table',
1220
-                                'layout_strategy' => new EE_Admin_Two_Column_Layout(),
1221
-                                'subsections'     => [
1222
-                                    'prices_displayed_including_taxes' => new EE_Yes_No_Input(
1223
-                                        [
1224
-                                            'html_label_text'         => esc_html__(
1225
-                                                'Show Prices With Taxes Included?',
1226
-                                                'event_espresso'
1227
-                                            ),
1228
-                                            'html_help_text'          => esc_html__(
1229
-                                                'Indicates whether or not to display prices with the taxes included',
1230
-                                                'event_espresso'
1231
-                                            ),
1232
-                                            'default'                 => $tax_settings->prices_displayed_including_taxes
1233
-                                                                         ?? true,
1234
-                                            'display_html_label_text' => false,
1235
-                                        ]
1236
-                                    ),
1237
-                                ],
1238
-                            ]
1239
-                        ),
1240
-                    ]
1241
-                ),
1242
-            ]
1243
-        );
1244
-    }
1245
-
1246
-
1247
-    /**
1248
-     * _update_tax_settings
1249
-     *
1250
-     * @return void
1251
-     * @throws EE_Error
1252
-     * @throws ReflectionException
1253
-     * @since 4.9.13
1254
-     */
1255
-    public function _update_tax_settings()
1256
-    {
1257
-        $tax_settings = EE_Config::instance()->tax_settings;
1258
-        if (! $tax_settings instanceof EE_Tax_Config) {
1259
-            $tax_settings = new EE_Tax_Config();
1260
-        }
1261
-        try {
1262
-            $tax_form = $this->tax_settings_form();
1263
-            // check for form submission
1264
-            if ($tax_form->was_submitted()) {
1265
-                // capture form data
1266
-                $tax_form->receive_form_submission();
1267
-                // validate form data
1268
-                if ($tax_form->is_valid()) {
1269
-                    // grab validated data from form
1270
-                    $valid_data = $tax_form->valid_data();
1271
-                    // set data on config
1272
-                    $tax_settings->prices_displayed_including_taxes =
1273
-                        $valid_data['tax_settings']['prices_displayed_including_taxes'];
1274
-                } else {
1275
-                    if ($tax_form->submission_error_message() !== '') {
1276
-                        EE_Error::add_error(
1277
-                            $tax_form->submission_error_message(),
1278
-                            __FILE__,
1279
-                            __FUNCTION__,
1280
-                            __LINE__
1281
-                        );
1282
-                    }
1283
-                }
1284
-            }
1285
-        } catch (EE_Error $e) {
1286
-            EE_Error::add_error($e->get_error(), __FILE__, __FUNCTION__, __LINE__);
1287
-        }
1288
-
1289
-        $what    = 'Tax Settings';
1290
-        $success = $this->_update_espresso_configuration(
1291
-            $what,
1292
-            $tax_settings,
1293
-            __FILE__,
1294
-            __FUNCTION__,
1295
-            __LINE__
1296
-        );
1297
-        $this->_redirect_after_action($success, $what, 'updated', ['action' => 'tax_settings']);
1298
-    }
1184
+	}
1185
+
1186
+
1187
+	/**
1188
+	 * @throws EE_Error
1189
+	 */
1190
+	protected function _tax_settings()
1191
+	{
1192
+		$this->_set_add_edit_form_tags('update_tax_settings');
1193
+		$this->_set_publish_post_box_vars(null, false, false, null, false);
1194
+		$this->_template_args['admin_page_content'] = $this->tax_settings_form()->get_html();
1195
+		$this->display_admin_page_with_sidebar();
1196
+	}
1197
+
1198
+
1199
+	/**
1200
+	 * @return EE_Form_Section_Proper
1201
+	 * @throws EE_Error
1202
+	 */
1203
+	protected function tax_settings_form(): EE_Form_Section_Proper
1204
+	{
1205
+		$tax_settings = EE_Config::instance()->tax_settings;
1206
+		return new EE_Form_Section_Proper(
1207
+			[
1208
+				'name'            => 'tax_settings_form',
1209
+				'html_id'         => 'tax_settings_form',
1210
+				'html_class'      => 'padding',
1211
+				'layout_strategy' => new EE_Div_Per_Section_Layout(),
1212
+				'subsections'     => apply_filters(
1213
+					'FHEE__Pricing_Admin_Page__tax_settings_form__form_subsections',
1214
+					[
1215
+						'tax_settings' => new EE_Form_Section_Proper(
1216
+							[
1217
+								'name'            => 'tax_settings_tbl',
1218
+								'html_id'         => 'tax_settings_tbl',
1219
+								'html_class'      => 'form-table',
1220
+								'layout_strategy' => new EE_Admin_Two_Column_Layout(),
1221
+								'subsections'     => [
1222
+									'prices_displayed_including_taxes' => new EE_Yes_No_Input(
1223
+										[
1224
+											'html_label_text'         => esc_html__(
1225
+												'Show Prices With Taxes Included?',
1226
+												'event_espresso'
1227
+											),
1228
+											'html_help_text'          => esc_html__(
1229
+												'Indicates whether or not to display prices with the taxes included',
1230
+												'event_espresso'
1231
+											),
1232
+											'default'                 => $tax_settings->prices_displayed_including_taxes
1233
+																		 ?? true,
1234
+											'display_html_label_text' => false,
1235
+										]
1236
+									),
1237
+								],
1238
+							]
1239
+						),
1240
+					]
1241
+				),
1242
+			]
1243
+		);
1244
+	}
1245
+
1246
+
1247
+	/**
1248
+	 * _update_tax_settings
1249
+	 *
1250
+	 * @return void
1251
+	 * @throws EE_Error
1252
+	 * @throws ReflectionException
1253
+	 * @since 4.9.13
1254
+	 */
1255
+	public function _update_tax_settings()
1256
+	{
1257
+		$tax_settings = EE_Config::instance()->tax_settings;
1258
+		if (! $tax_settings instanceof EE_Tax_Config) {
1259
+			$tax_settings = new EE_Tax_Config();
1260
+		}
1261
+		try {
1262
+			$tax_form = $this->tax_settings_form();
1263
+			// check for form submission
1264
+			if ($tax_form->was_submitted()) {
1265
+				// capture form data
1266
+				$tax_form->receive_form_submission();
1267
+				// validate form data
1268
+				if ($tax_form->is_valid()) {
1269
+					// grab validated data from form
1270
+					$valid_data = $tax_form->valid_data();
1271
+					// set data on config
1272
+					$tax_settings->prices_displayed_including_taxes =
1273
+						$valid_data['tax_settings']['prices_displayed_including_taxes'];
1274
+				} else {
1275
+					if ($tax_form->submission_error_message() !== '') {
1276
+						EE_Error::add_error(
1277
+							$tax_form->submission_error_message(),
1278
+							__FILE__,
1279
+							__FUNCTION__,
1280
+							__LINE__
1281
+						);
1282
+					}
1283
+				}
1284
+			}
1285
+		} catch (EE_Error $e) {
1286
+			EE_Error::add_error($e->get_error(), __FILE__, __FUNCTION__, __LINE__);
1287
+		}
1288
+
1289
+		$what    = 'Tax Settings';
1290
+		$success = $this->_update_espresso_configuration(
1291
+			$what,
1292
+			$tax_settings,
1293
+			__FILE__,
1294
+			__FUNCTION__,
1295
+			__LINE__
1296
+		);
1297
+		$this->_redirect_after_action($success, $what, 'updated', ['action' => 'tax_settings']);
1298
+	}
1299 1299
 }
Please login to merge, or discard this patch.
core/admin/EE_Admin_Page.core.php 2 patches
Indentation   +4184 added lines, -4184 removed lines patch added patch discarded remove patch
@@ -22,4272 +22,4272 @@
 block discarded – undo
22 22
  */
23 23
 abstract class EE_Admin_Page extends EE_Base implements InterminableInterface
24 24
 {
25
-    /**
26
-     * @var EE_Admin_Config
27
-     */
28
-    protected $admin_config;
25
+	/**
26
+	 * @var EE_Admin_Config
27
+	 */
28
+	protected $admin_config;
29 29
 
30
-    /**
31
-     * @var LoaderInterface
32
-     */
33
-    protected $loader;
30
+	/**
31
+	 * @var LoaderInterface
32
+	 */
33
+	protected $loader;
34 34
 
35
-    /**
36
-     * @var RequestInterface
37
-     */
38
-    protected $request;
35
+	/**
36
+	 * @var RequestInterface
37
+	 */
38
+	protected $request;
39 39
 
40
-    // set in _init_page_props()
41
-    public $page_slug;
40
+	// set in _init_page_props()
41
+	public $page_slug;
42 42
 
43
-    public $page_label;
43
+	public $page_label;
44 44
 
45
-    public $page_folder;
45
+	public $page_folder;
46 46
 
47
-    // set in define_page_props()
48
-    protected $_admin_base_url;
47
+	// set in define_page_props()
48
+	protected $_admin_base_url;
49 49
 
50
-    protected $_admin_base_path;
50
+	protected $_admin_base_path;
51 51
 
52
-    protected $_admin_page_title;
52
+	protected $_admin_page_title;
53 53
 
54
-    protected $_labels;
54
+	protected $_labels;
55 55
 
56 56
 
57
-    // set early within EE_Admin_Init
58
-    protected $_wp_page_slug;
57
+	// set early within EE_Admin_Init
58
+	protected $_wp_page_slug;
59 59
 
60
-    // nav tabs
61
-    protected $_nav_tabs;
60
+	// nav tabs
61
+	protected $_nav_tabs;
62 62
 
63
-    protected $_default_nav_tab_name;
63
+	protected $_default_nav_tab_name;
64 64
 
65 65
 
66
-    // template variables (used by templates)
67
-    protected $_template_path;
66
+	// template variables (used by templates)
67
+	protected $_template_path;
68 68
 
69
-    protected $_column_template_path;
69
+	protected $_column_template_path;
70 70
 
71
-    /**
72
-     * @var array $_template_args
73
-     */
74
-    protected $_template_args = [];
71
+	/**
72
+	 * @var array $_template_args
73
+	 */
74
+	protected $_template_args = [];
75 75
 
76
-    /**
77
-     * this will hold the list table object for a given view.
78
-     *
79
-     * @var EE_Admin_List_Table $_list_table_object
80
-     */
81
-    protected $_list_table_object;
76
+	/**
77
+	 * this will hold the list table object for a given view.
78
+	 *
79
+	 * @var EE_Admin_List_Table $_list_table_object
80
+	 */
81
+	protected $_list_table_object;
82 82
 
83
-    // boolean
84
-    protected $_is_UI_request; // this starts at null so we can have no header routes progress through two states.
83
+	// boolean
84
+	protected $_is_UI_request; // this starts at null so we can have no header routes progress through two states.
85 85
 
86
-    protected $_routing;
86
+	protected $_routing;
87 87
 
88
-    // list table args
89
-    protected $_view;
88
+	// list table args
89
+	protected $_view;
90 90
 
91
-    protected $_views;
91
+	protected $_views;
92 92
 
93 93
 
94
-    // action => method pairs used for routing incoming requests
95
-    protected $_page_routes;
94
+	// action => method pairs used for routing incoming requests
95
+	protected $_page_routes;
96 96
 
97
-    /**
98
-     * @var array $_page_config
99
-     */
100
-    protected $_page_config;
97
+	/**
98
+	 * @var array $_page_config
99
+	 */
100
+	protected $_page_config;
101 101
 
102
-    /**
103
-     * the current page route and route config
104
-     *
105
-     * @var string $_route
106
-     */
107
-    protected $_route;
102
+	/**
103
+	 * the current page route and route config
104
+	 *
105
+	 * @var string $_route
106
+	 */
107
+	protected $_route;
108 108
 
109
-    /**
110
-     * @var string $_cpt_route
111
-     */
112
-    protected $_cpt_route;
109
+	/**
110
+	 * @var string $_cpt_route
111
+	 */
112
+	protected $_cpt_route;
113 113
 
114
-    /**
115
-     * @var array $_route_config
116
-     */
117
-    protected $_route_config;
114
+	/**
115
+	 * @var array $_route_config
116
+	 */
117
+	protected $_route_config;
118 118
 
119
-    /**
120
-     * Used to hold default query args for list table routes to help preserve stickiness of filters for carried out
121
-     * actions.
122
-     *
123
-     * @since 4.6.x
124
-     * @var array.
125
-     */
126
-    protected $_default_route_query_args;
119
+	/**
120
+	 * Used to hold default query args for list table routes to help preserve stickiness of filters for carried out
121
+	 * actions.
122
+	 *
123
+	 * @since 4.6.x
124
+	 * @var array.
125
+	 */
126
+	protected $_default_route_query_args;
127 127
 
128
-    // set via request page and action args.
129
-    protected $_current_page;
128
+	// set via request page and action args.
129
+	protected $_current_page;
130 130
 
131
-    protected $_current_view;
131
+	protected $_current_view;
132 132
 
133
-    protected $_current_page_view_url;
133
+	protected $_current_page_view_url;
134 134
 
135
-    /**
136
-     * unprocessed value for the 'action' request param (default '')
137
-     *
138
-     * @var string
139
-     */
140
-    protected $raw_req_action = '';
135
+	/**
136
+	 * unprocessed value for the 'action' request param (default '')
137
+	 *
138
+	 * @var string
139
+	 */
140
+	protected $raw_req_action = '';
141 141
 
142
-    /**
143
-     * unprocessed value for the 'page' request param (default '')
144
-     *
145
-     * @var string
146
-     */
147
-    protected $raw_req_page = '';
148
-
149
-    /**
150
-     * sanitized request action (and nonce)
151
-     *
152
-     * @var string
153
-     */
154
-    protected $_req_action = '';
155
-
156
-    /**
157
-     * sanitized request action nonce
158
-     *
159
-     * @var string
160
-     */
161
-    protected $_req_nonce = '';
162
-
163
-    /**
164
-     * @var string
165
-     */
166
-    protected $_search_btn_label = '';
167
-
168
-    /**
169
-     * @var string
170
-     */
171
-    protected $_search_box_callback = '';
172
-
173
-    /**
174
-     * @var WP_Screen
175
-     */
176
-    protected $_current_screen;
177
-
178
-    // for holding EE_Admin_Hooks object when needed (set via set_hook_object())
179
-    protected $_hook_obj;
180
-
181
-    // for holding incoming request data
182
-    protected $_req_data = [];
183
-
184
-    // yes / no array for admin form fields
185
-    protected $_yes_no_values = [];
186
-
187
-    // some default things shared by all child classes
188
-    protected $_default_espresso_metaboxes = [
189
-        '_espresso_news_post_box',
190
-        '_espresso_links_post_box',
191
-        '_espresso_ratings_request',
192
-        '_espresso_sponsors_post_box',
193
-    ];
194
-
195
-    /**
196
-     * @var EE_Registry
197
-     */
198
-    protected $EE;
199
-
200
-
201
-    /**
202
-     * This is just a property that flags whether the given route is a caffeinated route or not.
203
-     *
204
-     * @var boolean
205
-     */
206
-    protected $_is_caf = false;
207
-
208
-    /**
209
-     * whether or not initializePage() has run
210
-     *
211
-     * @var boolean
212
-     */
213
-    protected $initialized = false;
214
-
215
-    /**
216
-     * @var FeatureFlags
217
-     */
218
-    protected $feature;
219
-
220
-
221
-    /**
222
-     * @var string
223
-     */
224
-    protected $class_name;
225
-
226
-    /**
227
-     * if the current class is an admin page extension, like: Extend_Events_Admin_Page,
228
-     * then this would be the parent classname: Events_Admin_Page
229
-     *
230
-     * @var string
231
-     */
232
-    protected $base_class_name;
233
-
234
-
235
-    /**
236
-     * @Constructor
237
-     * @param bool $routing indicate whether we want to just load the object and handle routing or just load the object.
238
-     * @throws InvalidArgumentException
239
-     * @throws InvalidDataTypeException
240
-     * @throws InvalidInterfaceException
241
-     * @throws ReflectionException
242
-     */
243
-    public function __construct($routing = true)
244
-    {
245
-        $this->loader = LoaderFactory::getLoader();
246
-        $this->admin_config = $this->loader->getShared('EE_Admin_Config');
247
-        $this->feature = $this->loader->getShared(FeatureFlags::class);
248
-        $this->request = $this->loader->getShared(RequestInterface::class);
249
-        // routing enabled?
250
-        $this->_routing = $routing;
251
-
252
-        $this->class_name = get_class($this);
253
-        $this->base_class_name = strpos($this->class_name, 'Extend_') === 0
254
-            ? str_replace('Extend_', '', $this->class_name)
255
-            : '';
256
-
257
-        if (strpos($this->_get_dir(), 'caffeinated') !== false) {
258
-            $this->_is_caf = true;
259
-        }
260
-        $this->_yes_no_values = [
261
-            ['id' => true, 'text' => esc_html__('Yes', 'event_espresso')],
262
-            ['id' => false, 'text' => esc_html__('No', 'event_espresso')],
263
-        ];
264
-        // set the _req_data property.
265
-        $this->_req_data = $this->request->requestParams();
266
-    }
267
-
268
-
269
-    /**
270
-     * @return EE_Admin_Config
271
-     */
272
-    public function adminConfig(): EE_Admin_Config
273
-    {
274
-        return $this->admin_config;
275
-    }
276
-
277
-
278
-    /**
279
-     * @return FeatureFlags
280
-     */
281
-    public function feature(): FeatureFlags
282
-    {
283
-        return $this->feature;
284
-    }
285
-
286
-
287
-    /**
288
-     * This logic used to be in the constructor, but that caused a chicken <--> egg scenario
289
-     * for child classes that needed to set properties prior to these methods getting called,
290
-     * but also needed the parent class to have its construction completed as well.
291
-     * Bottom line is that constructors should ONLY be used for setting initial properties
292
-     * and any complex initialization logic should only run after instantiation is complete.
293
-     *
294
-     * This method gets called immediately after construction from within
295
-     *      EE_Admin_Page_Init::_initialize_admin_page()
296
-     *
297
-     * @throws EE_Error
298
-     * @throws InvalidArgumentException
299
-     * @throws InvalidDataTypeException
300
-     * @throws InvalidInterfaceException
301
-     * @throws ReflectionException
302
-     * @since $VID:$
303
-     */
304
-    public function initializePage()
305
-    {
306
-        if ($this->initialized) {
307
-            return;
308
-        }
309
-        // set initial page props (child method)
310
-        $this->_init_page_props();
311
-        // set global defaults
312
-        $this->_set_defaults();
313
-        // set early because incoming requests could be ajax related and we need to register those hooks.
314
-        $this->_global_ajax_hooks();
315
-        $this->_ajax_hooks();
316
-        // other_page_hooks have to be early too.
317
-        $this->_do_other_page_hooks();
318
-        // set up page dependencies
319
-        $this->_before_page_setup();
320
-        $this->_page_setup();
321
-        $this->initialized = true;
322
-    }
323
-
324
-
325
-    /**
326
-     * _init_page_props
327
-     * Child classes use to set at least the following properties:
328
-     * $page_slug.
329
-     * $page_label.
330
-     *
331
-     * @abstract
332
-     * @return void
333
-     */
334
-    abstract protected function _init_page_props();
335
-
336
-
337
-    /**
338
-     * _ajax_hooks
339
-     * child classes put all their add_action('wp_ajax_{name_of_hook}') hooks in here.
340
-     * Note: within the ajax callback methods.
341
-     *
342
-     * @abstract
343
-     * @return void
344
-     */
345
-    abstract protected function _ajax_hooks();
346
-
347
-
348
-    /**
349
-     * _define_page_props
350
-     * child classes define page properties in here.  Must include at least:
351
-     * $_admin_base_url = base_url for all admin pages
352
-     * $_admin_page_title = default admin_page_title for admin pages
353
-     * $_labels = array of default labels for various automatically generated elements:
354
-     *    array(
355
-     *        'buttons' => array(
356
-     *            'add' => esc_html__('label for add new button'),
357
-     *            'edit' => esc_html__('label for edit button'),
358
-     *            'delete' => esc_html__('label for delete button')
359
-     *            )
360
-     *        )
361
-     *
362
-     * @abstract
363
-     * @return void
364
-     */
365
-    abstract protected function _define_page_props();
366
-
367
-
368
-    /**
369
-     * _set_page_routes
370
-     * child classes use this to define the page routes for all subpages handled by the class.  Page routes are
371
-     * assigned to a action => method pairs in an array and to the $_page_routes property.  Each page route must also
372
-     * have a 'default' route. Here's the format
373
-     * $this->_page_routes = array(
374
-     *        'default' => array(
375
-     *            'func' => '_default_method_handling_route',
376
-     *            'args' => array('array','of','args'),
377
-     *            'noheader' => true, //add this in if this page route is processed before any headers are loaded (i.e.
378
-     *            ajax request, backend processing)
379
-     *            'headers_sent_route'=>'headers_route_reference', //add this if noheader=>true, and you want to load a
380
-     *            headers route after.  The string you enter here should match the defined route reference for a
381
-     *            headers sent route.
382
-     *            'capability' => 'route_capability', //indicate a string for minimum capability required to access
383
-     *            this route.
384
-     *            'obj_id' => 10 // if this route has an object id, then this can include it (used for capability
385
-     *            checks).
386
-     *        ),
387
-     *        'insert_item' => '_method_for_handling_insert_item' //this can be used if all we need to have is a
388
-     *        handling method.
389
-     *        )
390
-     * )
391
-     *
392
-     * @abstract
393
-     * @return void
394
-     */
395
-    abstract protected function _set_page_routes();
396
-
397
-
398
-    /**
399
-     * _set_page_config
400
-     * child classes use this to define the _page_config array for all subpages handled by the class. Each key in the
401
-     * array corresponds to the page_route for the loaded page. Format:
402
-     * $this->_page_config = array(
403
-     *        'default' => array(
404
-     *            'labels' => array(
405
-     *                'buttons' => array(
406
-     *                    'add' => esc_html__('label for adding item'),
407
-     *                    'edit' => esc_html__('label for editing item'),
408
-     *                    'delete' => esc_html__('label for deleting item')
409
-     *                ),
410
-     *                'publishbox' => esc_html__('Localized Title for Publish metabox', 'event_espresso')
411
-     *            ), //optional an array of custom labels for various automatically generated elements to use on the
412
-     *            page. If this isn't present then the defaults will be used as set for the $this->_labels in
413
-     *            _define_page_props() method
414
-     *            'nav' => array(
415
-     *                'label' => esc_html__('Label for Tab', 'event_espresso').
416
-     *                'url' => 'http://someurl', //automatically generated UNLESS you define
417
-     *                'css_class' => 'css-class', //automatically generated UNLESS you define
418
-     *                'order' => 10, //required to indicate tab position.
419
-     *                'persistent' => false //if you want the nav tab to ONLY display when the specific route is
420
-     *                displayed then add this parameter.
421
-     *            'list_table' => 'name_of_list_table' //string for list table class to be loaded for this admin_page.
422
-     *            'metaboxes' => array('metabox1', 'metabox2'), //if present this key indicates we want to load
423
-     *            metaboxes set for eventespresso admin pages.
424
-     *            'has_metaboxes' => true, //this boolean flag can simply be used to indicate if the route will have
425
-     *            metaboxes.  Typically this is used if the 'metaboxes' index is not used because metaboxes are added
426
-     *            later.  We just use this flag to make sure the necessary js gets enqueued on page load.
427
-     *            'has_help_popups' => false //defaults(true) //this boolean flag can simply be used to indicate if the
428
-     *            given route has help popups setup and if it does then we need to make sure thickbox is enqueued.
429
-     *            'columns' => array(4, 2), //this key triggers the setup of a page that uses columns (metaboxes).  The
430
-     *            array indicates the max number of columns (4) and the default number of columns on page load (2).
431
-     *            There is an option in the "screen_options" dropdown that is setup so users can pick what columns they
432
-     *            want to display.
433
-     *            'help_tabs' => array( //this is used for adding help tabs to a page
434
-     *                'tab_id' => array(
435
-     *                    'title' => 'tab_title',
436
-     *                    'filename' => 'name_of_file_containing_content', //this is the primary method for setting
437
-     *                    help tab content.  The fallback if it isn't present is to try a the callback.  Filename
438
-     *                    should match a file in the admin folder's "help_tabs" dir (ie..
439
-     *                    events/help_tabs/name_of_file_containing_content.help_tab.php)
440
-     *                    'callback' => 'callback_method_for_content', //if 'filename' isn't present then system will
441
-     *                    attempt to use the callback which should match the name of a method in the class
442
-     *                    ),
443
-     *                'tab2_id' => array(
444
-     *                    'title' => 'tab2 title',
445
-     *                    'filename' => 'file_name_2'
446
-     *                    'callback' => 'callback_method_for_content',
447
-     *                 ),
448
-     *            'help_sidebar' => 'callback_for_sidebar_content', //this is used for setting up the sidebar in the
449
-     *            help tab area on an admin page. @return void
450
-     *
451
-     * @abstract
452
-     */
453
-    abstract protected function _set_page_config();
454
-
455
-
456
-    /**
457
-     * _add_screen_options
458
-     * Child classes can add any extra wp_screen_options within this method using built-in WP functions/methods for
459
-     * doing so. Note child classes can also define _add_screen_options_($this->_current_view) to limit screen options
460
-     * to a particular view.
461
-     *
462
-     * @link   http://chrismarslender.com/wp-tutorials/wordpress-screen-options-tutorial/
463
-     *         see also WP_Screen object documents...
464
-     * @link   http://codex.wordpress.org/Class_Reference/WP_Screen
465
-     * @abstract
466
-     * @return void
467
-     */
468
-    abstract protected function _add_screen_options();
469
-
470
-
471
-    /**
472
-     * _add_feature_pointers
473
-     * Child classes should use this method for implementing any "feature pointers" (using built-in WP styling js).
474
-     * Note child classes can also define _add_feature_pointers_($this->_current_view) to limit screen options to a
475
-     * particular view. Note: this is just a placeholder for now.  Implementation will come down the road See:
476
-     * WP_Internal_Pointers class in wp-admin/includes/template.php for example (its a final class so can't be
477
-     * extended) also see:
478
-     *
479
-     * @link   http://eamann.com/tech/wordpress-portland/
480
-     * @abstract
481
-     * @return void
482
-     */
483
-    abstract protected function _add_feature_pointers();
484
-
485
-
486
-    /**
487
-     * load_scripts_styles
488
-     * child classes put their wp_enqueue_script and wp_enqueue_style hooks in here for anything they need loaded for
489
-     * their pages/subpages.  Note this is for all pages/subpages of the system.  You can also load only specific
490
-     * scripts/styles per view by putting them in a dynamic function in this format
491
-     * (load_scripts_styles_{$this->_current_view}) which matches your page route (action request arg)
492
-     *
493
-     * @abstract
494
-     * @return void
495
-     */
496
-    abstract public function load_scripts_styles();
497
-
498
-
499
-    /**
500
-     * admin_init
501
-     * Anything that should be set/executed at 'admin_init' WP hook runtime should be put in here.  This will apply to
502
-     * all pages/views loaded by child class.
503
-     *
504
-     * @abstract
505
-     * @return void
506
-     */
507
-    abstract public function admin_init();
508
-
509
-
510
-    /**
511
-     * admin_notices
512
-     * Anything triggered by the 'admin_notices' WP hook should be put in here.  This particular method will apply to
513
-     * all pages/views loaded by child class.
514
-     *
515
-     * @abstract
516
-     * @return void
517
-     */
518
-    abstract public function admin_notices();
519
-
520
-
521
-    /**
522
-     * admin_footer_scripts
523
-     * Anything triggered by the 'admin_print_footer_scripts' WP hook should be put in here. This particular method
524
-     * will apply to all pages/views loaded by child class.
525
-     *
526
-     * @return void
527
-     */
528
-    abstract public function admin_footer_scripts();
529
-
530
-
531
-    /**
532
-     * admin_footer
533
-     * anything triggered by the 'admin_footer' WP action hook should be added to here. This particular method will
534
-     * apply to all pages/views loaded by child class.
535
-     *
536
-     * @return void
537
-     */
538
-    public function admin_footer()
539
-    {
540
-    }
541
-
542
-
543
-    /**
544
-     * _global_ajax_hooks
545
-     * all global add_action('wp_ajax_{name_of_hook}') hooks in here.
546
-     * Note: within the ajax callback methods.
547
-     *
548
-     * @abstract
549
-     * @return void
550
-     */
551
-    protected function _global_ajax_hooks()
552
-    {
553
-        // for lazy loading of metabox content
554
-        add_action('wp_ajax_espresso-ajax-content', [$this, 'ajax_metabox_content'], 10);
555
-
556
-        add_action(
557
-            'wp_ajax_espresso_hide_status_change_notice',
558
-            [$this, 'hideStatusChangeNotice']
559
-        );
560
-        add_action(
561
-            'wp_ajax_nopriv_espresso_hide_status_change_notice',
562
-            [$this, 'hideStatusChangeNotice']
563
-        );
564
-    }
565
-
566
-
567
-    public function ajax_metabox_content()
568
-    {
569
-        $content_id  = $this->request->getRequestParam('contentid', '');
570
-        $content_url = $this->request->getRequestParam('contenturl', '', 'url');
571
-        EE_Admin_Page::cached_rss_display($content_id, $content_url);
572
-        wp_die();
573
-    }
574
-
575
-
576
-    public function hideStatusChangeNotice()
577
-    {
578
-        $response = [];
579
-        try {
580
-            /** @var StatusChangeNotice $status_change_notice */
581
-            $status_change_notice = $this->loader->getShared(
582
-                'EventEspresso\core\domain\services\admin\notices\status_change\StatusChangeNotice'
583
-            );
584
-            $response['success'] = $status_change_notice->dismiss() > -1;
585
-        } catch (Exception $exception) {
586
-            $response['errors'] = $exception->getMessage();
587
-        }
588
-        echo wp_json_encode($response);
589
-        exit();
590
-    }
591
-
592
-
593
-    /**
594
-     * allows extending classes do something specific before the parent constructor runs _page_setup().
595
-     *
596
-     * @return void
597
-     */
598
-    protected function _before_page_setup()
599
-    {
600
-        // default is to do nothing
601
-    }
602
-
603
-
604
-    /**
605
-     * Makes sure any things that need to be loaded early get handled.
606
-     * We also escape early here if the page requested doesn't match the object.
607
-     *
608
-     * @final
609
-     * @return void
610
-     * @throws EE_Error
611
-     * @throws InvalidArgumentException
612
-     * @throws ReflectionException
613
-     * @throws InvalidDataTypeException
614
-     * @throws InvalidInterfaceException
615
-     */
616
-    final protected function _page_setup()
617
-    {
618
-        // requires?
619
-        // admin_init stuff - global - we're setting this REALLY early
620
-        // so if EE_Admin pages have to hook into other WP pages they can.
621
-        // But keep in mind, not everything is available from the EE_Admin Page object at this point.
622
-        add_action('admin_init', [$this, 'admin_init_global'], 5);
623
-        // next verify if we need to load anything...
624
-        $this->_current_page = $this->request->getRequestParam('page', '', 'key');
625
-        $this->page_folder   = strtolower(
626
-            str_replace(['_Admin_Page', 'Extend_'], '', $this->class_name)
627
-        );
628
-        global $ee_menu_slugs;
629
-        $ee_menu_slugs = (array) $ee_menu_slugs;
630
-        if (
631
-            ! $this->request->isAjax()
632
-            && (! $this->_current_page || ! isset($ee_menu_slugs[ $this->_current_page ]))
633
-        ) {
634
-            return;
635
-        }
636
-        // because WP List tables have two duplicate select inputs for choosing bulk actions,
637
-        // we need to copy the action from the second to the first
638
-        $action     = $this->request->getRequestParam('action', '-1', 'key');
639
-        $action2    = $this->request->getRequestParam('action2', '-1', 'key');
640
-        $action     = $action !== '-1' ? $action : $action2;
641
-        $req_action = $action !== '-1' ? $action : 'default';
642
-
643
-        // if a specific 'route' has been set, and the action is 'default' OR we are doing_ajax
644
-        // then let's use the route as the action.
645
-        // This covers cases where we're coming in from a list table that isn't on the default route.
646
-        $route = $this->request->getRequestParam('route');
647
-        $this->_req_action = $route && ($req_action === 'default' || $this->request->isAjax())
648
-            ? $route
649
-            : $req_action;
650
-
651
-        $this->_current_view = $this->_req_action;
652
-        $this->_req_nonce    = $this->_req_action . '_nonce';
653
-        $this->_define_page_props();
654
-        $this->_current_page_view_url = add_query_arg(
655
-            ['page' => $this->_current_page, 'action' => $this->_current_view],
656
-            $this->_admin_base_url
657
-        );
658
-        // set page configs
659
-        $this->_set_page_routes();
660
-        $this->_set_page_config();
661
-        // let's include any referrer data in our default_query_args for this route for "stickiness".
662
-        if ($this->request->requestParamIsSet('wp_referer')) {
663
-            $wp_referer = $this->request->getRequestParam('wp_referer');
664
-            if ($wp_referer) {
665
-                $this->_default_route_query_args['wp_referer'] = $wp_referer;
666
-            }
667
-        }
668
-        // for caffeinated and other extended functionality.
669
-        //  If there is a _extend_page_config method
670
-        // then let's run that to modify the all the various page configuration arrays
671
-        if (method_exists($this, '_extend_page_config')) {
672
-            $this->_extend_page_config();
673
-        }
674
-        // for CPT and other extended functionality.
675
-        // If there is an _extend_page_config_for_cpt
676
-        // then let's run that to modify all the various page configuration arrays.
677
-        if (method_exists($this, '_extend_page_config_for_cpt')) {
678
-            $this->_extend_page_config_for_cpt();
679
-        }
680
-        // filter routes and page_config so addons can add their stuff. Filtering done per class
681
-        $this->_page_routes = apply_filters(
682
-            'FHEE__' . $this->class_name . '__page_setup__page_routes',
683
-            $this->_page_routes,
684
-            $this
685
-        );
686
-        $this->_page_config = apply_filters(
687
-            'FHEE__' . $this->class_name . '__page_setup__page_config',
688
-            $this->_page_config,
689
-            $this
690
-        );
691
-        if ($this->base_class_name !== '') {
692
-            $this->_page_routes = apply_filters(
693
-                'FHEE__' . $this->base_class_name . '__page_setup__page_routes',
694
-                $this->_page_routes,
695
-                $this
696
-            );
697
-            $this->_page_config = apply_filters(
698
-                'FHEE__' . $this->base_class_name . '__page_setup__page_config',
699
-                $this->_page_config,
700
-                $this
701
-            );
702
-        }
703
-        // if AHEE__EE_Admin_Page__route_admin_request_$this->_current_view method is present
704
-        // then we call it hooked into the AHEE__EE_Admin_Page__route_admin_request action
705
-        if (method_exists($this, 'AHEE__EE_Admin_Page__route_admin_request_' . $this->_current_view)) {
706
-            add_action(
707
-                'AHEE__EE_Admin_Page__route_admin_request',
708
-                [$this, 'AHEE__EE_Admin_Page__route_admin_request_' . $this->_current_view],
709
-                10,
710
-                2
711
-            );
712
-        }
713
-        // next route only if routing enabled
714
-        if ($this->_routing && ! $this->request->isAjax()) {
715
-            $this->_verify_routes();
716
-            // next let's just check user_access and kill if no access
717
-            $this->check_user_access();
718
-            if ($this->_is_UI_request) {
719
-                // admin_init stuff - global, all views for this page class, specific view
720
-                add_action('admin_init', [$this, 'admin_init'], 10);
721
-                if (method_exists($this, 'admin_init_' . $this->_current_view)) {
722
-                    add_action('admin_init', [$this, 'admin_init_' . $this->_current_view], 15);
723
-                }
724
-            } else {
725
-                // hijack regular WP loading and route admin request immediately
726
-                @ini_set('memory_limit', apply_filters('admin_memory_limit', WP_MAX_MEMORY_LIMIT));
727
-                $this->route_admin_request();
728
-            }
729
-        }
730
-    }
731
-
732
-
733
-    /**
734
-     * Provides a way for related child admin pages to load stuff on the loaded admin page.
735
-     *
736
-     * @return void
737
-     * @throws EE_Error
738
-     */
739
-    private function _do_other_page_hooks()
740
-    {
741
-        $registered_pages = apply_filters('FHEE_do_other_page_hooks_' . $this->page_slug, []);
742
-        foreach ($registered_pages as $page) {
743
-            // now let's setup the file name and class that should be present
744
-            $classname = str_replace('.class.php', '', $page);
745
-            // autoloaders should take care of loading file
746
-            if (! class_exists($classname)) {
747
-                $error_msg[] = sprintf(
748
-                    esc_html__(
749
-                        'Something went wrong with loading the %s admin hooks page.',
750
-                        'event_espresso'
751
-                    ),
752
-                    $page
753
-                );
754
-                $error_msg[] = $error_msg[0]
755
-                               . "\r\n"
756
-                               . sprintf(
757
-                                   esc_html__(
758
-                                       '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',
759
-                                       'event_espresso'
760
-                                   ),
761
-                                   $page,
762
-                                   '<br />',
763
-                                   '<strong>' . $classname . '</strong>'
764
-                               );
765
-                throw new EE_Error(implode('||', $error_msg));
766
-            }
767
-            // notice we are passing the instance of this class to the hook object.
768
-            $this->loader->getShared($classname, [$this]);
769
-        }
770
-    }
771
-
772
-
773
-    /**
774
-     * @throws ReflectionException
775
-     * @throws EE_Error
776
-     */
777
-    public function load_page_dependencies()
778
-    {
779
-        try {
780
-            $this->_load_page_dependencies();
781
-        } catch (EE_Error $e) {
782
-            $e->get_error();
783
-        }
784
-    }
785
-
786
-
787
-    /**
788
-     * load_page_dependencies
789
-     * loads things specific to this page class when its loaded.  Really helps with efficiency.
790
-     *
791
-     * @return void
792
-     * @throws DomainException
793
-     * @throws EE_Error
794
-     * @throws InvalidArgumentException
795
-     * @throws InvalidDataTypeException
796
-     * @throws InvalidInterfaceException
797
-     */
798
-    protected function _load_page_dependencies()
799
-    {
800
-        // let's set the current_screen and screen options to override what WP set
801
-        $this->_current_screen = get_current_screen();
802
-        // load admin_notices - global, page class, and view specific
803
-        add_action('admin_notices', [$this, 'admin_notices_global'], 5);
804
-        add_action('admin_notices', [$this, 'admin_notices'], 10);
805
-        if (method_exists($this, 'admin_notices_' . $this->_current_view)) {
806
-            add_action('admin_notices', [$this, 'admin_notices_' . $this->_current_view], 15);
807
-        }
808
-        // load network admin_notices - global, page class, and view specific
809
-        add_action('network_admin_notices', [$this, 'network_admin_notices_global'], 5);
810
-        if (method_exists($this, 'network_admin_notices_' . $this->_current_view)) {
811
-            add_action('network_admin_notices', [$this, 'network_admin_notices_' . $this->_current_view]);
812
-        }
813
-        // this will save any per_page screen options if they are present
814
-        $this->_set_per_page_screen_options();
815
-        // setup list table properties
816
-        $this->_set_list_table();
817
-        // child classes can "register" a metabox to be automatically handled via the _page_config array property.
818
-        // However in some cases the metaboxes will need to be added within a route handling callback.
819
-        $this->_add_registered_meta_boxes();
820
-        $this->_add_screen_columns();
821
-        // add screen options - global, page child class, and view specific
822
-        $this->_add_global_screen_options();
823
-        $this->_add_screen_options();
824
-        $add_screen_options = "_add_screen_options_{$this->_current_view}";
825
-        if (method_exists($this, $add_screen_options)) {
826
-            $this->{$add_screen_options}();
827
-        }
828
-        // add help tab(s) - set via page_config and qtips.
829
-        $this->_add_help_tabs();
830
-        $this->_add_qtips();
831
-        // add feature_pointers - global, page child class, and view specific
832
-        $this->_add_feature_pointers();
833
-        $this->_add_global_feature_pointers();
834
-        $add_feature_pointer = "_add_feature_pointer_{$this->_current_view}";
835
-        if (method_exists($this, $add_feature_pointer)) {
836
-            $this->{$add_feature_pointer}();
837
-        }
838
-        // enqueue scripts/styles - global, page class, and view specific
839
-        add_action('admin_enqueue_scripts', [$this, 'load_global_scripts_styles'], 5);
840
-        add_action('admin_enqueue_scripts', [$this, 'load_scripts_styles'], 10);
841
-        if (method_exists($this, "load_scripts_styles_{$this->_current_view}")) {
842
-            add_action('admin_enqueue_scripts', [$this, "load_scripts_styles_{$this->_current_view}"], 15);
843
-        }
844
-        add_action('admin_enqueue_scripts', [$this, 'admin_footer_scripts_eei18n_js_strings'], 100);
845
-        // admin_print_footer_scripts - global, page child class, and view specific.
846
-        // NOTE, despite the name, whenever possible, scripts should NOT be loaded using this.
847
-        // In most cases that's doing_it_wrong().  But adding hidden container elements etc.
848
-        // is a good use case. Notice the late priority we're giving these
849
-        add_action('admin_print_footer_scripts', [$this, 'admin_footer_scripts_global'], 99);
850
-        add_action('admin_print_footer_scripts', [$this, 'admin_footer_scripts'], 100);
851
-        if (method_exists($this, "admin_footer_scripts_{$this->_current_view}")) {
852
-            add_action('admin_print_footer_scripts', [$this, "admin_footer_scripts_{$this->_current_view}"], 101);
853
-        }
854
-        // admin footer scripts
855
-        add_action('admin_footer', [$this, 'admin_footer_global'], 99);
856
-        add_action('admin_footer', [$this, 'admin_footer'], 100);
857
-        if (method_exists($this, "admin_footer_{$this->_current_view}")) {
858
-            add_action('admin_footer', [$this, "admin_footer_{$this->_current_view}"], 101);
859
-        }
860
-        do_action('FHEE__EE_Admin_Page___load_page_dependencies__after_load', $this->page_slug);
861
-        // targeted hook
862
-        do_action(
863
-            "FHEE__EE_Admin_Page___load_page_dependencies__after_load__{$this->page_slug}__{$this->_req_action}"
864
-        );
865
-    }
866
-
867
-
868
-    /**
869
-     * _set_defaults
870
-     * This sets some global defaults for class properties.
871
-     */
872
-    private function _set_defaults()
873
-    {
874
-        $this->_current_screen       = $this->_admin_page_title = $this->_req_action = $this->_req_nonce = null;
875
-        $this->_event                = $this->_template_path = $this->_column_template_path = null;
876
-        $this->_nav_tabs             = $this->_views = $this->_page_routes = [];
877
-        $this->_page_config          = $this->_default_route_query_args = [];
878
-        $this->_default_nav_tab_name = 'overview';
879
-        // init template args
880
-        $this->_template_args = [
881
-            'admin_page_header'  => '',
882
-            'admin_page_content' => '',
883
-            'post_body_content'  => '',
884
-            'before_list_table'  => '',
885
-            'after_list_table'   => '',
886
-        ];
887
-    }
888
-
889
-
890
-    /**
891
-     * route_admin_request
892
-     *
893
-     * @return void
894
-     * @throws InvalidArgumentException
895
-     * @throws InvalidInterfaceException
896
-     * @throws InvalidDataTypeException
897
-     * @throws EE_Error
898
-     * @throws ReflectionException
899
-     * @see    _route_admin_request()
900
-     */
901
-    public function route_admin_request()
902
-    {
903
-        try {
904
-            $this->_route_admin_request();
905
-        } catch (EE_Error $e) {
906
-            $e->get_error();
907
-        }
908
-    }
909
-
910
-
911
-    public function set_wp_page_slug($wp_page_slug)
912
-    {
913
-        $this->_wp_page_slug = $wp_page_slug;
914
-        // if in network admin then we need to append "-network" to the page slug. Why? Because that's how WP rolls...
915
-        if (is_network_admin()) {
916
-            $this->_wp_page_slug .= '-network';
917
-        }
918
-    }
919
-
920
-
921
-    /**
922
-     * _verify_routes
923
-     * All this method does is verify the incoming request and make sure that routes exist for it.  We do this early so
924
-     * we know if we need to drop out.
925
-     *
926
-     * @return bool
927
-     * @throws EE_Error
928
-     */
929
-    protected function _verify_routes()
930
-    {
931
-        do_action('AHEE_log', __FILE__, __FUNCTION__, '');
932
-        if (! $this->_current_page && ! $this->request->isAjax()) {
933
-            return false;
934
-        }
935
-        $this->_route = false;
936
-        // check that the page_routes array is not empty
937
-        if (empty($this->_page_routes)) {
938
-            // user error msg
939
-            $error_msg = sprintf(
940
-                esc_html__('No page routes have been set for the %s admin page.', 'event_espresso'),
941
-                $this->_admin_page_title
942
-            );
943
-            // developer error msg
944
-            $error_msg .= '||' . $error_msg
945
-                          . esc_html__(
946
-                              ' Make sure the "set_page_routes()" method exists, and is setting the "_page_routes" array properly.',
947
-                              'event_espresso'
948
-                          );
949
-            throw new EE_Error($error_msg);
950
-        }
951
-        // and that the requested page route exists
952
-        if (array_key_exists($this->_req_action, $this->_page_routes)) {
953
-            $this->_route        = $this->_page_routes[ $this->_req_action ];
954
-            $this->_route_config = $this->_page_config[ $this->_req_action ] ?? [];
955
-        } else {
956
-            // user error msg
957
-            $error_msg = sprintf(
958
-                esc_html__(
959
-                    'The requested page route does not exist for the %s admin page.',
960
-                    'event_espresso'
961
-                ),
962
-                $this->_admin_page_title
963
-            );
964
-            // developer error msg
965
-            $error_msg .= '||' . $error_msg
966
-                          . sprintf(
967
-                              esc_html__(
968
-                                  ' Create a key in the "_page_routes" array named "%s" and set its value to the appropriate method.',
969
-                                  'event_espresso'
970
-                              ),
971
-                              $this->_req_action
972
-                          );
973
-            throw new EE_Error($error_msg);
974
-        }
975
-        // and that a default route exists
976
-        if (! array_key_exists('default', $this->_page_routes)) {
977
-            // user error msg
978
-            $error_msg = sprintf(
979
-                esc_html__(
980
-                    'A default page route has not been set for the % admin page.',
981
-                    'event_espresso'
982
-                ),
983
-                $this->_admin_page_title
984
-            );
985
-            // developer error msg
986
-            $error_msg .= '||' . $error_msg
987
-                          . esc_html__(
988
-                              ' Create a key in the "_page_routes" array named "default" and set its value to your default page method.',
989
-                              'event_espresso'
990
-                          );
991
-            throw new EE_Error($error_msg);
992
-        }
993
-
994
-        // first lets' catch if the UI request has EVER been set.
995
-        if ($this->_is_UI_request === null) {
996
-            // lets set if this is a UI request or not.
997
-            $this->_is_UI_request = ! $this->request->getRequestParam('noheader', false, 'bool');
998
-            // wait a minute... we might have a noheader in the route array
999
-            $this->_is_UI_request = ! (
1000
-                is_array($this->_route) && isset($this->_route['noheader']) && $this->_route['noheader']
1001
-            )
1002
-                ? $this->_is_UI_request
1003
-                : false;
1004
-        }
1005
-        $this->_set_current_labels();
1006
-        return true;
1007
-    }
1008
-
1009
-
1010
-    /**
1011
-     * this method simply verifies a given route and makes sure its an actual route available for the loaded page
1012
-     *
1013
-     * @param string $route the route name we're verifying
1014
-     * @return bool we'll throw an exception if this isn't a valid route.
1015
-     * @throws EE_Error
1016
-     */
1017
-    protected function _verify_route($route)
1018
-    {
1019
-        if (array_key_exists($this->_req_action, $this->_page_routes)) {
1020
-            return true;
1021
-        }
1022
-        // user error msg
1023
-        $error_msg = sprintf(
1024
-            esc_html__('The given page route does not exist for the %s admin page.', 'event_espresso'),
1025
-            $this->_admin_page_title
1026
-        );
1027
-        // developer error msg
1028
-        $error_msg .= '||' . $error_msg
1029
-                      . sprintf(
1030
-                          esc_html__(
1031
-                              ' Check the route you are using in your method (%s) and make sure it matches a route set in your "_page_routes" array property',
1032
-                              'event_espresso'
1033
-                          ),
1034
-                          $route
1035
-                      );
1036
-        throw new EE_Error($error_msg);
1037
-    }
1038
-
1039
-
1040
-    /**
1041
-     * perform nonce verification
1042
-     * This method has be encapsulated here so that any ajax requests that bypass normal routes can verify their nonces
1043
-     * using this method (and save retyping!)
1044
-     *
1045
-     * @param string $nonce     The nonce sent
1046
-     * @param string $nonce_ref The nonce reference string (name0)
1047
-     * @return void
1048
-     * @throws EE_Error
1049
-     * @throws InvalidArgumentException
1050
-     * @throws InvalidDataTypeException
1051
-     * @throws InvalidInterfaceException
1052
-     */
1053
-    protected function _verify_nonce($nonce, $nonce_ref)
1054
-    {
1055
-        // verify nonce against expected value
1056
-        if (! wp_verify_nonce($nonce, $nonce_ref)) {
1057
-            // these are not the droids you are looking for !!!
1058
-            $msg = sprintf(
1059
-                esc_html__('%sNonce Fail.%s', 'event_espresso'),
1060
-                '<a href="https://www.youtube.com/watch?v=56_S0WeTkzs">',
1061
-                '</a>'
1062
-            );
1063
-            if (WP_DEBUG) {
1064
-                $msg .= "\n  ";
1065
-                $msg .= sprintf(
1066
-                    esc_html__(
1067
-                        'In order to dynamically generate nonces for your actions, use the %s::add_query_args_and_nonce() method. May the Nonce be with you!',
1068
-                        'event_espresso'
1069
-                    ),
1070
-                    __CLASS__
1071
-                );
1072
-            }
1073
-            if (! $this->request->isAjax()) {
1074
-                wp_die($msg);
1075
-            }
1076
-            EE_Error::add_error($msg, __FILE__, __FUNCTION__, __LINE__);
1077
-            $this->_return_json();
1078
-        }
1079
-    }
1080
-
1081
-
1082
-    /**
1083
-     * _route_admin_request()
1084
-     * Meat and potatoes of the class.  Basically, this dude checks out what's being requested and sees if there are
1085
-     * some doodads to work the magic and handle the flingjangy. Translation:  Checks if the requested action is listed
1086
-     * in the page routes and then will try to load the corresponding method.
1087
-     *
1088
-     * @return void
1089
-     * @throws EE_Error
1090
-     * @throws InvalidArgumentException
1091
-     * @throws InvalidDataTypeException
1092
-     * @throws InvalidInterfaceException
1093
-     * @throws ReflectionException
1094
-     */
1095
-    protected function _route_admin_request()
1096
-    {
1097
-        if (! $this->_is_UI_request) {
1098
-            $this->_verify_routes();
1099
-        }
1100
-        $nonce_check = ! isset($this->_route_config['require_nonce']) || $this->_route_config['require_nonce'];
1101
-        if ($this->_req_action !== 'default' && $nonce_check) {
1102
-            // set nonce from post data
1103
-            $nonce = $this->request->getRequestParam($this->_req_nonce, '');
1104
-            $this->_verify_nonce($nonce, $this->_req_nonce);
1105
-        }
1106
-        // set the nav_tabs array but ONLY if this is  UI_request
1107
-        if ($this->_is_UI_request) {
1108
-            $this->_set_nav_tabs();
1109
-        }
1110
-        // grab callback function
1111
-        $func = is_array($this->_route) && isset($this->_route['func']) ? $this->_route['func'] : $this->_route;
1112
-        // check if callback has args
1113
-        $args      = is_array($this->_route) && isset($this->_route['args']) ? $this->_route['args'] : [];
1114
-        $error_msg = '';
1115
-        // action right before calling route
1116
-        // (hook is something like 'AHEE__Registrations_Admin_Page__route_admin_request')
1117
-        if (! did_action('AHEE__EE_Admin_Page__route_admin_request')) {
1118
-            do_action('AHEE__EE_Admin_Page__route_admin_request', $this->_current_view, $this);
1119
-        }
1120
-        // strip _wp_http_referer from the server REQUEST_URI
1121
-        // else it grows in length on every submission due to recursion,
1122
-        // ultimately causing a "Request-URI Too Large" error
1123
-        $request_uri = remove_query_arg(
1124
-            '_wp_http_referer',
1125
-            wp_unslash($this->request->getServerParam('REQUEST_URI'))
1126
-        );
1127
-        // set new value in both our Request object and the super global
1128
-        $this->request->setServerParam('REQUEST_URI', $request_uri, true);
1129
-        if (! empty($func)) {
1130
-            if (is_array($func)) {
1131
-                [$class, $method] = $func;
1132
-            } elseif (strpos($func, '::') !== false) {
1133
-                [$class, $method] = explode('::', $func);
1134
-            } else {
1135
-                $class  = $this;
1136
-                $method = $func;
1137
-            }
1138
-            if (! (is_object($class) && $class === $this)) {
1139
-                // send along this admin page object for access by addons.
1140
-                $args['admin_page_object'] = $this;
1141
-            }
1142
-            if (
1143
-                // is it a method on a class that doesn't work?
1144
-                (
1145
-                    (
1146
-                        method_exists($class, $method)
1147
-                        && call_user_func_array([$class, $method], $args) === false
1148
-                    )
1149
-                    && (
1150
-                        // is it a standalone function that doesn't work?
1151
-                        function_exists($method)
1152
-                        && call_user_func_array(
1153
-                            $func,
1154
-                            array_merge(['admin_page_object' => $this], $args)
1155
-                        ) === false
1156
-                    )
1157
-                )
1158
-                || (
1159
-                    // is it neither a class method NOR a standalone function?
1160
-                    ! method_exists($class, $method)
1161
-                    && ! function_exists($method)
1162
-                )
1163
-            ) {
1164
-                // user error msg
1165
-                $error_msg = esc_html__(
1166
-                    'An error occurred. The  requested page route could not be found.',
1167
-                    'event_espresso'
1168
-                );
1169
-                // developer error msg
1170
-                $error_msg .= '||';
1171
-                $error_msg .= sprintf(
1172
-                    esc_html__(
1173
-                        'Page route "%s" could not be called. Check that the spelling for method names and actions in the "_page_routes" array are all correct.',
1174
-                        'event_espresso'
1175
-                    ),
1176
-                    $method
1177
-                );
1178
-            }
1179
-            if (! empty($error_msg)) {
1180
-                throw new EE_Error($error_msg);
1181
-            }
1182
-        }
1183
-        // if we've routed and this route has a no headers route AND a sent_headers_route,
1184
-        // then we need to reset the routing properties to the new route.
1185
-        // 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.
1186
-        if (
1187
-            $this->_is_UI_request === false
1188
-            && is_array($this->_route)
1189
-            && ! empty($this->_route['headers_sent_route'])
1190
-        ) {
1191
-            $this->_reset_routing_properties($this->_route['headers_sent_route']);
1192
-        }
1193
-    }
1194
-
1195
-
1196
-    /**
1197
-     * This method just allows the resetting of page properties in the case where a no headers
1198
-     * route redirects to a headers route in its route config.
1199
-     *
1200
-     * @param string $new_route New (non header) route to redirect to.
1201
-     * @return   void
1202
-     * @throws ReflectionException
1203
-     * @throws InvalidArgumentException
1204
-     * @throws InvalidInterfaceException
1205
-     * @throws InvalidDataTypeException
1206
-     * @throws EE_Error
1207
-     * @since   4.3.0
1208
-     */
1209
-    protected function _reset_routing_properties($new_route)
1210
-    {
1211
-        $this->_is_UI_request = true;
1212
-        // now we set the current route to whatever the headers_sent_route is set at
1213
-        $this->request->setRequestParam('action', $new_route);
1214
-        // rerun page setup
1215
-        $this->_page_setup();
1216
-    }
1217
-
1218
-
1219
-    /**
1220
-     * _add_query_arg
1221
-     * adds nonce to array of arguments then calls WP add_query_arg function
1222
-     *(internally just uses EEH_URL's function with the same name)
1223
-     *
1224
-     * @param array  $args
1225
-     * @param string $url
1226
-     * @param bool   $sticky                  if true, then the existing Request params will be appended to the
1227
-     *                                        generated url in an associative array indexed by the key 'wp_referer';
1228
-     *                                        Example usage: If the current page is:
1229
-     *                                        http://mydomain.com/wp-admin/admin.php?page=espresso_registrations
1230
-     *                                        &action=default&event_id=20&month_range=March%202015
1231
-     *                                        &_wpnonce=5467821
1232
-     *                                        and you call:
1233
-     *                                        EE_Admin_Page::add_query_args_and_nonce(
1234
-     *                                        array(
1235
-     *                                        'action' => 'resend_something',
1236
-     *                                        'page=>espresso_registrations'
1237
-     *                                        ),
1238
-     *                                        $some_url,
1239
-     *                                        true
1240
-     *                                        );
1241
-     *                                        It will produce a url in this structure:
1242
-     *                                        http://{$some_url}/?page=espresso_registrations&action=resend_something
1243
-     *                                        &wp_referer[action]=default&wp_referer[event_id]=20&wpreferer[
1244
-     *                                        month_range]=March%202015
1245
-     * @param bool   $exclude_nonce           If true, the the nonce will be excluded from the generated nonce.
1246
-     * @return string
1247
-     */
1248
-    public static function add_query_args_and_nonce(
1249
-        $args = [],
1250
-        $url = '',
1251
-        $sticky = false,
1252
-        $exclude_nonce = false
1253
-    ) {
1254
-        // if there is a _wp_http_referer include the values from the request but only if sticky = true
1255
-        if ($sticky) {
1256
-            /** @var RequestInterface $request */
1257
-            $request = LoaderFactory::getLoader()->getShared(RequestInterface::class);
1258
-            $request->unSetRequestParams(['_wp_http_referer', 'wp_referer'], true);
1259
-            $request->unSetServerParam('_wp_http_referer', true);
1260
-            foreach ($request->requestParams() as $key => $value) {
1261
-                // do not add nonces
1262
-                if (strpos($key, 'nonce') !== false) {
1263
-                    continue;
1264
-                }
1265
-                $args[ 'wp_referer[' . $key . ']' ] = is_string($value) ? htmlspecialchars($value) : $value;
1266
-            }
1267
-        }
1268
-        return EEH_URL::add_query_args_and_nonce($args, $url, $exclude_nonce);
1269
-    }
1270
-
1271
-
1272
-    /**
1273
-     * This returns a generated link that will load the related help tab.
1274
-     *
1275
-     * @param string $help_tab_id the id for the connected help tab
1276
-     * @param string $icon_style  (optional) include css class for the style you want to use for the help icon.
1277
-     * @param string $help_text   (optional) send help text you want to use for the link if default not to be used
1278
-     * @return string              generated link
1279
-     * @uses EEH_Template::get_help_tab_link()
1280
-     */
1281
-    protected function _get_help_tab_link($help_tab_id, $icon_style = '', $help_text = '')
1282
-    {
1283
-        return EEH_Template::get_help_tab_link(
1284
-            $help_tab_id,
1285
-            $this->page_slug,
1286
-            $this->_req_action,
1287
-            $icon_style,
1288
-            $help_text
1289
-        );
1290
-    }
1291
-
1292
-
1293
-    /**
1294
-     * _add_help_tabs
1295
-     * Note child classes define their help tabs within the page_config array.
1296
-     *
1297
-     * @link   http://codex.wordpress.org/Function_Reference/add_help_tab
1298
-     * @return void
1299
-     * @throws DomainException
1300
-     * @throws EE_Error
1301
-     * @throws ReflectionException
1302
-     */
1303
-    protected function _add_help_tabs()
1304
-    {
1305
-        if (isset($this->_page_config[ $this->_req_action ])) {
1306
-            $config = $this->_page_config[ $this->_req_action ];
1307
-            // let's see if there is a help_sidebar set for the current route and we'll set that up for usage as well.
1308
-            if (is_array($config) && isset($config['help_sidebar'])) {
1309
-                // check that the callback given is valid
1310
-                if (! method_exists($this, $config['help_sidebar'])) {
1311
-                    throw new EE_Error(
1312
-                        sprintf(
1313
-                            esc_html__(
1314
-                                'The _page_config array has a callback set for the "help_sidebar" option.  However the callback given (%s) is not a valid callback.  Doublecheck the spelling and make sure this method exists for the class %s',
1315
-                                'event_espresso'
1316
-                            ),
1317
-                            $config['help_sidebar'],
1318
-                            $this->class_name
1319
-                        )
1320
-                    );
1321
-                }
1322
-                $content = apply_filters(
1323
-                    'FHEE__' . $this->class_name . '__add_help_tabs__help_sidebar',
1324
-                    $this->{$config['help_sidebar']}()
1325
-                );
1326
-                $this->_current_screen->set_help_sidebar($content);
1327
-            }
1328
-            if (! isset($config['help_tabs'])) {
1329
-                return;
1330
-            } //no help tabs for this route
1331
-            foreach ((array) $config['help_tabs'] as $tab_id => $cfg) {
1332
-                // we're here so there ARE help tabs!
1333
-                // make sure we've got what we need
1334
-                if (! isset($cfg['title'])) {
1335
-                    throw new EE_Error(
1336
-                        esc_html__(
1337
-                            'The _page_config array is not set up properly for help tabs.  It is missing a title',
1338
-                            'event_espresso'
1339
-                        )
1340
-                    );
1341
-                }
1342
-                if (! isset($cfg['filename']) && ! isset($cfg['callback']) && ! isset($cfg['content'])) {
1343
-                    throw new EE_Error(
1344
-                        esc_html__(
1345
-                            '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',
1346
-                            'event_espresso'
1347
-                        )
1348
-                    );
1349
-                }
1350
-                // first priority goes to content.
1351
-                if (! empty($cfg['content'])) {
1352
-                    $content = ! empty($cfg['content']) ? $cfg['content'] : null;
1353
-                    // second priority goes to filename
1354
-                } elseif (! empty($cfg['filename'])) {
1355
-                    $file_path = $this->_get_dir() . '/help_tabs/' . $cfg['filename'] . '.help_tab.php';
1356
-                    // 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)
1357
-                    $file_path = ! is_readable($file_path) ? EE_ADMIN_PAGES
1358
-                                                             . basename($this->_get_dir())
1359
-                                                             . '/help_tabs/'
1360
-                                                             . $cfg['filename']
1361
-                                                             . '.help_tab.php' : $file_path;
1362
-                    // if file is STILL not readable then let's do a EE_Error so its more graceful than a fatal error.
1363
-                    if (! isset($cfg['callback']) && ! is_readable($file_path)) {
1364
-                        EE_Error::add_error(
1365
-                            sprintf(
1366
-                                esc_html__(
1367
-                                    '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',
1368
-                                    'event_espresso'
1369
-                                ),
1370
-                                $tab_id,
1371
-                                key($config),
1372
-                                $file_path
1373
-                            ),
1374
-                            __FILE__,
1375
-                            __FUNCTION__,
1376
-                            __LINE__
1377
-                        );
1378
-                        return;
1379
-                    }
1380
-                    $template_args['admin_page_obj'] = $this;
1381
-                    $content                         = EEH_Template::display_template(
1382
-                        $file_path,
1383
-                        $template_args,
1384
-                        true
1385
-                    );
1386
-                } else {
1387
-                    $content = '';
1388
-                }
1389
-                // check if callback is valid
1390
-                if (
1391
-                    empty($content)
1392
-                    && (
1393
-                        ! isset($cfg['callback']) || ! method_exists($this, $cfg['callback'])
1394
-                    )
1395
-                ) {
1396
-                    EE_Error::add_error(
1397
-                        sprintf(
1398
-                            esc_html__(
1399
-                                '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.',
1400
-                                'event_espresso'
1401
-                            ),
1402
-                            $cfg['title']
1403
-                        ),
1404
-                        __FILE__,
1405
-                        __FUNCTION__,
1406
-                        __LINE__
1407
-                    );
1408
-                    return;
1409
-                }
1410
-                // setup config array for help tab method
1411
-                $id  = $this->page_slug . '-' . $this->_req_action . '-' . $tab_id;
1412
-                $_ht = [
1413
-                    'id'       => $id,
1414
-                    'title'    => $cfg['title'],
1415
-                    'callback' => isset($cfg['callback']) && empty($content) ? [$this, $cfg['callback']] : null,
1416
-                    'content'  => $content,
1417
-                ];
1418
-                $this->_current_screen->add_help_tab($_ht);
1419
-            }
1420
-        }
1421
-    }
1422
-
1423
-
1424
-    /**
1425
-     * This simply sets up any qtips that have been defined in the page config
1426
-     *
1427
-     * @return void
1428
-     * @throws ReflectionException
1429
-     * @throws EE_Error
1430
-     */
1431
-    protected function _add_qtips()
1432
-    {
1433
-        if (isset($this->_route_config['qtips'])) {
1434
-            $qtips = (array) $this->_route_config['qtips'];
1435
-            // load qtip loader
1436
-            $path = [
1437
-                $this->_get_dir() . '/qtips/',
1438
-                EE_ADMIN_PAGES . basename($this->_get_dir()) . '/qtips/',
1439
-            ];
1440
-            EEH_Qtip_Loader::instance()->register($qtips, $path);
1441
-        }
1442
-    }
1443
-
1444
-
1445
-    /**
1446
-     * _set_nav_tabs
1447
-     * This sets up the nav tabs from the page_routes array.  This method can be overwritten by child classes if you
1448
-     * wish to add additional tabs or modify accordingly.
1449
-     *
1450
-     * @return void
1451
-     * @throws InvalidArgumentException
1452
-     * @throws InvalidInterfaceException
1453
-     * @throws InvalidDataTypeException
1454
-     */
1455
-    protected function _set_nav_tabs()
1456
-    {
1457
-        $i = 0;
1458
-        $only_tab = count($this->_page_config) < 2;
1459
-        foreach ($this->_page_config as $slug => $config) {
1460
-            if (! is_array($config) || empty($config['nav'])) {
1461
-                continue;
1462
-            }
1463
-            // no nav tab for this config
1464
-            // check for persistent flag
1465
-            if ($slug !== $this->_req_action && isset($config['nav']['persistent']) && ! $config['nav']['persistent']) {
1466
-                // nav tab is only to appear when route requested.
1467
-                continue;
1468
-            }
1469
-            if (! $this->check_user_access($slug, true)) {
1470
-                // no nav tab because current user does not have access.
1471
-                continue;
1472
-            }
1473
-            $css_class = isset($config['css_class']) ? $config['css_class'] . ' ' : '';
1474
-            $css_class .= $only_tab ? ' ee-only-tab' : '';
1475
-
1476
-            $this->_nav_tabs[ $slug ] = [
1477
-                'url'       => $config['nav']['url'] ?? EE_Admin_Page::add_query_args_and_nonce(
1478
-                        ['action' => $slug],
1479
-                        $this->_admin_base_url
1480
-                    ),
1481
-                'link_text' => $this->navTabLabel($config['nav'], $slug),
1482
-                'css_class' => $this->_req_action === $slug ? $css_class . ' nav-tab-active' : $css_class,
1483
-                'order'     => $config['nav']['order'] ?? $i,
1484
-            ];
1485
-            $i++;
1486
-        }
1487
-        // if $this->_nav_tabs is empty then lets set the default
1488
-        if (empty($this->_nav_tabs)) {
1489
-            $this->_nav_tabs[ $this->_default_nav_tab_name ] = [
1490
-                'url'       => $this->_admin_base_url,
1491
-                'link_text' => ucwords(str_replace('_', ' ', $this->_default_nav_tab_name)),
1492
-                'css_class' => 'nav-tab-active',
1493
-                'order'     => 10,
1494
-            ];
1495
-        }
1496
-        // now let's sort the tabs according to order
1497
-        usort($this->_nav_tabs, [$this, '_sort_nav_tabs']);
1498
-    }
1499
-
1500
-
1501
-    private function navTabLabel(array $nav_tab, string $slug): string
1502
-    {
1503
-        $label = $nav_tab['label'] ?? ucwords(str_replace('_', ' ', $slug));
1504
-        $icon = $nav_tab['icon'] ?? null;
1505
-        $icon = $icon ? '<span class="dashicons ' . $icon . '"></span>' : '';
1506
-        return '
142
+	/**
143
+	 * unprocessed value for the 'page' request param (default '')
144
+	 *
145
+	 * @var string
146
+	 */
147
+	protected $raw_req_page = '';
148
+
149
+	/**
150
+	 * sanitized request action (and nonce)
151
+	 *
152
+	 * @var string
153
+	 */
154
+	protected $_req_action = '';
155
+
156
+	/**
157
+	 * sanitized request action nonce
158
+	 *
159
+	 * @var string
160
+	 */
161
+	protected $_req_nonce = '';
162
+
163
+	/**
164
+	 * @var string
165
+	 */
166
+	protected $_search_btn_label = '';
167
+
168
+	/**
169
+	 * @var string
170
+	 */
171
+	protected $_search_box_callback = '';
172
+
173
+	/**
174
+	 * @var WP_Screen
175
+	 */
176
+	protected $_current_screen;
177
+
178
+	// for holding EE_Admin_Hooks object when needed (set via set_hook_object())
179
+	protected $_hook_obj;
180
+
181
+	// for holding incoming request data
182
+	protected $_req_data = [];
183
+
184
+	// yes / no array for admin form fields
185
+	protected $_yes_no_values = [];
186
+
187
+	// some default things shared by all child classes
188
+	protected $_default_espresso_metaboxes = [
189
+		'_espresso_news_post_box',
190
+		'_espresso_links_post_box',
191
+		'_espresso_ratings_request',
192
+		'_espresso_sponsors_post_box',
193
+	];
194
+
195
+	/**
196
+	 * @var EE_Registry
197
+	 */
198
+	protected $EE;
199
+
200
+
201
+	/**
202
+	 * This is just a property that flags whether the given route is a caffeinated route or not.
203
+	 *
204
+	 * @var boolean
205
+	 */
206
+	protected $_is_caf = false;
207
+
208
+	/**
209
+	 * whether or not initializePage() has run
210
+	 *
211
+	 * @var boolean
212
+	 */
213
+	protected $initialized = false;
214
+
215
+	/**
216
+	 * @var FeatureFlags
217
+	 */
218
+	protected $feature;
219
+
220
+
221
+	/**
222
+	 * @var string
223
+	 */
224
+	protected $class_name;
225
+
226
+	/**
227
+	 * if the current class is an admin page extension, like: Extend_Events_Admin_Page,
228
+	 * then this would be the parent classname: Events_Admin_Page
229
+	 *
230
+	 * @var string
231
+	 */
232
+	protected $base_class_name;
233
+
234
+
235
+	/**
236
+	 * @Constructor
237
+	 * @param bool $routing indicate whether we want to just load the object and handle routing or just load the object.
238
+	 * @throws InvalidArgumentException
239
+	 * @throws InvalidDataTypeException
240
+	 * @throws InvalidInterfaceException
241
+	 * @throws ReflectionException
242
+	 */
243
+	public function __construct($routing = true)
244
+	{
245
+		$this->loader = LoaderFactory::getLoader();
246
+		$this->admin_config = $this->loader->getShared('EE_Admin_Config');
247
+		$this->feature = $this->loader->getShared(FeatureFlags::class);
248
+		$this->request = $this->loader->getShared(RequestInterface::class);
249
+		// routing enabled?
250
+		$this->_routing = $routing;
251
+
252
+		$this->class_name = get_class($this);
253
+		$this->base_class_name = strpos($this->class_name, 'Extend_') === 0
254
+			? str_replace('Extend_', '', $this->class_name)
255
+			: '';
256
+
257
+		if (strpos($this->_get_dir(), 'caffeinated') !== false) {
258
+			$this->_is_caf = true;
259
+		}
260
+		$this->_yes_no_values = [
261
+			['id' => true, 'text' => esc_html__('Yes', 'event_espresso')],
262
+			['id' => false, 'text' => esc_html__('No', 'event_espresso')],
263
+		];
264
+		// set the _req_data property.
265
+		$this->_req_data = $this->request->requestParams();
266
+	}
267
+
268
+
269
+	/**
270
+	 * @return EE_Admin_Config
271
+	 */
272
+	public function adminConfig(): EE_Admin_Config
273
+	{
274
+		return $this->admin_config;
275
+	}
276
+
277
+
278
+	/**
279
+	 * @return FeatureFlags
280
+	 */
281
+	public function feature(): FeatureFlags
282
+	{
283
+		return $this->feature;
284
+	}
285
+
286
+
287
+	/**
288
+	 * This logic used to be in the constructor, but that caused a chicken <--> egg scenario
289
+	 * for child classes that needed to set properties prior to these methods getting called,
290
+	 * but also needed the parent class to have its construction completed as well.
291
+	 * Bottom line is that constructors should ONLY be used for setting initial properties
292
+	 * and any complex initialization logic should only run after instantiation is complete.
293
+	 *
294
+	 * This method gets called immediately after construction from within
295
+	 *      EE_Admin_Page_Init::_initialize_admin_page()
296
+	 *
297
+	 * @throws EE_Error
298
+	 * @throws InvalidArgumentException
299
+	 * @throws InvalidDataTypeException
300
+	 * @throws InvalidInterfaceException
301
+	 * @throws ReflectionException
302
+	 * @since $VID:$
303
+	 */
304
+	public function initializePage()
305
+	{
306
+		if ($this->initialized) {
307
+			return;
308
+		}
309
+		// set initial page props (child method)
310
+		$this->_init_page_props();
311
+		// set global defaults
312
+		$this->_set_defaults();
313
+		// set early because incoming requests could be ajax related and we need to register those hooks.
314
+		$this->_global_ajax_hooks();
315
+		$this->_ajax_hooks();
316
+		// other_page_hooks have to be early too.
317
+		$this->_do_other_page_hooks();
318
+		// set up page dependencies
319
+		$this->_before_page_setup();
320
+		$this->_page_setup();
321
+		$this->initialized = true;
322
+	}
323
+
324
+
325
+	/**
326
+	 * _init_page_props
327
+	 * Child classes use to set at least the following properties:
328
+	 * $page_slug.
329
+	 * $page_label.
330
+	 *
331
+	 * @abstract
332
+	 * @return void
333
+	 */
334
+	abstract protected function _init_page_props();
335
+
336
+
337
+	/**
338
+	 * _ajax_hooks
339
+	 * child classes put all their add_action('wp_ajax_{name_of_hook}') hooks in here.
340
+	 * Note: within the ajax callback methods.
341
+	 *
342
+	 * @abstract
343
+	 * @return void
344
+	 */
345
+	abstract protected function _ajax_hooks();
346
+
347
+
348
+	/**
349
+	 * _define_page_props
350
+	 * child classes define page properties in here.  Must include at least:
351
+	 * $_admin_base_url = base_url for all admin pages
352
+	 * $_admin_page_title = default admin_page_title for admin pages
353
+	 * $_labels = array of default labels for various automatically generated elements:
354
+	 *    array(
355
+	 *        'buttons' => array(
356
+	 *            'add' => esc_html__('label for add new button'),
357
+	 *            'edit' => esc_html__('label for edit button'),
358
+	 *            'delete' => esc_html__('label for delete button')
359
+	 *            )
360
+	 *        )
361
+	 *
362
+	 * @abstract
363
+	 * @return void
364
+	 */
365
+	abstract protected function _define_page_props();
366
+
367
+
368
+	/**
369
+	 * _set_page_routes
370
+	 * child classes use this to define the page routes for all subpages handled by the class.  Page routes are
371
+	 * assigned to a action => method pairs in an array and to the $_page_routes property.  Each page route must also
372
+	 * have a 'default' route. Here's the format
373
+	 * $this->_page_routes = array(
374
+	 *        'default' => array(
375
+	 *            'func' => '_default_method_handling_route',
376
+	 *            'args' => array('array','of','args'),
377
+	 *            'noheader' => true, //add this in if this page route is processed before any headers are loaded (i.e.
378
+	 *            ajax request, backend processing)
379
+	 *            'headers_sent_route'=>'headers_route_reference', //add this if noheader=>true, and you want to load a
380
+	 *            headers route after.  The string you enter here should match the defined route reference for a
381
+	 *            headers sent route.
382
+	 *            'capability' => 'route_capability', //indicate a string for minimum capability required to access
383
+	 *            this route.
384
+	 *            'obj_id' => 10 // if this route has an object id, then this can include it (used for capability
385
+	 *            checks).
386
+	 *        ),
387
+	 *        'insert_item' => '_method_for_handling_insert_item' //this can be used if all we need to have is a
388
+	 *        handling method.
389
+	 *        )
390
+	 * )
391
+	 *
392
+	 * @abstract
393
+	 * @return void
394
+	 */
395
+	abstract protected function _set_page_routes();
396
+
397
+
398
+	/**
399
+	 * _set_page_config
400
+	 * child classes use this to define the _page_config array for all subpages handled by the class. Each key in the
401
+	 * array corresponds to the page_route for the loaded page. Format:
402
+	 * $this->_page_config = array(
403
+	 *        'default' => array(
404
+	 *            'labels' => array(
405
+	 *                'buttons' => array(
406
+	 *                    'add' => esc_html__('label for adding item'),
407
+	 *                    'edit' => esc_html__('label for editing item'),
408
+	 *                    'delete' => esc_html__('label for deleting item')
409
+	 *                ),
410
+	 *                'publishbox' => esc_html__('Localized Title for Publish metabox', 'event_espresso')
411
+	 *            ), //optional an array of custom labels for various automatically generated elements to use on the
412
+	 *            page. If this isn't present then the defaults will be used as set for the $this->_labels in
413
+	 *            _define_page_props() method
414
+	 *            'nav' => array(
415
+	 *                'label' => esc_html__('Label for Tab', 'event_espresso').
416
+	 *                'url' => 'http://someurl', //automatically generated UNLESS you define
417
+	 *                'css_class' => 'css-class', //automatically generated UNLESS you define
418
+	 *                'order' => 10, //required to indicate tab position.
419
+	 *                'persistent' => false //if you want the nav tab to ONLY display when the specific route is
420
+	 *                displayed then add this parameter.
421
+	 *            'list_table' => 'name_of_list_table' //string for list table class to be loaded for this admin_page.
422
+	 *            'metaboxes' => array('metabox1', 'metabox2'), //if present this key indicates we want to load
423
+	 *            metaboxes set for eventespresso admin pages.
424
+	 *            'has_metaboxes' => true, //this boolean flag can simply be used to indicate if the route will have
425
+	 *            metaboxes.  Typically this is used if the 'metaboxes' index is not used because metaboxes are added
426
+	 *            later.  We just use this flag to make sure the necessary js gets enqueued on page load.
427
+	 *            'has_help_popups' => false //defaults(true) //this boolean flag can simply be used to indicate if the
428
+	 *            given route has help popups setup and if it does then we need to make sure thickbox is enqueued.
429
+	 *            'columns' => array(4, 2), //this key triggers the setup of a page that uses columns (metaboxes).  The
430
+	 *            array indicates the max number of columns (4) and the default number of columns on page load (2).
431
+	 *            There is an option in the "screen_options" dropdown that is setup so users can pick what columns they
432
+	 *            want to display.
433
+	 *            'help_tabs' => array( //this is used for adding help tabs to a page
434
+	 *                'tab_id' => array(
435
+	 *                    'title' => 'tab_title',
436
+	 *                    'filename' => 'name_of_file_containing_content', //this is the primary method for setting
437
+	 *                    help tab content.  The fallback if it isn't present is to try a the callback.  Filename
438
+	 *                    should match a file in the admin folder's "help_tabs" dir (ie..
439
+	 *                    events/help_tabs/name_of_file_containing_content.help_tab.php)
440
+	 *                    'callback' => 'callback_method_for_content', //if 'filename' isn't present then system will
441
+	 *                    attempt to use the callback which should match the name of a method in the class
442
+	 *                    ),
443
+	 *                'tab2_id' => array(
444
+	 *                    'title' => 'tab2 title',
445
+	 *                    'filename' => 'file_name_2'
446
+	 *                    'callback' => 'callback_method_for_content',
447
+	 *                 ),
448
+	 *            'help_sidebar' => 'callback_for_sidebar_content', //this is used for setting up the sidebar in the
449
+	 *            help tab area on an admin page. @return void
450
+	 *
451
+	 * @abstract
452
+	 */
453
+	abstract protected function _set_page_config();
454
+
455
+
456
+	/**
457
+	 * _add_screen_options
458
+	 * Child classes can add any extra wp_screen_options within this method using built-in WP functions/methods for
459
+	 * doing so. Note child classes can also define _add_screen_options_($this->_current_view) to limit screen options
460
+	 * to a particular view.
461
+	 *
462
+	 * @link   http://chrismarslender.com/wp-tutorials/wordpress-screen-options-tutorial/
463
+	 *         see also WP_Screen object documents...
464
+	 * @link   http://codex.wordpress.org/Class_Reference/WP_Screen
465
+	 * @abstract
466
+	 * @return void
467
+	 */
468
+	abstract protected function _add_screen_options();
469
+
470
+
471
+	/**
472
+	 * _add_feature_pointers
473
+	 * Child classes should use this method for implementing any "feature pointers" (using built-in WP styling js).
474
+	 * Note child classes can also define _add_feature_pointers_($this->_current_view) to limit screen options to a
475
+	 * particular view. Note: this is just a placeholder for now.  Implementation will come down the road See:
476
+	 * WP_Internal_Pointers class in wp-admin/includes/template.php for example (its a final class so can't be
477
+	 * extended) also see:
478
+	 *
479
+	 * @link   http://eamann.com/tech/wordpress-portland/
480
+	 * @abstract
481
+	 * @return void
482
+	 */
483
+	abstract protected function _add_feature_pointers();
484
+
485
+
486
+	/**
487
+	 * load_scripts_styles
488
+	 * child classes put their wp_enqueue_script and wp_enqueue_style hooks in here for anything they need loaded for
489
+	 * their pages/subpages.  Note this is for all pages/subpages of the system.  You can also load only specific
490
+	 * scripts/styles per view by putting them in a dynamic function in this format
491
+	 * (load_scripts_styles_{$this->_current_view}) which matches your page route (action request arg)
492
+	 *
493
+	 * @abstract
494
+	 * @return void
495
+	 */
496
+	abstract public function load_scripts_styles();
497
+
498
+
499
+	/**
500
+	 * admin_init
501
+	 * Anything that should be set/executed at 'admin_init' WP hook runtime should be put in here.  This will apply to
502
+	 * all pages/views loaded by child class.
503
+	 *
504
+	 * @abstract
505
+	 * @return void
506
+	 */
507
+	abstract public function admin_init();
508
+
509
+
510
+	/**
511
+	 * admin_notices
512
+	 * Anything triggered by the 'admin_notices' WP hook should be put in here.  This particular method will apply to
513
+	 * all pages/views loaded by child class.
514
+	 *
515
+	 * @abstract
516
+	 * @return void
517
+	 */
518
+	abstract public function admin_notices();
519
+
520
+
521
+	/**
522
+	 * admin_footer_scripts
523
+	 * Anything triggered by the 'admin_print_footer_scripts' WP hook should be put in here. This particular method
524
+	 * will apply to all pages/views loaded by child class.
525
+	 *
526
+	 * @return void
527
+	 */
528
+	abstract public function admin_footer_scripts();
529
+
530
+
531
+	/**
532
+	 * admin_footer
533
+	 * anything triggered by the 'admin_footer' WP action hook should be added to here. This particular method will
534
+	 * apply to all pages/views loaded by child class.
535
+	 *
536
+	 * @return void
537
+	 */
538
+	public function admin_footer()
539
+	{
540
+	}
541
+
542
+
543
+	/**
544
+	 * _global_ajax_hooks
545
+	 * all global add_action('wp_ajax_{name_of_hook}') hooks in here.
546
+	 * Note: within the ajax callback methods.
547
+	 *
548
+	 * @abstract
549
+	 * @return void
550
+	 */
551
+	protected function _global_ajax_hooks()
552
+	{
553
+		// for lazy loading of metabox content
554
+		add_action('wp_ajax_espresso-ajax-content', [$this, 'ajax_metabox_content'], 10);
555
+
556
+		add_action(
557
+			'wp_ajax_espresso_hide_status_change_notice',
558
+			[$this, 'hideStatusChangeNotice']
559
+		);
560
+		add_action(
561
+			'wp_ajax_nopriv_espresso_hide_status_change_notice',
562
+			[$this, 'hideStatusChangeNotice']
563
+		);
564
+	}
565
+
566
+
567
+	public function ajax_metabox_content()
568
+	{
569
+		$content_id  = $this->request->getRequestParam('contentid', '');
570
+		$content_url = $this->request->getRequestParam('contenturl', '', 'url');
571
+		EE_Admin_Page::cached_rss_display($content_id, $content_url);
572
+		wp_die();
573
+	}
574
+
575
+
576
+	public function hideStatusChangeNotice()
577
+	{
578
+		$response = [];
579
+		try {
580
+			/** @var StatusChangeNotice $status_change_notice */
581
+			$status_change_notice = $this->loader->getShared(
582
+				'EventEspresso\core\domain\services\admin\notices\status_change\StatusChangeNotice'
583
+			);
584
+			$response['success'] = $status_change_notice->dismiss() > -1;
585
+		} catch (Exception $exception) {
586
+			$response['errors'] = $exception->getMessage();
587
+		}
588
+		echo wp_json_encode($response);
589
+		exit();
590
+	}
591
+
592
+
593
+	/**
594
+	 * allows extending classes do something specific before the parent constructor runs _page_setup().
595
+	 *
596
+	 * @return void
597
+	 */
598
+	protected function _before_page_setup()
599
+	{
600
+		// default is to do nothing
601
+	}
602
+
603
+
604
+	/**
605
+	 * Makes sure any things that need to be loaded early get handled.
606
+	 * We also escape early here if the page requested doesn't match the object.
607
+	 *
608
+	 * @final
609
+	 * @return void
610
+	 * @throws EE_Error
611
+	 * @throws InvalidArgumentException
612
+	 * @throws ReflectionException
613
+	 * @throws InvalidDataTypeException
614
+	 * @throws InvalidInterfaceException
615
+	 */
616
+	final protected function _page_setup()
617
+	{
618
+		// requires?
619
+		// admin_init stuff - global - we're setting this REALLY early
620
+		// so if EE_Admin pages have to hook into other WP pages they can.
621
+		// But keep in mind, not everything is available from the EE_Admin Page object at this point.
622
+		add_action('admin_init', [$this, 'admin_init_global'], 5);
623
+		// next verify if we need to load anything...
624
+		$this->_current_page = $this->request->getRequestParam('page', '', 'key');
625
+		$this->page_folder   = strtolower(
626
+			str_replace(['_Admin_Page', 'Extend_'], '', $this->class_name)
627
+		);
628
+		global $ee_menu_slugs;
629
+		$ee_menu_slugs = (array) $ee_menu_slugs;
630
+		if (
631
+			! $this->request->isAjax()
632
+			&& (! $this->_current_page || ! isset($ee_menu_slugs[ $this->_current_page ]))
633
+		) {
634
+			return;
635
+		}
636
+		// because WP List tables have two duplicate select inputs for choosing bulk actions,
637
+		// we need to copy the action from the second to the first
638
+		$action     = $this->request->getRequestParam('action', '-1', 'key');
639
+		$action2    = $this->request->getRequestParam('action2', '-1', 'key');
640
+		$action     = $action !== '-1' ? $action : $action2;
641
+		$req_action = $action !== '-1' ? $action : 'default';
642
+
643
+		// if a specific 'route' has been set, and the action is 'default' OR we are doing_ajax
644
+		// then let's use the route as the action.
645
+		// This covers cases where we're coming in from a list table that isn't on the default route.
646
+		$route = $this->request->getRequestParam('route');
647
+		$this->_req_action = $route && ($req_action === 'default' || $this->request->isAjax())
648
+			? $route
649
+			: $req_action;
650
+
651
+		$this->_current_view = $this->_req_action;
652
+		$this->_req_nonce    = $this->_req_action . '_nonce';
653
+		$this->_define_page_props();
654
+		$this->_current_page_view_url = add_query_arg(
655
+			['page' => $this->_current_page, 'action' => $this->_current_view],
656
+			$this->_admin_base_url
657
+		);
658
+		// set page configs
659
+		$this->_set_page_routes();
660
+		$this->_set_page_config();
661
+		// let's include any referrer data in our default_query_args for this route for "stickiness".
662
+		if ($this->request->requestParamIsSet('wp_referer')) {
663
+			$wp_referer = $this->request->getRequestParam('wp_referer');
664
+			if ($wp_referer) {
665
+				$this->_default_route_query_args['wp_referer'] = $wp_referer;
666
+			}
667
+		}
668
+		// for caffeinated and other extended functionality.
669
+		//  If there is a _extend_page_config method
670
+		// then let's run that to modify the all the various page configuration arrays
671
+		if (method_exists($this, '_extend_page_config')) {
672
+			$this->_extend_page_config();
673
+		}
674
+		// for CPT and other extended functionality.
675
+		// If there is an _extend_page_config_for_cpt
676
+		// then let's run that to modify all the various page configuration arrays.
677
+		if (method_exists($this, '_extend_page_config_for_cpt')) {
678
+			$this->_extend_page_config_for_cpt();
679
+		}
680
+		// filter routes and page_config so addons can add their stuff. Filtering done per class
681
+		$this->_page_routes = apply_filters(
682
+			'FHEE__' . $this->class_name . '__page_setup__page_routes',
683
+			$this->_page_routes,
684
+			$this
685
+		);
686
+		$this->_page_config = apply_filters(
687
+			'FHEE__' . $this->class_name . '__page_setup__page_config',
688
+			$this->_page_config,
689
+			$this
690
+		);
691
+		if ($this->base_class_name !== '') {
692
+			$this->_page_routes = apply_filters(
693
+				'FHEE__' . $this->base_class_name . '__page_setup__page_routes',
694
+				$this->_page_routes,
695
+				$this
696
+			);
697
+			$this->_page_config = apply_filters(
698
+				'FHEE__' . $this->base_class_name . '__page_setup__page_config',
699
+				$this->_page_config,
700
+				$this
701
+			);
702
+		}
703
+		// if AHEE__EE_Admin_Page__route_admin_request_$this->_current_view method is present
704
+		// then we call it hooked into the AHEE__EE_Admin_Page__route_admin_request action
705
+		if (method_exists($this, 'AHEE__EE_Admin_Page__route_admin_request_' . $this->_current_view)) {
706
+			add_action(
707
+				'AHEE__EE_Admin_Page__route_admin_request',
708
+				[$this, 'AHEE__EE_Admin_Page__route_admin_request_' . $this->_current_view],
709
+				10,
710
+				2
711
+			);
712
+		}
713
+		// next route only if routing enabled
714
+		if ($this->_routing && ! $this->request->isAjax()) {
715
+			$this->_verify_routes();
716
+			// next let's just check user_access and kill if no access
717
+			$this->check_user_access();
718
+			if ($this->_is_UI_request) {
719
+				// admin_init stuff - global, all views for this page class, specific view
720
+				add_action('admin_init', [$this, 'admin_init'], 10);
721
+				if (method_exists($this, 'admin_init_' . $this->_current_view)) {
722
+					add_action('admin_init', [$this, 'admin_init_' . $this->_current_view], 15);
723
+				}
724
+			} else {
725
+				// hijack regular WP loading and route admin request immediately
726
+				@ini_set('memory_limit', apply_filters('admin_memory_limit', WP_MAX_MEMORY_LIMIT));
727
+				$this->route_admin_request();
728
+			}
729
+		}
730
+	}
731
+
732
+
733
+	/**
734
+	 * Provides a way for related child admin pages to load stuff on the loaded admin page.
735
+	 *
736
+	 * @return void
737
+	 * @throws EE_Error
738
+	 */
739
+	private function _do_other_page_hooks()
740
+	{
741
+		$registered_pages = apply_filters('FHEE_do_other_page_hooks_' . $this->page_slug, []);
742
+		foreach ($registered_pages as $page) {
743
+			// now let's setup the file name and class that should be present
744
+			$classname = str_replace('.class.php', '', $page);
745
+			// autoloaders should take care of loading file
746
+			if (! class_exists($classname)) {
747
+				$error_msg[] = sprintf(
748
+					esc_html__(
749
+						'Something went wrong with loading the %s admin hooks page.',
750
+						'event_espresso'
751
+					),
752
+					$page
753
+				);
754
+				$error_msg[] = $error_msg[0]
755
+							   . "\r\n"
756
+							   . sprintf(
757
+								   esc_html__(
758
+									   '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',
759
+									   'event_espresso'
760
+								   ),
761
+								   $page,
762
+								   '<br />',
763
+								   '<strong>' . $classname . '</strong>'
764
+							   );
765
+				throw new EE_Error(implode('||', $error_msg));
766
+			}
767
+			// notice we are passing the instance of this class to the hook object.
768
+			$this->loader->getShared($classname, [$this]);
769
+		}
770
+	}
771
+
772
+
773
+	/**
774
+	 * @throws ReflectionException
775
+	 * @throws EE_Error
776
+	 */
777
+	public function load_page_dependencies()
778
+	{
779
+		try {
780
+			$this->_load_page_dependencies();
781
+		} catch (EE_Error $e) {
782
+			$e->get_error();
783
+		}
784
+	}
785
+
786
+
787
+	/**
788
+	 * load_page_dependencies
789
+	 * loads things specific to this page class when its loaded.  Really helps with efficiency.
790
+	 *
791
+	 * @return void
792
+	 * @throws DomainException
793
+	 * @throws EE_Error
794
+	 * @throws InvalidArgumentException
795
+	 * @throws InvalidDataTypeException
796
+	 * @throws InvalidInterfaceException
797
+	 */
798
+	protected function _load_page_dependencies()
799
+	{
800
+		// let's set the current_screen and screen options to override what WP set
801
+		$this->_current_screen = get_current_screen();
802
+		// load admin_notices - global, page class, and view specific
803
+		add_action('admin_notices', [$this, 'admin_notices_global'], 5);
804
+		add_action('admin_notices', [$this, 'admin_notices'], 10);
805
+		if (method_exists($this, 'admin_notices_' . $this->_current_view)) {
806
+			add_action('admin_notices', [$this, 'admin_notices_' . $this->_current_view], 15);
807
+		}
808
+		// load network admin_notices - global, page class, and view specific
809
+		add_action('network_admin_notices', [$this, 'network_admin_notices_global'], 5);
810
+		if (method_exists($this, 'network_admin_notices_' . $this->_current_view)) {
811
+			add_action('network_admin_notices', [$this, 'network_admin_notices_' . $this->_current_view]);
812
+		}
813
+		// this will save any per_page screen options if they are present
814
+		$this->_set_per_page_screen_options();
815
+		// setup list table properties
816
+		$this->_set_list_table();
817
+		// child classes can "register" a metabox to be automatically handled via the _page_config array property.
818
+		// However in some cases the metaboxes will need to be added within a route handling callback.
819
+		$this->_add_registered_meta_boxes();
820
+		$this->_add_screen_columns();
821
+		// add screen options - global, page child class, and view specific
822
+		$this->_add_global_screen_options();
823
+		$this->_add_screen_options();
824
+		$add_screen_options = "_add_screen_options_{$this->_current_view}";
825
+		if (method_exists($this, $add_screen_options)) {
826
+			$this->{$add_screen_options}();
827
+		}
828
+		// add help tab(s) - set via page_config and qtips.
829
+		$this->_add_help_tabs();
830
+		$this->_add_qtips();
831
+		// add feature_pointers - global, page child class, and view specific
832
+		$this->_add_feature_pointers();
833
+		$this->_add_global_feature_pointers();
834
+		$add_feature_pointer = "_add_feature_pointer_{$this->_current_view}";
835
+		if (method_exists($this, $add_feature_pointer)) {
836
+			$this->{$add_feature_pointer}();
837
+		}
838
+		// enqueue scripts/styles - global, page class, and view specific
839
+		add_action('admin_enqueue_scripts', [$this, 'load_global_scripts_styles'], 5);
840
+		add_action('admin_enqueue_scripts', [$this, 'load_scripts_styles'], 10);
841
+		if (method_exists($this, "load_scripts_styles_{$this->_current_view}")) {
842
+			add_action('admin_enqueue_scripts', [$this, "load_scripts_styles_{$this->_current_view}"], 15);
843
+		}
844
+		add_action('admin_enqueue_scripts', [$this, 'admin_footer_scripts_eei18n_js_strings'], 100);
845
+		// admin_print_footer_scripts - global, page child class, and view specific.
846
+		// NOTE, despite the name, whenever possible, scripts should NOT be loaded using this.
847
+		// In most cases that's doing_it_wrong().  But adding hidden container elements etc.
848
+		// is a good use case. Notice the late priority we're giving these
849
+		add_action('admin_print_footer_scripts', [$this, 'admin_footer_scripts_global'], 99);
850
+		add_action('admin_print_footer_scripts', [$this, 'admin_footer_scripts'], 100);
851
+		if (method_exists($this, "admin_footer_scripts_{$this->_current_view}")) {
852
+			add_action('admin_print_footer_scripts', [$this, "admin_footer_scripts_{$this->_current_view}"], 101);
853
+		}
854
+		// admin footer scripts
855
+		add_action('admin_footer', [$this, 'admin_footer_global'], 99);
856
+		add_action('admin_footer', [$this, 'admin_footer'], 100);
857
+		if (method_exists($this, "admin_footer_{$this->_current_view}")) {
858
+			add_action('admin_footer', [$this, "admin_footer_{$this->_current_view}"], 101);
859
+		}
860
+		do_action('FHEE__EE_Admin_Page___load_page_dependencies__after_load', $this->page_slug);
861
+		// targeted hook
862
+		do_action(
863
+			"FHEE__EE_Admin_Page___load_page_dependencies__after_load__{$this->page_slug}__{$this->_req_action}"
864
+		);
865
+	}
866
+
867
+
868
+	/**
869
+	 * _set_defaults
870
+	 * This sets some global defaults for class properties.
871
+	 */
872
+	private function _set_defaults()
873
+	{
874
+		$this->_current_screen       = $this->_admin_page_title = $this->_req_action = $this->_req_nonce = null;
875
+		$this->_event                = $this->_template_path = $this->_column_template_path = null;
876
+		$this->_nav_tabs             = $this->_views = $this->_page_routes = [];
877
+		$this->_page_config          = $this->_default_route_query_args = [];
878
+		$this->_default_nav_tab_name = 'overview';
879
+		// init template args
880
+		$this->_template_args = [
881
+			'admin_page_header'  => '',
882
+			'admin_page_content' => '',
883
+			'post_body_content'  => '',
884
+			'before_list_table'  => '',
885
+			'after_list_table'   => '',
886
+		];
887
+	}
888
+
889
+
890
+	/**
891
+	 * route_admin_request
892
+	 *
893
+	 * @return void
894
+	 * @throws InvalidArgumentException
895
+	 * @throws InvalidInterfaceException
896
+	 * @throws InvalidDataTypeException
897
+	 * @throws EE_Error
898
+	 * @throws ReflectionException
899
+	 * @see    _route_admin_request()
900
+	 */
901
+	public function route_admin_request()
902
+	{
903
+		try {
904
+			$this->_route_admin_request();
905
+		} catch (EE_Error $e) {
906
+			$e->get_error();
907
+		}
908
+	}
909
+
910
+
911
+	public function set_wp_page_slug($wp_page_slug)
912
+	{
913
+		$this->_wp_page_slug = $wp_page_slug;
914
+		// if in network admin then we need to append "-network" to the page slug. Why? Because that's how WP rolls...
915
+		if (is_network_admin()) {
916
+			$this->_wp_page_slug .= '-network';
917
+		}
918
+	}
919
+
920
+
921
+	/**
922
+	 * _verify_routes
923
+	 * All this method does is verify the incoming request and make sure that routes exist for it.  We do this early so
924
+	 * we know if we need to drop out.
925
+	 *
926
+	 * @return bool
927
+	 * @throws EE_Error
928
+	 */
929
+	protected function _verify_routes()
930
+	{
931
+		do_action('AHEE_log', __FILE__, __FUNCTION__, '');
932
+		if (! $this->_current_page && ! $this->request->isAjax()) {
933
+			return false;
934
+		}
935
+		$this->_route = false;
936
+		// check that the page_routes array is not empty
937
+		if (empty($this->_page_routes)) {
938
+			// user error msg
939
+			$error_msg = sprintf(
940
+				esc_html__('No page routes have been set for the %s admin page.', 'event_espresso'),
941
+				$this->_admin_page_title
942
+			);
943
+			// developer error msg
944
+			$error_msg .= '||' . $error_msg
945
+						  . esc_html__(
946
+							  ' Make sure the "set_page_routes()" method exists, and is setting the "_page_routes" array properly.',
947
+							  'event_espresso'
948
+						  );
949
+			throw new EE_Error($error_msg);
950
+		}
951
+		// and that the requested page route exists
952
+		if (array_key_exists($this->_req_action, $this->_page_routes)) {
953
+			$this->_route        = $this->_page_routes[ $this->_req_action ];
954
+			$this->_route_config = $this->_page_config[ $this->_req_action ] ?? [];
955
+		} else {
956
+			// user error msg
957
+			$error_msg = sprintf(
958
+				esc_html__(
959
+					'The requested page route does not exist for the %s admin page.',
960
+					'event_espresso'
961
+				),
962
+				$this->_admin_page_title
963
+			);
964
+			// developer error msg
965
+			$error_msg .= '||' . $error_msg
966
+						  . sprintf(
967
+							  esc_html__(
968
+								  ' Create a key in the "_page_routes" array named "%s" and set its value to the appropriate method.',
969
+								  'event_espresso'
970
+							  ),
971
+							  $this->_req_action
972
+						  );
973
+			throw new EE_Error($error_msg);
974
+		}
975
+		// and that a default route exists
976
+		if (! array_key_exists('default', $this->_page_routes)) {
977
+			// user error msg
978
+			$error_msg = sprintf(
979
+				esc_html__(
980
+					'A default page route has not been set for the % admin page.',
981
+					'event_espresso'
982
+				),
983
+				$this->_admin_page_title
984
+			);
985
+			// developer error msg
986
+			$error_msg .= '||' . $error_msg
987
+						  . esc_html__(
988
+							  ' Create a key in the "_page_routes" array named "default" and set its value to your default page method.',
989
+							  'event_espresso'
990
+						  );
991
+			throw new EE_Error($error_msg);
992
+		}
993
+
994
+		// first lets' catch if the UI request has EVER been set.
995
+		if ($this->_is_UI_request === null) {
996
+			// lets set if this is a UI request or not.
997
+			$this->_is_UI_request = ! $this->request->getRequestParam('noheader', false, 'bool');
998
+			// wait a minute... we might have a noheader in the route array
999
+			$this->_is_UI_request = ! (
1000
+				is_array($this->_route) && isset($this->_route['noheader']) && $this->_route['noheader']
1001
+			)
1002
+				? $this->_is_UI_request
1003
+				: false;
1004
+		}
1005
+		$this->_set_current_labels();
1006
+		return true;
1007
+	}
1008
+
1009
+
1010
+	/**
1011
+	 * this method simply verifies a given route and makes sure its an actual route available for the loaded page
1012
+	 *
1013
+	 * @param string $route the route name we're verifying
1014
+	 * @return bool we'll throw an exception if this isn't a valid route.
1015
+	 * @throws EE_Error
1016
+	 */
1017
+	protected function _verify_route($route)
1018
+	{
1019
+		if (array_key_exists($this->_req_action, $this->_page_routes)) {
1020
+			return true;
1021
+		}
1022
+		// user error msg
1023
+		$error_msg = sprintf(
1024
+			esc_html__('The given page route does not exist for the %s admin page.', 'event_espresso'),
1025
+			$this->_admin_page_title
1026
+		);
1027
+		// developer error msg
1028
+		$error_msg .= '||' . $error_msg
1029
+					  . sprintf(
1030
+						  esc_html__(
1031
+							  ' Check the route you are using in your method (%s) and make sure it matches a route set in your "_page_routes" array property',
1032
+							  'event_espresso'
1033
+						  ),
1034
+						  $route
1035
+					  );
1036
+		throw new EE_Error($error_msg);
1037
+	}
1038
+
1039
+
1040
+	/**
1041
+	 * perform nonce verification
1042
+	 * This method has be encapsulated here so that any ajax requests that bypass normal routes can verify their nonces
1043
+	 * using this method (and save retyping!)
1044
+	 *
1045
+	 * @param string $nonce     The nonce sent
1046
+	 * @param string $nonce_ref The nonce reference string (name0)
1047
+	 * @return void
1048
+	 * @throws EE_Error
1049
+	 * @throws InvalidArgumentException
1050
+	 * @throws InvalidDataTypeException
1051
+	 * @throws InvalidInterfaceException
1052
+	 */
1053
+	protected function _verify_nonce($nonce, $nonce_ref)
1054
+	{
1055
+		// verify nonce against expected value
1056
+		if (! wp_verify_nonce($nonce, $nonce_ref)) {
1057
+			// these are not the droids you are looking for !!!
1058
+			$msg = sprintf(
1059
+				esc_html__('%sNonce Fail.%s', 'event_espresso'),
1060
+				'<a href="https://www.youtube.com/watch?v=56_S0WeTkzs">',
1061
+				'</a>'
1062
+			);
1063
+			if (WP_DEBUG) {
1064
+				$msg .= "\n  ";
1065
+				$msg .= sprintf(
1066
+					esc_html__(
1067
+						'In order to dynamically generate nonces for your actions, use the %s::add_query_args_and_nonce() method. May the Nonce be with you!',
1068
+						'event_espresso'
1069
+					),
1070
+					__CLASS__
1071
+				);
1072
+			}
1073
+			if (! $this->request->isAjax()) {
1074
+				wp_die($msg);
1075
+			}
1076
+			EE_Error::add_error($msg, __FILE__, __FUNCTION__, __LINE__);
1077
+			$this->_return_json();
1078
+		}
1079
+	}
1080
+
1081
+
1082
+	/**
1083
+	 * _route_admin_request()
1084
+	 * Meat and potatoes of the class.  Basically, this dude checks out what's being requested and sees if there are
1085
+	 * some doodads to work the magic and handle the flingjangy. Translation:  Checks if the requested action is listed
1086
+	 * in the page routes and then will try to load the corresponding method.
1087
+	 *
1088
+	 * @return void
1089
+	 * @throws EE_Error
1090
+	 * @throws InvalidArgumentException
1091
+	 * @throws InvalidDataTypeException
1092
+	 * @throws InvalidInterfaceException
1093
+	 * @throws ReflectionException
1094
+	 */
1095
+	protected function _route_admin_request()
1096
+	{
1097
+		if (! $this->_is_UI_request) {
1098
+			$this->_verify_routes();
1099
+		}
1100
+		$nonce_check = ! isset($this->_route_config['require_nonce']) || $this->_route_config['require_nonce'];
1101
+		if ($this->_req_action !== 'default' && $nonce_check) {
1102
+			// set nonce from post data
1103
+			$nonce = $this->request->getRequestParam($this->_req_nonce, '');
1104
+			$this->_verify_nonce($nonce, $this->_req_nonce);
1105
+		}
1106
+		// set the nav_tabs array but ONLY if this is  UI_request
1107
+		if ($this->_is_UI_request) {
1108
+			$this->_set_nav_tabs();
1109
+		}
1110
+		// grab callback function
1111
+		$func = is_array($this->_route) && isset($this->_route['func']) ? $this->_route['func'] : $this->_route;
1112
+		// check if callback has args
1113
+		$args      = is_array($this->_route) && isset($this->_route['args']) ? $this->_route['args'] : [];
1114
+		$error_msg = '';
1115
+		// action right before calling route
1116
+		// (hook is something like 'AHEE__Registrations_Admin_Page__route_admin_request')
1117
+		if (! did_action('AHEE__EE_Admin_Page__route_admin_request')) {
1118
+			do_action('AHEE__EE_Admin_Page__route_admin_request', $this->_current_view, $this);
1119
+		}
1120
+		// strip _wp_http_referer from the server REQUEST_URI
1121
+		// else it grows in length on every submission due to recursion,
1122
+		// ultimately causing a "Request-URI Too Large" error
1123
+		$request_uri = remove_query_arg(
1124
+			'_wp_http_referer',
1125
+			wp_unslash($this->request->getServerParam('REQUEST_URI'))
1126
+		);
1127
+		// set new value in both our Request object and the super global
1128
+		$this->request->setServerParam('REQUEST_URI', $request_uri, true);
1129
+		if (! empty($func)) {
1130
+			if (is_array($func)) {
1131
+				[$class, $method] = $func;
1132
+			} elseif (strpos($func, '::') !== false) {
1133
+				[$class, $method] = explode('::', $func);
1134
+			} else {
1135
+				$class  = $this;
1136
+				$method = $func;
1137
+			}
1138
+			if (! (is_object($class) && $class === $this)) {
1139
+				// send along this admin page object for access by addons.
1140
+				$args['admin_page_object'] = $this;
1141
+			}
1142
+			if (
1143
+				// is it a method on a class that doesn't work?
1144
+				(
1145
+					(
1146
+						method_exists($class, $method)
1147
+						&& call_user_func_array([$class, $method], $args) === false
1148
+					)
1149
+					&& (
1150
+						// is it a standalone function that doesn't work?
1151
+						function_exists($method)
1152
+						&& call_user_func_array(
1153
+							$func,
1154
+							array_merge(['admin_page_object' => $this], $args)
1155
+						) === false
1156
+					)
1157
+				)
1158
+				|| (
1159
+					// is it neither a class method NOR a standalone function?
1160
+					! method_exists($class, $method)
1161
+					&& ! function_exists($method)
1162
+				)
1163
+			) {
1164
+				// user error msg
1165
+				$error_msg = esc_html__(
1166
+					'An error occurred. The  requested page route could not be found.',
1167
+					'event_espresso'
1168
+				);
1169
+				// developer error msg
1170
+				$error_msg .= '||';
1171
+				$error_msg .= sprintf(
1172
+					esc_html__(
1173
+						'Page route "%s" could not be called. Check that the spelling for method names and actions in the "_page_routes" array are all correct.',
1174
+						'event_espresso'
1175
+					),
1176
+					$method
1177
+				);
1178
+			}
1179
+			if (! empty($error_msg)) {
1180
+				throw new EE_Error($error_msg);
1181
+			}
1182
+		}
1183
+		// if we've routed and this route has a no headers route AND a sent_headers_route,
1184
+		// then we need to reset the routing properties to the new route.
1185
+		// 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.
1186
+		if (
1187
+			$this->_is_UI_request === false
1188
+			&& is_array($this->_route)
1189
+			&& ! empty($this->_route['headers_sent_route'])
1190
+		) {
1191
+			$this->_reset_routing_properties($this->_route['headers_sent_route']);
1192
+		}
1193
+	}
1194
+
1195
+
1196
+	/**
1197
+	 * This method just allows the resetting of page properties in the case where a no headers
1198
+	 * route redirects to a headers route in its route config.
1199
+	 *
1200
+	 * @param string $new_route New (non header) route to redirect to.
1201
+	 * @return   void
1202
+	 * @throws ReflectionException
1203
+	 * @throws InvalidArgumentException
1204
+	 * @throws InvalidInterfaceException
1205
+	 * @throws InvalidDataTypeException
1206
+	 * @throws EE_Error
1207
+	 * @since   4.3.0
1208
+	 */
1209
+	protected function _reset_routing_properties($new_route)
1210
+	{
1211
+		$this->_is_UI_request = true;
1212
+		// now we set the current route to whatever the headers_sent_route is set at
1213
+		$this->request->setRequestParam('action', $new_route);
1214
+		// rerun page setup
1215
+		$this->_page_setup();
1216
+	}
1217
+
1218
+
1219
+	/**
1220
+	 * _add_query_arg
1221
+	 * adds nonce to array of arguments then calls WP add_query_arg function
1222
+	 *(internally just uses EEH_URL's function with the same name)
1223
+	 *
1224
+	 * @param array  $args
1225
+	 * @param string $url
1226
+	 * @param bool   $sticky                  if true, then the existing Request params will be appended to the
1227
+	 *                                        generated url in an associative array indexed by the key 'wp_referer';
1228
+	 *                                        Example usage: If the current page is:
1229
+	 *                                        http://mydomain.com/wp-admin/admin.php?page=espresso_registrations
1230
+	 *                                        &action=default&event_id=20&month_range=March%202015
1231
+	 *                                        &_wpnonce=5467821
1232
+	 *                                        and you call:
1233
+	 *                                        EE_Admin_Page::add_query_args_and_nonce(
1234
+	 *                                        array(
1235
+	 *                                        'action' => 'resend_something',
1236
+	 *                                        'page=>espresso_registrations'
1237
+	 *                                        ),
1238
+	 *                                        $some_url,
1239
+	 *                                        true
1240
+	 *                                        );
1241
+	 *                                        It will produce a url in this structure:
1242
+	 *                                        http://{$some_url}/?page=espresso_registrations&action=resend_something
1243
+	 *                                        &wp_referer[action]=default&wp_referer[event_id]=20&wpreferer[
1244
+	 *                                        month_range]=March%202015
1245
+	 * @param bool   $exclude_nonce           If true, the the nonce will be excluded from the generated nonce.
1246
+	 * @return string
1247
+	 */
1248
+	public static function add_query_args_and_nonce(
1249
+		$args = [],
1250
+		$url = '',
1251
+		$sticky = false,
1252
+		$exclude_nonce = false
1253
+	) {
1254
+		// if there is a _wp_http_referer include the values from the request but only if sticky = true
1255
+		if ($sticky) {
1256
+			/** @var RequestInterface $request */
1257
+			$request = LoaderFactory::getLoader()->getShared(RequestInterface::class);
1258
+			$request->unSetRequestParams(['_wp_http_referer', 'wp_referer'], true);
1259
+			$request->unSetServerParam('_wp_http_referer', true);
1260
+			foreach ($request->requestParams() as $key => $value) {
1261
+				// do not add nonces
1262
+				if (strpos($key, 'nonce') !== false) {
1263
+					continue;
1264
+				}
1265
+				$args[ 'wp_referer[' . $key . ']' ] = is_string($value) ? htmlspecialchars($value) : $value;
1266
+			}
1267
+		}
1268
+		return EEH_URL::add_query_args_and_nonce($args, $url, $exclude_nonce);
1269
+	}
1270
+
1271
+
1272
+	/**
1273
+	 * This returns a generated link that will load the related help tab.
1274
+	 *
1275
+	 * @param string $help_tab_id the id for the connected help tab
1276
+	 * @param string $icon_style  (optional) include css class for the style you want to use for the help icon.
1277
+	 * @param string $help_text   (optional) send help text you want to use for the link if default not to be used
1278
+	 * @return string              generated link
1279
+	 * @uses EEH_Template::get_help_tab_link()
1280
+	 */
1281
+	protected function _get_help_tab_link($help_tab_id, $icon_style = '', $help_text = '')
1282
+	{
1283
+		return EEH_Template::get_help_tab_link(
1284
+			$help_tab_id,
1285
+			$this->page_slug,
1286
+			$this->_req_action,
1287
+			$icon_style,
1288
+			$help_text
1289
+		);
1290
+	}
1291
+
1292
+
1293
+	/**
1294
+	 * _add_help_tabs
1295
+	 * Note child classes define their help tabs within the page_config array.
1296
+	 *
1297
+	 * @link   http://codex.wordpress.org/Function_Reference/add_help_tab
1298
+	 * @return void
1299
+	 * @throws DomainException
1300
+	 * @throws EE_Error
1301
+	 * @throws ReflectionException
1302
+	 */
1303
+	protected function _add_help_tabs()
1304
+	{
1305
+		if (isset($this->_page_config[ $this->_req_action ])) {
1306
+			$config = $this->_page_config[ $this->_req_action ];
1307
+			// let's see if there is a help_sidebar set for the current route and we'll set that up for usage as well.
1308
+			if (is_array($config) && isset($config['help_sidebar'])) {
1309
+				// check that the callback given is valid
1310
+				if (! method_exists($this, $config['help_sidebar'])) {
1311
+					throw new EE_Error(
1312
+						sprintf(
1313
+							esc_html__(
1314
+								'The _page_config array has a callback set for the "help_sidebar" option.  However the callback given (%s) is not a valid callback.  Doublecheck the spelling and make sure this method exists for the class %s',
1315
+								'event_espresso'
1316
+							),
1317
+							$config['help_sidebar'],
1318
+							$this->class_name
1319
+						)
1320
+					);
1321
+				}
1322
+				$content = apply_filters(
1323
+					'FHEE__' . $this->class_name . '__add_help_tabs__help_sidebar',
1324
+					$this->{$config['help_sidebar']}()
1325
+				);
1326
+				$this->_current_screen->set_help_sidebar($content);
1327
+			}
1328
+			if (! isset($config['help_tabs'])) {
1329
+				return;
1330
+			} //no help tabs for this route
1331
+			foreach ((array) $config['help_tabs'] as $tab_id => $cfg) {
1332
+				// we're here so there ARE help tabs!
1333
+				// make sure we've got what we need
1334
+				if (! isset($cfg['title'])) {
1335
+					throw new EE_Error(
1336
+						esc_html__(
1337
+							'The _page_config array is not set up properly for help tabs.  It is missing a title',
1338
+							'event_espresso'
1339
+						)
1340
+					);
1341
+				}
1342
+				if (! isset($cfg['filename']) && ! isset($cfg['callback']) && ! isset($cfg['content'])) {
1343
+					throw new EE_Error(
1344
+						esc_html__(
1345
+							'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',
1346
+							'event_espresso'
1347
+						)
1348
+					);
1349
+				}
1350
+				// first priority goes to content.
1351
+				if (! empty($cfg['content'])) {
1352
+					$content = ! empty($cfg['content']) ? $cfg['content'] : null;
1353
+					// second priority goes to filename
1354
+				} elseif (! empty($cfg['filename'])) {
1355
+					$file_path = $this->_get_dir() . '/help_tabs/' . $cfg['filename'] . '.help_tab.php';
1356
+					// 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)
1357
+					$file_path = ! is_readable($file_path) ? EE_ADMIN_PAGES
1358
+															 . basename($this->_get_dir())
1359
+															 . '/help_tabs/'
1360
+															 . $cfg['filename']
1361
+															 . '.help_tab.php' : $file_path;
1362
+					// if file is STILL not readable then let's do a EE_Error so its more graceful than a fatal error.
1363
+					if (! isset($cfg['callback']) && ! is_readable($file_path)) {
1364
+						EE_Error::add_error(
1365
+							sprintf(
1366
+								esc_html__(
1367
+									'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',
1368
+									'event_espresso'
1369
+								),
1370
+								$tab_id,
1371
+								key($config),
1372
+								$file_path
1373
+							),
1374
+							__FILE__,
1375
+							__FUNCTION__,
1376
+							__LINE__
1377
+						);
1378
+						return;
1379
+					}
1380
+					$template_args['admin_page_obj'] = $this;
1381
+					$content                         = EEH_Template::display_template(
1382
+						$file_path,
1383
+						$template_args,
1384
+						true
1385
+					);
1386
+				} else {
1387
+					$content = '';
1388
+				}
1389
+				// check if callback is valid
1390
+				if (
1391
+					empty($content)
1392
+					&& (
1393
+						! isset($cfg['callback']) || ! method_exists($this, $cfg['callback'])
1394
+					)
1395
+				) {
1396
+					EE_Error::add_error(
1397
+						sprintf(
1398
+							esc_html__(
1399
+								'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.',
1400
+								'event_espresso'
1401
+							),
1402
+							$cfg['title']
1403
+						),
1404
+						__FILE__,
1405
+						__FUNCTION__,
1406
+						__LINE__
1407
+					);
1408
+					return;
1409
+				}
1410
+				// setup config array for help tab method
1411
+				$id  = $this->page_slug . '-' . $this->_req_action . '-' . $tab_id;
1412
+				$_ht = [
1413
+					'id'       => $id,
1414
+					'title'    => $cfg['title'],
1415
+					'callback' => isset($cfg['callback']) && empty($content) ? [$this, $cfg['callback']] : null,
1416
+					'content'  => $content,
1417
+				];
1418
+				$this->_current_screen->add_help_tab($_ht);
1419
+			}
1420
+		}
1421
+	}
1422
+
1423
+
1424
+	/**
1425
+	 * This simply sets up any qtips that have been defined in the page config
1426
+	 *
1427
+	 * @return void
1428
+	 * @throws ReflectionException
1429
+	 * @throws EE_Error
1430
+	 */
1431
+	protected function _add_qtips()
1432
+	{
1433
+		if (isset($this->_route_config['qtips'])) {
1434
+			$qtips = (array) $this->_route_config['qtips'];
1435
+			// load qtip loader
1436
+			$path = [
1437
+				$this->_get_dir() . '/qtips/',
1438
+				EE_ADMIN_PAGES . basename($this->_get_dir()) . '/qtips/',
1439
+			];
1440
+			EEH_Qtip_Loader::instance()->register($qtips, $path);
1441
+		}
1442
+	}
1443
+
1444
+
1445
+	/**
1446
+	 * _set_nav_tabs
1447
+	 * This sets up the nav tabs from the page_routes array.  This method can be overwritten by child classes if you
1448
+	 * wish to add additional tabs or modify accordingly.
1449
+	 *
1450
+	 * @return void
1451
+	 * @throws InvalidArgumentException
1452
+	 * @throws InvalidInterfaceException
1453
+	 * @throws InvalidDataTypeException
1454
+	 */
1455
+	protected function _set_nav_tabs()
1456
+	{
1457
+		$i = 0;
1458
+		$only_tab = count($this->_page_config) < 2;
1459
+		foreach ($this->_page_config as $slug => $config) {
1460
+			if (! is_array($config) || empty($config['nav'])) {
1461
+				continue;
1462
+			}
1463
+			// no nav tab for this config
1464
+			// check for persistent flag
1465
+			if ($slug !== $this->_req_action && isset($config['nav']['persistent']) && ! $config['nav']['persistent']) {
1466
+				// nav tab is only to appear when route requested.
1467
+				continue;
1468
+			}
1469
+			if (! $this->check_user_access($slug, true)) {
1470
+				// no nav tab because current user does not have access.
1471
+				continue;
1472
+			}
1473
+			$css_class = isset($config['css_class']) ? $config['css_class'] . ' ' : '';
1474
+			$css_class .= $only_tab ? ' ee-only-tab' : '';
1475
+
1476
+			$this->_nav_tabs[ $slug ] = [
1477
+				'url'       => $config['nav']['url'] ?? EE_Admin_Page::add_query_args_and_nonce(
1478
+						['action' => $slug],
1479
+						$this->_admin_base_url
1480
+					),
1481
+				'link_text' => $this->navTabLabel($config['nav'], $slug),
1482
+				'css_class' => $this->_req_action === $slug ? $css_class . ' nav-tab-active' : $css_class,
1483
+				'order'     => $config['nav']['order'] ?? $i,
1484
+			];
1485
+			$i++;
1486
+		}
1487
+		// if $this->_nav_tabs is empty then lets set the default
1488
+		if (empty($this->_nav_tabs)) {
1489
+			$this->_nav_tabs[ $this->_default_nav_tab_name ] = [
1490
+				'url'       => $this->_admin_base_url,
1491
+				'link_text' => ucwords(str_replace('_', ' ', $this->_default_nav_tab_name)),
1492
+				'css_class' => 'nav-tab-active',
1493
+				'order'     => 10,
1494
+			];
1495
+		}
1496
+		// now let's sort the tabs according to order
1497
+		usort($this->_nav_tabs, [$this, '_sort_nav_tabs']);
1498
+	}
1499
+
1500
+
1501
+	private function navTabLabel(array $nav_tab, string $slug): string
1502
+	{
1503
+		$label = $nav_tab['label'] ?? ucwords(str_replace('_', ' ', $slug));
1504
+		$icon = $nav_tab['icon'] ?? null;
1505
+		$icon = $icon ? '<span class="dashicons ' . $icon . '"></span>' : '';
1506
+		return '
1507 1507
             <span class="ee-admin-screen-tab__label">
1508 1508
                 ' . $icon . '
1509 1509
                 <span class="ee-nav-label__text">' . $label . '</span>
1510 1510
             </span>';
1511
-    }
1512
-
1513
-    /**
1514
-     * _set_current_labels
1515
-     * This method modifies the _labels property with any optional specific labels indicated in the _page_routes
1516
-     * property array
1517
-     *
1518
-     * @return void
1519
-     */
1520
-    private function _set_current_labels()
1521
-    {
1522
-        if (is_array($this->_route_config) && isset($this->_route_config['labels'])) {
1523
-            foreach ($this->_route_config['labels'] as $label => $text) {
1524
-                if (is_array($text)) {
1525
-                    foreach ($text as $sublabel => $subtext) {
1526
-                        $this->_labels[ $label ][ $sublabel ] = $subtext;
1527
-                    }
1528
-                } else {
1529
-                    $this->_labels[ $label ] = $text;
1530
-                }
1531
-            }
1532
-        }
1533
-    }
1534
-
1535
-
1536
-    /**
1537
-     *        verifies user access for this admin page
1538
-     *
1539
-     * @param string $route_to_check if present then the capability for the route matching this string is checked.
1540
-     * @param bool   $verify_only    Default is FALSE which means if user check fails then wp_die().  Otherwise just
1541
-     *                               return false if verify fail.
1542
-     * @return bool
1543
-     * @throws InvalidArgumentException
1544
-     * @throws InvalidDataTypeException
1545
-     * @throws InvalidInterfaceException
1546
-     */
1547
-    public function check_user_access($route_to_check = '', $verify_only = false)
1548
-    {
1549
-        do_action('AHEE_log', __FILE__, __FUNCTION__, '');
1550
-        $route_to_check = empty($route_to_check) ? $this->_req_action : $route_to_check;
1551
-        $capability = ! empty($route_to_check) && isset($this->_page_routes[ $route_to_check ])
1552
-                                      && is_array($this->_page_routes[ $route_to_check ])
1553
-                        && ! empty($this->_page_routes[ $route_to_check ]['capability'])
1554
-            ? $this->_page_routes[ $route_to_check ]['capability']
1555
-            : null;
1556
-
1557
-        if (empty($capability) && empty($route_to_check)) {
1558
-            $capability = is_array($this->_route) && empty($this->_route['capability']) ? 'manage_options'
1559
-                : $this->_route['capability'];
1560
-        } else {
1561
-            $capability = empty($capability) ? 'manage_options' : $capability;
1562
-        }
1563
-        $id = is_array($this->_route) && ! empty($this->_route['obj_id']) ? $this->_route['obj_id'] : 0;
1564
-        if (
1565
-            ! $this->request->isAjax()
1566
-            && (
1567
-                ! function_exists('is_admin')
1568
-                || ! EE_Registry::instance()->CAP->current_user_can(
1569
-                    $capability,
1570
-                    $this->page_slug
1571
-                    . '_'
1572
-                    . $route_to_check,
1573
-                    $id
1574
-                )
1575
-            )
1576
-        ) {
1577
-            if ($verify_only) {
1578
-                return false;
1579
-            }
1580
-            if (is_user_logged_in()) {
1581
-                wp_die(esc_html__('You do not have access to this route.', 'event_espresso'));
1582
-            } else {
1583
-                return false;
1584
-            }
1585
-        }
1586
-        return true;
1587
-    }
1588
-
1589
-
1590
-    /**
1591
-     * @param string                 $box_id
1592
-     * @param string                 $title
1593
-     * @param callable|string|null   $callback
1594
-     * @param string|array|WP_Screen $screen
1595
-     * @param string                 $context
1596
-     * @param string                 $priority
1597
-     * @param array|null             $callback_args
1598
-     */
1599
-    protected function addMetaBox(
1600
-        string $box_id,
1601
-        string $title,
1602
-        $callback,
1603
-        $screen,
1604
-        string $context = 'normal',
1605
-        string $priority = 'default',
1606
-        ?array $callback_args = null
1607
-    ) {
1608
-        if (! is_callable($callback)) {
1609
-            return;
1610
-        }
1611
-
1612
-        add_meta_box($box_id, $title, $callback, $screen, $context, $priority, $callback_args);
1613
-        add_filter(
1614
-            "postbox_classes_{$this->_wp_page_slug}_{$box_id}",
1615
-            function ($classes) {
1616
-                array_push($classes, 'ee-admin-container');
1617
-                return $classes;
1618
-            }
1619
-        );
1620
-    }
1621
-
1622
-
1623
-    /**
1624
-     * admin_init_global
1625
-     * This runs all the code that we want executed within the WP admin_init hook.
1626
-     * This method executes for ALL EE Admin pages.
1627
-     *
1628
-     * @return void
1629
-     */
1630
-    public function admin_init_global()
1631
-    {
1632
-    }
1633
-
1634
-
1635
-    /**
1636
-     * wp_loaded_global
1637
-     * This runs all the code that we want executed within the WP wp_loaded hook.  This method is optional for an
1638
-     * EE_Admin page and will execute on every EE Admin Page load
1639
-     *
1640
-     * @return void
1641
-     */
1642
-    public function wp_loaded()
1643
-    {
1644
-    }
1645
-
1646
-
1647
-    /**
1648
-     * admin_notices
1649
-     * Anything triggered by the 'admin_notices' WP hook should be put in here.  This particular method will apply on
1650
-     * ALL EE_Admin pages.
1651
-     *
1652
-     * @return void
1653
-     */
1654
-    public function admin_notices_global()
1655
-    {
1656
-        $this->_display_no_javascript_warning();
1657
-        $this->_display_espresso_notices();
1658
-    }
1659
-
1660
-
1661
-    public function network_admin_notices_global()
1662
-    {
1663
-        $this->_display_no_javascript_warning();
1664
-        $this->_display_espresso_notices();
1665
-    }
1666
-
1667
-
1668
-    /**
1669
-     * admin_footer_scripts_global
1670
-     * Anything triggered by the 'admin_print_footer_scripts' WP hook should be put in here. This particular method
1671
-     * will apply on ALL EE_Admin pages.
1672
-     *
1673
-     * @return void
1674
-     */
1675
-    public function admin_footer_scripts_global()
1676
-    {
1677
-        $this->_add_admin_page_ajax_loading_img();
1678
-        $this->_add_admin_page_overlay();
1679
-        // if metaboxes are present we need to add the nonce field
1680
-        if (
1681
-            isset($this->_route_config['metaboxes'])
1682
-            || isset($this->_route_config['list_table'])
1683
-            || (isset($this->_route_config['has_metaboxes']) && $this->_route_config['has_metaboxes'])
1684
-        ) {
1685
-            wp_nonce_field('closedpostboxes', 'closedpostboxesnonce', false);
1686
-            wp_nonce_field('meta-box-order', 'meta-box-order-nonce', false);
1687
-        }
1688
-    }
1689
-
1690
-
1691
-    /**
1692
-     * admin_footer_global
1693
-     * Anything triggered by the wp 'admin_footer' wp hook should be put in here.
1694
-     * This particular method will apply on ALL EE_Admin Pages.
1695
-     *
1696
-     * @return void
1697
-     */
1698
-    public function admin_footer_global()
1699
-    {
1700
-        // dialog container for dialog helper
1701
-        echo '
1511
+	}
1512
+
1513
+	/**
1514
+	 * _set_current_labels
1515
+	 * This method modifies the _labels property with any optional specific labels indicated in the _page_routes
1516
+	 * property array
1517
+	 *
1518
+	 * @return void
1519
+	 */
1520
+	private function _set_current_labels()
1521
+	{
1522
+		if (is_array($this->_route_config) && isset($this->_route_config['labels'])) {
1523
+			foreach ($this->_route_config['labels'] as $label => $text) {
1524
+				if (is_array($text)) {
1525
+					foreach ($text as $sublabel => $subtext) {
1526
+						$this->_labels[ $label ][ $sublabel ] = $subtext;
1527
+					}
1528
+				} else {
1529
+					$this->_labels[ $label ] = $text;
1530
+				}
1531
+			}
1532
+		}
1533
+	}
1534
+
1535
+
1536
+	/**
1537
+	 *        verifies user access for this admin page
1538
+	 *
1539
+	 * @param string $route_to_check if present then the capability for the route matching this string is checked.
1540
+	 * @param bool   $verify_only    Default is FALSE which means if user check fails then wp_die().  Otherwise just
1541
+	 *                               return false if verify fail.
1542
+	 * @return bool
1543
+	 * @throws InvalidArgumentException
1544
+	 * @throws InvalidDataTypeException
1545
+	 * @throws InvalidInterfaceException
1546
+	 */
1547
+	public function check_user_access($route_to_check = '', $verify_only = false)
1548
+	{
1549
+		do_action('AHEE_log', __FILE__, __FUNCTION__, '');
1550
+		$route_to_check = empty($route_to_check) ? $this->_req_action : $route_to_check;
1551
+		$capability = ! empty($route_to_check) && isset($this->_page_routes[ $route_to_check ])
1552
+									  && is_array($this->_page_routes[ $route_to_check ])
1553
+						&& ! empty($this->_page_routes[ $route_to_check ]['capability'])
1554
+			? $this->_page_routes[ $route_to_check ]['capability']
1555
+			: null;
1556
+
1557
+		if (empty($capability) && empty($route_to_check)) {
1558
+			$capability = is_array($this->_route) && empty($this->_route['capability']) ? 'manage_options'
1559
+				: $this->_route['capability'];
1560
+		} else {
1561
+			$capability = empty($capability) ? 'manage_options' : $capability;
1562
+		}
1563
+		$id = is_array($this->_route) && ! empty($this->_route['obj_id']) ? $this->_route['obj_id'] : 0;
1564
+		if (
1565
+			! $this->request->isAjax()
1566
+			&& (
1567
+				! function_exists('is_admin')
1568
+				|| ! EE_Registry::instance()->CAP->current_user_can(
1569
+					$capability,
1570
+					$this->page_slug
1571
+					. '_'
1572
+					. $route_to_check,
1573
+					$id
1574
+				)
1575
+			)
1576
+		) {
1577
+			if ($verify_only) {
1578
+				return false;
1579
+			}
1580
+			if (is_user_logged_in()) {
1581
+				wp_die(esc_html__('You do not have access to this route.', 'event_espresso'));
1582
+			} else {
1583
+				return false;
1584
+			}
1585
+		}
1586
+		return true;
1587
+	}
1588
+
1589
+
1590
+	/**
1591
+	 * @param string                 $box_id
1592
+	 * @param string                 $title
1593
+	 * @param callable|string|null   $callback
1594
+	 * @param string|array|WP_Screen $screen
1595
+	 * @param string                 $context
1596
+	 * @param string                 $priority
1597
+	 * @param array|null             $callback_args
1598
+	 */
1599
+	protected function addMetaBox(
1600
+		string $box_id,
1601
+		string $title,
1602
+		$callback,
1603
+		$screen,
1604
+		string $context = 'normal',
1605
+		string $priority = 'default',
1606
+		?array $callback_args = null
1607
+	) {
1608
+		if (! is_callable($callback)) {
1609
+			return;
1610
+		}
1611
+
1612
+		add_meta_box($box_id, $title, $callback, $screen, $context, $priority, $callback_args);
1613
+		add_filter(
1614
+			"postbox_classes_{$this->_wp_page_slug}_{$box_id}",
1615
+			function ($classes) {
1616
+				array_push($classes, 'ee-admin-container');
1617
+				return $classes;
1618
+			}
1619
+		);
1620
+	}
1621
+
1622
+
1623
+	/**
1624
+	 * admin_init_global
1625
+	 * This runs all the code that we want executed within the WP admin_init hook.
1626
+	 * This method executes for ALL EE Admin pages.
1627
+	 *
1628
+	 * @return void
1629
+	 */
1630
+	public function admin_init_global()
1631
+	{
1632
+	}
1633
+
1634
+
1635
+	/**
1636
+	 * wp_loaded_global
1637
+	 * This runs all the code that we want executed within the WP wp_loaded hook.  This method is optional for an
1638
+	 * EE_Admin page and will execute on every EE Admin Page load
1639
+	 *
1640
+	 * @return void
1641
+	 */
1642
+	public function wp_loaded()
1643
+	{
1644
+	}
1645
+
1646
+
1647
+	/**
1648
+	 * admin_notices
1649
+	 * Anything triggered by the 'admin_notices' WP hook should be put in here.  This particular method will apply on
1650
+	 * ALL EE_Admin pages.
1651
+	 *
1652
+	 * @return void
1653
+	 */
1654
+	public function admin_notices_global()
1655
+	{
1656
+		$this->_display_no_javascript_warning();
1657
+		$this->_display_espresso_notices();
1658
+	}
1659
+
1660
+
1661
+	public function network_admin_notices_global()
1662
+	{
1663
+		$this->_display_no_javascript_warning();
1664
+		$this->_display_espresso_notices();
1665
+	}
1666
+
1667
+
1668
+	/**
1669
+	 * admin_footer_scripts_global
1670
+	 * Anything triggered by the 'admin_print_footer_scripts' WP hook should be put in here. This particular method
1671
+	 * will apply on ALL EE_Admin pages.
1672
+	 *
1673
+	 * @return void
1674
+	 */
1675
+	public function admin_footer_scripts_global()
1676
+	{
1677
+		$this->_add_admin_page_ajax_loading_img();
1678
+		$this->_add_admin_page_overlay();
1679
+		// if metaboxes are present we need to add the nonce field
1680
+		if (
1681
+			isset($this->_route_config['metaboxes'])
1682
+			|| isset($this->_route_config['list_table'])
1683
+			|| (isset($this->_route_config['has_metaboxes']) && $this->_route_config['has_metaboxes'])
1684
+		) {
1685
+			wp_nonce_field('closedpostboxes', 'closedpostboxesnonce', false);
1686
+			wp_nonce_field('meta-box-order', 'meta-box-order-nonce', false);
1687
+		}
1688
+	}
1689
+
1690
+
1691
+	/**
1692
+	 * admin_footer_global
1693
+	 * Anything triggered by the wp 'admin_footer' wp hook should be put in here.
1694
+	 * This particular method will apply on ALL EE_Admin Pages.
1695
+	 *
1696
+	 * @return void
1697
+	 */
1698
+	public function admin_footer_global()
1699
+	{
1700
+		// dialog container for dialog helper
1701
+		echo '
1702 1702
         <div class="ee-admin-dialog-container auto-hide hidden">
1703 1703
             <div class="ee-notices"></div>
1704 1704
             <div class="ee-admin-dialog-container-inner-content"></div>
1705 1705
         </div>
1706 1706
         ';
1707 1707
 
1708
-        // current set timezone for timezone js
1709
-        echo '<span id="current_timezone" class="hidden">' . esc_html(EEH_DTT_Helper::get_timezone()) . '</span>';
1710
-    }
1711
-
1712
-
1713
-    /**
1714
-     * This function sees if there is a method for help popup content existing for the given route.  If there is then
1715
-     * we'll use the retrieved array to output the content using the template. For child classes: If you want to have
1716
-     * help popups then in your templates or your content you set "triggers" for the content using the
1717
-     * "_set_help_trigger('help_trigger_id')" where "help_trigger_id" is what you will use later in your custom method
1718
-     * for the help popup content on that page. Then in your Child_Admin_Page class you need to define a help popup
1719
-     * method for the content in the format "_help_popup_content_{route_name}()"  So if you are setting help content
1720
-     * for the
1721
-     * 'edit_event' route you should have a method named "_help_popup_content_edit_route". In your defined
1722
-     * "help_popup_content_..." method.  You must prepare and return an array in the following format array(
1723
-     *    'help_trigger_id' => array(
1724
-     *        'title' => esc_html__('localized title for popup', 'event_espresso'),
1725
-     *        'content' => esc_html__('localized content for popup', 'event_espresso')
1726
-     *    )
1727
-     * );
1728
-     * Then the EE_Admin_Parent will take care of making sure that is setup properly on the correct route.
1729
-     *
1730
-     * @param array $help_array
1731
-     * @param bool  $display
1732
-     * @return string content
1733
-     * @throws DomainException
1734
-     * @throws EE_Error
1735
-     */
1736
-    protected function _set_help_popup_content($help_array = [], $display = false)
1737
-    {
1738
-        $content    = '';
1739
-        $help_array = empty($help_array) ? $this->_get_help_content() : $help_array;
1740
-        // loop through the array and setup content
1741
-        foreach ($help_array as $trigger => $help) {
1742
-            // make sure the array is setup properly
1743
-            if (! isset($help['title'], $help['content'])) {
1744
-                throw new EE_Error(
1745
-                    esc_html__(
1746
-                        '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',
1747
-                        'event_espresso'
1748
-                    )
1749
-                );
1750
-            }
1751
-            // we're good so let's setup the template vars and then assign parsed template content to our content.
1752
-            $template_args = [
1753
-                'help_popup_id'      => $trigger,
1754
-                'help_popup_title'   => $help['title'],
1755
-                'help_popup_content' => $help['content'],
1756
-            ];
1757
-            $content       .= EEH_Template::display_template(
1758
-                EE_ADMIN_TEMPLATE . 'admin_help_popup.template.php',
1759
-                $template_args,
1760
-                true
1761
-            );
1762
-        }
1763
-        if ($display) {
1764
-            echo wp_kses($content, AllowedTags::getWithFormTags());
1765
-            return '';
1766
-        }
1767
-        return $content;
1768
-    }
1769
-
1770
-
1771
-    /**
1772
-     * All this does is retrieve the help content array if set by the EE_Admin_Page child
1773
-     *
1774
-     * @return array properly formatted array for help popup content
1775
-     * @throws EE_Error
1776
-     */
1777
-    private function _get_help_content()
1778
-    {
1779
-        // what is the method we're looking for?
1780
-        $method_name = '_help_popup_content_' . $this->_req_action;
1781
-        // if method doesn't exist let's get out.
1782
-        if (! method_exists($this, $method_name)) {
1783
-            return [];
1784
-        }
1785
-        // k we're good to go let's retrieve the help array
1786
-        $help_array = $this->{$method_name}();
1787
-        // make sure we've got an array!
1788
-        if (! is_array($help_array)) {
1789
-            throw new EE_Error(
1790
-                esc_html__(
1791
-                    'Something went wrong with help popup content generation. Expecting an array and well, this ain\'t no array bub.',
1792
-                    'event_espresso'
1793
-                )
1794
-            );
1795
-        }
1796
-        return $help_array;
1797
-    }
1798
-
1799
-
1800
-    /**
1801
-     * EE Admin Pages can use this to set a properly formatted trigger for a help popup.
1802
-     * By default the trigger html is printed.  Otherwise it can be returned if the $display flag is set "false"
1803
-     * See comments made on the _set_help_content method for understanding other parts to the help popup tool.
1804
-     *
1805
-     * @param string  $trigger_id reference for retrieving the trigger content for the popup
1806
-     * @param boolean $display    if false then we return the trigger string
1807
-     * @param array   $dimensions an array of dimensions for the box (array(h,w))
1808
-     * @return string
1809
-     * @throws DomainException
1810
-     * @throws EE_Error
1811
-     */
1812
-    protected function _set_help_trigger($trigger_id, $display = true, $dimensions = ['400', '640'])
1813
-    {
1814
-        if ($this->request->isAjax()) {
1815
-            return '';
1816
-        }
1817
-        // 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
1818
-        $help_array   = $this->_get_help_content();
1819
-        $help_content = '';
1820
-        if (empty($help_array) || ! isset($help_array[ $trigger_id ])) {
1821
-            $help_array[ $trigger_id ] = [
1822
-                'title'   => esc_html__('Missing Content', 'event_espresso'),
1823
-                'content' => esc_html__(
1824
-                    '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.)',
1825
-                    'event_espresso'
1826
-                ),
1827
-            ];
1828
-            $help_content = $this->_set_help_popup_content($help_array);
1829
-        }
1830
-        // let's setup the trigger
1831
-        $content = '<a class="ee-dialog" href="?height='
1832
-                   . esc_attr($dimensions[0])
1833
-                   . '&width='
1834
-                   . esc_attr($dimensions[1])
1835
-                   . '&inlineId='
1836
-                   . esc_attr($trigger_id)
1837
-                   . '" target="_blank"><span class="question ee-help-popup-question"></span></a>';
1838
-        $content .= $help_content;
1839
-        if ($display) {
1840
-            echo wp_kses($content, AllowedTags::getWithFormTags());
1841
-            return '';
1842
-        }
1843
-        return $content;
1844
-    }
1845
-
1846
-
1847
-    /**
1848
-     * _add_global_screen_options
1849
-     * Add any extra wp_screen_options within this method using built-in WP functions/methods for doing so.
1850
-     * This particular method will add_screen_options on ALL EE_Admin Pages
1851
-     *
1852
-     * @link   http://chrismarslender.com/wp-tutorials/wordpress-screen-options-tutorial/
1853
-     *         see also WP_Screen object documents...
1854
-     * @link   http://codex.wordpress.org/Class_Reference/WP_Screen
1855
-     * @abstract
1856
-     * @return void
1857
-     */
1858
-    private function _add_global_screen_options()
1859
-    {
1860
-    }
1861
-
1862
-
1863
-    /**
1864
-     * _add_global_feature_pointers
1865
-     * This method is used for implementing any "feature pointers" (using built-in WP styling js).
1866
-     * This particular method will implement feature pointers for ALL EE_Admin pages.
1867
-     * Note: this is just a placeholder for now.  Implementation will come down the road
1868
-     *
1869
-     * @see    WP_Internal_Pointers class in wp-admin/includes/template.php for example (its a final class so can't be
1870
-     *         extended) also see:
1871
-     * @link   http://eamann.com/tech/wordpress-portland/
1872
-     * @abstract
1873
-     * @return void
1874
-     */
1875
-    private function _add_global_feature_pointers()
1876
-    {
1877
-    }
1878
-
1879
-
1880
-    /**
1881
-     * load_global_scripts_styles
1882
-     * The scripts and styles enqueued in here will be loaded on every EE Admin page
1883
-     *
1884
-     * @return void
1885
-     */
1886
-    public function load_global_scripts_styles()
1887
-    {
1888
-        // add debugging styles
1889
-        if (WP_DEBUG) {
1890
-            add_action('admin_head', [$this, 'add_xdebug_style']);
1891
-        }
1892
-        // taking care of metaboxes
1893
-        if (
1894
-            empty($this->_cpt_route)
1895
-            && (isset($this->_route_config['metaboxes']) || isset($this->_route_config['has_metaboxes']))
1896
-        ) {
1897
-            wp_enqueue_script('dashboard');
1898
-        }
1899
-
1900
-        wp_enqueue_script(JqueryAssetManager::JS_HANDLE_JQUERY_UI_TOUCH_PUNCH);
1901
-        wp_enqueue_script(EspressoLegacyAdminAssetManager::JS_HANDLE_EE_ADMIN);
1902
-        // LOCALIZED DATA
1903
-        // localize script for ajax lazy loading
1904
-        wp_localize_script(
1905
-            EspressoLegacyAdminAssetManager::JS_HANDLE_EE_ADMIN,
1906
-            'eeLazyLoadingContainers',
1907
-            apply_filters(
1908
-                'FHEE__EE_Admin_Page_Core__load_global_scripts_styles__loader_containers',
1909
-                ['espresso_news_post_box_content']
1910
-            )
1911
-        );
1912
-        StatusChangeNotice::loadAssets();
1913
-
1914
-        add_filter(
1915
-            'admin_body_class',
1916
-            function ($classes) {
1917
-                if (strpos($classes, 'espresso-admin') === false) {
1918
-                    $classes .= ' espresso-admin';
1919
-                }
1920
-                return $classes;
1921
-            }
1922
-        );
1923
-    }
1924
-
1925
-
1926
-    /**
1927
-     *        admin_footer_scripts_eei18n_js_strings
1928
-     *
1929
-     * @return        void
1930
-     */
1931
-    public function admin_footer_scripts_eei18n_js_strings()
1932
-    {
1933
-        EE_Registry::$i18n_js_strings['ajax_url']       = WP_AJAX_URL;
1934
-        EE_Registry::$i18n_js_strings['confirm_delete'] = wp_strip_all_tags(
1935
-            __(
1936
-                '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!!!',
1937
-                'event_espresso'
1938
-            )
1939
-        );
1940
-        EE_Registry::$i18n_js_strings['January']        = wp_strip_all_tags(__('January', 'event_espresso'));
1941
-        EE_Registry::$i18n_js_strings['February']       = wp_strip_all_tags(__('February', 'event_espresso'));
1942
-        EE_Registry::$i18n_js_strings['March']          = wp_strip_all_tags(__('March', 'event_espresso'));
1943
-        EE_Registry::$i18n_js_strings['April']          = wp_strip_all_tags(__('April', 'event_espresso'));
1944
-        EE_Registry::$i18n_js_strings['May']            = wp_strip_all_tags(__('May', 'event_espresso'));
1945
-        EE_Registry::$i18n_js_strings['June']           = wp_strip_all_tags(__('June', 'event_espresso'));
1946
-        EE_Registry::$i18n_js_strings['July']           = wp_strip_all_tags(__('July', 'event_espresso'));
1947
-        EE_Registry::$i18n_js_strings['August']         = wp_strip_all_tags(__('August', 'event_espresso'));
1948
-        EE_Registry::$i18n_js_strings['September']      = wp_strip_all_tags(__('September', 'event_espresso'));
1949
-        EE_Registry::$i18n_js_strings['October']        = wp_strip_all_tags(__('October', 'event_espresso'));
1950
-        EE_Registry::$i18n_js_strings['November']       = wp_strip_all_tags(__('November', 'event_espresso'));
1951
-        EE_Registry::$i18n_js_strings['December']       = wp_strip_all_tags(__('December', 'event_espresso'));
1952
-        EE_Registry::$i18n_js_strings['Jan']            = wp_strip_all_tags(__('Jan', 'event_espresso'));
1953
-        EE_Registry::$i18n_js_strings['Feb']            = wp_strip_all_tags(__('Feb', 'event_espresso'));
1954
-        EE_Registry::$i18n_js_strings['Mar']            = wp_strip_all_tags(__('Mar', 'event_espresso'));
1955
-        EE_Registry::$i18n_js_strings['Apr']            = wp_strip_all_tags(__('Apr', 'event_espresso'));
1956
-        EE_Registry::$i18n_js_strings['May']            = wp_strip_all_tags(__('May', 'event_espresso'));
1957
-        EE_Registry::$i18n_js_strings['Jun']            = wp_strip_all_tags(__('Jun', 'event_espresso'));
1958
-        EE_Registry::$i18n_js_strings['Jul']            = wp_strip_all_tags(__('Jul', 'event_espresso'));
1959
-        EE_Registry::$i18n_js_strings['Aug']            = wp_strip_all_tags(__('Aug', 'event_espresso'));
1960
-        EE_Registry::$i18n_js_strings['Sep']            = wp_strip_all_tags(__('Sep', 'event_espresso'));
1961
-        EE_Registry::$i18n_js_strings['Oct']            = wp_strip_all_tags(__('Oct', 'event_espresso'));
1962
-        EE_Registry::$i18n_js_strings['Nov']            = wp_strip_all_tags(__('Nov', 'event_espresso'));
1963
-        EE_Registry::$i18n_js_strings['Dec']            = wp_strip_all_tags(__('Dec', 'event_espresso'));
1964
-        EE_Registry::$i18n_js_strings['Sunday']         = wp_strip_all_tags(__('Sunday', 'event_espresso'));
1965
-        EE_Registry::$i18n_js_strings['Monday']         = wp_strip_all_tags(__('Monday', 'event_espresso'));
1966
-        EE_Registry::$i18n_js_strings['Tuesday']        = wp_strip_all_tags(__('Tuesday', 'event_espresso'));
1967
-        EE_Registry::$i18n_js_strings['Wednesday']      = wp_strip_all_tags(__('Wednesday', 'event_espresso'));
1968
-        EE_Registry::$i18n_js_strings['Thursday']       = wp_strip_all_tags(__('Thursday', 'event_espresso'));
1969
-        EE_Registry::$i18n_js_strings['Friday']         = wp_strip_all_tags(__('Friday', 'event_espresso'));
1970
-        EE_Registry::$i18n_js_strings['Saturday']       = wp_strip_all_tags(__('Saturday', 'event_espresso'));
1971
-        EE_Registry::$i18n_js_strings['Sun']            = wp_strip_all_tags(__('Sun', 'event_espresso'));
1972
-        EE_Registry::$i18n_js_strings['Mon']            = wp_strip_all_tags(__('Mon', 'event_espresso'));
1973
-        EE_Registry::$i18n_js_strings['Tue']            = wp_strip_all_tags(__('Tue', 'event_espresso'));
1974
-        EE_Registry::$i18n_js_strings['Wed']            = wp_strip_all_tags(__('Wed', 'event_espresso'));
1975
-        EE_Registry::$i18n_js_strings['Thu']            = wp_strip_all_tags(__('Thu', 'event_espresso'));
1976
-        EE_Registry::$i18n_js_strings['Fri']            = wp_strip_all_tags(__('Fri', 'event_espresso'));
1977
-        EE_Registry::$i18n_js_strings['Sat']            = wp_strip_all_tags(__('Sat', 'event_espresso'));
1978
-    }
1979
-
1980
-
1981
-    /**
1982
-     *        load enhanced xdebug styles for ppl with failing eyesight
1983
-     *
1984
-     * @return        void
1985
-     */
1986
-    public function add_xdebug_style()
1987
-    {
1988
-        echo '<style>.xdebug-error { font-size:1.5em; }</style>';
1989
-    }
1990
-
1991
-
1992
-    /************************/
1993
-    /** LIST TABLE METHODS **/
1994
-    /************************/
1995
-    /**
1996
-     * this sets up the list table if the current view requires it.
1997
-     *
1998
-     * @return void
1999
-     * @throws EE_Error
2000
-     * @throws InvalidArgumentException
2001
-     * @throws InvalidDataTypeException
2002
-     * @throws InvalidInterfaceException
2003
-     */
2004
-    protected function _set_list_table()
2005
-    {
2006
-        // first is this a list_table view?
2007
-        if (! isset($this->_route_config['list_table'])) {
2008
-            return;
2009
-        } //not a list_table view so get out.
2010
-        // list table functions are per view specific (because some admin pages might have more than one list table!)
2011
-        $list_table_view = '_set_list_table_views_' . $this->_req_action;
2012
-        if (! method_exists($this, $list_table_view) || $this->{$list_table_view}() === false) {
2013
-            // user error msg
2014
-            $error_msg = esc_html__(
2015
-                'An error occurred. The requested list table views could not be found.',
2016
-                'event_espresso'
2017
-            );
2018
-            // developer error msg
2019
-            $error_msg .= '||'
2020
-                          . sprintf(
2021
-                              esc_html__(
2022
-                                  '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.',
2023
-                                  'event_espresso'
2024
-                              ),
2025
-                              $this->_req_action,
2026
-                              $list_table_view
2027
-                          );
2028
-            throw new EE_Error($error_msg);
2029
-        }
2030
-        // let's provide the ability to filter the views per PAGE AND ROUTE, per PAGE, and globally
2031
-        $this->_views = apply_filters(
2032
-            'FHEE_list_table_views_' . $this->page_slug . '_' . $this->_req_action,
2033
-            $this->_views
2034
-        );
2035
-        $this->_views = apply_filters('FHEE_list_table_views_' . $this->page_slug, $this->_views);
2036
-        $this->_views = apply_filters('FHEE_list_table_views', $this->_views);
2037
-        $this->_set_list_table_view();
2038
-        $this->_set_list_table_object();
2039
-    }
2040
-
2041
-
2042
-    /**
2043
-     * set current view for List Table
2044
-     *
2045
-     * @return void
2046
-     */
2047
-    protected function _set_list_table_view()
2048
-    {
2049
-        $this->_view = isset($this->_views['in_use']) ? 'in_use' : 'all';
2050
-        $status = $this->request->getRequestParam('status', null, 'key');
2051
-        $this->_view = $status && array_key_exists($status, $this->_views)
2052
-            ? $status
2053
-            : $this->_view;
2054
-    }
2055
-
2056
-
2057
-    /**
2058
-     * _set_list_table_object
2059
-     * WP_List_Table objects need to be loaded fairly early so automatic stuff WP does is taken care of.
2060
-     *
2061
-     * @throws InvalidInterfaceException
2062
-     * @throws InvalidArgumentException
2063
-     * @throws InvalidDataTypeException
2064
-     * @throws EE_Error
2065
-     * @throws InvalidInterfaceException
2066
-     */
2067
-    protected function _set_list_table_object()
2068
-    {
2069
-        if (isset($this->_route_config['list_table'])) {
2070
-            if (! class_exists($this->_route_config['list_table'])) {
2071
-                throw new EE_Error(
2072
-                    sprintf(
2073
-                        esc_html__(
2074
-                            '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.',
2075
-                            'event_espresso'
2076
-                        ),
2077
-                        $this->_route_config['list_table'],
2078
-                        $this->class_name
2079
-                    )
2080
-                );
2081
-            }
2082
-            $this->_list_table_object = $this->loader->getShared(
2083
-                $this->_route_config['list_table'],
2084
-                [$this]
2085
-            );
2086
-        }
2087
-    }
2088
-
2089
-
2090
-    /**
2091
-     * get_list_table_view_RLs - get it? View RL ?? VU-RL???  URL ??
2092
-     *
2093
-     * @param array $extra_query_args                     Optional. An array of extra query args to add to the generated
2094
-     *                                                    urls.  The array should be indexed by the view it is being
2095
-     *                                                    added to.
2096
-     * @return array
2097
-     */
2098
-    public function get_list_table_view_RLs($extra_query_args = [])
2099
-    {
2100
-        do_action('AHEE_log', __FILE__, __FUNCTION__, '');
2101
-        if (empty($this->_views)) {
2102
-            $this->_views = [];
2103
-        }
2104
-        // cycle thru views
2105
-        foreach ($this->_views as $key => $view) {
2106
-            $query_args = [];
2107
-            // check for current view
2108
-            $this->_views[ $key ]['class']               = $this->_view === $view['slug'] ? 'current' : '';
2109
-            $query_args['action']                        = $this->_req_action;
2110
-            $query_args[ $this->_req_action . '_nonce' ] = wp_create_nonce($query_args['action'] . '_nonce');
2111
-            $query_args['status']                        = $view['slug'];
2112
-            // merge any other arguments sent in.
2113
-            if (isset($extra_query_args[ $view['slug'] ])) {
2114
-                foreach ($extra_query_args[ $view['slug'] ] as $extra_query_arg) {
2115
-                    $query_args[] = $extra_query_arg;
2116
-                }
2117
-            }
2118
-            $this->_views[ $key ]['url'] = EE_Admin_Page::add_query_args_and_nonce($query_args, $this->_admin_base_url);
2119
-        }
2120
-        return $this->_views;
2121
-    }
2122
-
2123
-
2124
-    /**
2125
-     * _entries_per_page_dropdown
2126
-     * generates a dropdown box for selecting the number of visible rows in an admin page list table
2127
-     *
2128
-     * @param int $max_entries total number of rows in the table
2129
-     * @return string
2130
-     * @todo   : Note: ideally this should be added to the screen options dropdown as that would be consistent with how
2131
-     *         WP does it.
2132
-     */
2133
-    protected function _entries_per_page_dropdown($max_entries = 0)
2134
-    {
2135
-        do_action('AHEE_log', __FILE__, __FUNCTION__, '');
2136
-        $values   = [10, 25, 50, 100];
2137
-        $per_page = $this->request->getRequestParam('per_page', 10, 'int');
2138
-        if ($max_entries) {
2139
-            $values[] = $max_entries;
2140
-            sort($values);
2141
-        }
2142
-        $entries_per_page_dropdown = '
1708
+		// current set timezone for timezone js
1709
+		echo '<span id="current_timezone" class="hidden">' . esc_html(EEH_DTT_Helper::get_timezone()) . '</span>';
1710
+	}
1711
+
1712
+
1713
+	/**
1714
+	 * This function sees if there is a method for help popup content existing for the given route.  If there is then
1715
+	 * we'll use the retrieved array to output the content using the template. For child classes: If you want to have
1716
+	 * help popups then in your templates or your content you set "triggers" for the content using the
1717
+	 * "_set_help_trigger('help_trigger_id')" where "help_trigger_id" is what you will use later in your custom method
1718
+	 * for the help popup content on that page. Then in your Child_Admin_Page class you need to define a help popup
1719
+	 * method for the content in the format "_help_popup_content_{route_name}()"  So if you are setting help content
1720
+	 * for the
1721
+	 * 'edit_event' route you should have a method named "_help_popup_content_edit_route". In your defined
1722
+	 * "help_popup_content_..." method.  You must prepare and return an array in the following format array(
1723
+	 *    'help_trigger_id' => array(
1724
+	 *        'title' => esc_html__('localized title for popup', 'event_espresso'),
1725
+	 *        'content' => esc_html__('localized content for popup', 'event_espresso')
1726
+	 *    )
1727
+	 * );
1728
+	 * Then the EE_Admin_Parent will take care of making sure that is setup properly on the correct route.
1729
+	 *
1730
+	 * @param array $help_array
1731
+	 * @param bool  $display
1732
+	 * @return string content
1733
+	 * @throws DomainException
1734
+	 * @throws EE_Error
1735
+	 */
1736
+	protected function _set_help_popup_content($help_array = [], $display = false)
1737
+	{
1738
+		$content    = '';
1739
+		$help_array = empty($help_array) ? $this->_get_help_content() : $help_array;
1740
+		// loop through the array and setup content
1741
+		foreach ($help_array as $trigger => $help) {
1742
+			// make sure the array is setup properly
1743
+			if (! isset($help['title'], $help['content'])) {
1744
+				throw new EE_Error(
1745
+					esc_html__(
1746
+						'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',
1747
+						'event_espresso'
1748
+					)
1749
+				);
1750
+			}
1751
+			// we're good so let's setup the template vars and then assign parsed template content to our content.
1752
+			$template_args = [
1753
+				'help_popup_id'      => $trigger,
1754
+				'help_popup_title'   => $help['title'],
1755
+				'help_popup_content' => $help['content'],
1756
+			];
1757
+			$content       .= EEH_Template::display_template(
1758
+				EE_ADMIN_TEMPLATE . 'admin_help_popup.template.php',
1759
+				$template_args,
1760
+				true
1761
+			);
1762
+		}
1763
+		if ($display) {
1764
+			echo wp_kses($content, AllowedTags::getWithFormTags());
1765
+			return '';
1766
+		}
1767
+		return $content;
1768
+	}
1769
+
1770
+
1771
+	/**
1772
+	 * All this does is retrieve the help content array if set by the EE_Admin_Page child
1773
+	 *
1774
+	 * @return array properly formatted array for help popup content
1775
+	 * @throws EE_Error
1776
+	 */
1777
+	private function _get_help_content()
1778
+	{
1779
+		// what is the method we're looking for?
1780
+		$method_name = '_help_popup_content_' . $this->_req_action;
1781
+		// if method doesn't exist let's get out.
1782
+		if (! method_exists($this, $method_name)) {
1783
+			return [];
1784
+		}
1785
+		// k we're good to go let's retrieve the help array
1786
+		$help_array = $this->{$method_name}();
1787
+		// make sure we've got an array!
1788
+		if (! is_array($help_array)) {
1789
+			throw new EE_Error(
1790
+				esc_html__(
1791
+					'Something went wrong with help popup content generation. Expecting an array and well, this ain\'t no array bub.',
1792
+					'event_espresso'
1793
+				)
1794
+			);
1795
+		}
1796
+		return $help_array;
1797
+	}
1798
+
1799
+
1800
+	/**
1801
+	 * EE Admin Pages can use this to set a properly formatted trigger for a help popup.
1802
+	 * By default the trigger html is printed.  Otherwise it can be returned if the $display flag is set "false"
1803
+	 * See comments made on the _set_help_content method for understanding other parts to the help popup tool.
1804
+	 *
1805
+	 * @param string  $trigger_id reference for retrieving the trigger content for the popup
1806
+	 * @param boolean $display    if false then we return the trigger string
1807
+	 * @param array   $dimensions an array of dimensions for the box (array(h,w))
1808
+	 * @return string
1809
+	 * @throws DomainException
1810
+	 * @throws EE_Error
1811
+	 */
1812
+	protected function _set_help_trigger($trigger_id, $display = true, $dimensions = ['400', '640'])
1813
+	{
1814
+		if ($this->request->isAjax()) {
1815
+			return '';
1816
+		}
1817
+		// 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
1818
+		$help_array   = $this->_get_help_content();
1819
+		$help_content = '';
1820
+		if (empty($help_array) || ! isset($help_array[ $trigger_id ])) {
1821
+			$help_array[ $trigger_id ] = [
1822
+				'title'   => esc_html__('Missing Content', 'event_espresso'),
1823
+				'content' => esc_html__(
1824
+					'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.)',
1825
+					'event_espresso'
1826
+				),
1827
+			];
1828
+			$help_content = $this->_set_help_popup_content($help_array);
1829
+		}
1830
+		// let's setup the trigger
1831
+		$content = '<a class="ee-dialog" href="?height='
1832
+				   . esc_attr($dimensions[0])
1833
+				   . '&width='
1834
+				   . esc_attr($dimensions[1])
1835
+				   . '&inlineId='
1836
+				   . esc_attr($trigger_id)
1837
+				   . '" target="_blank"><span class="question ee-help-popup-question"></span></a>';
1838
+		$content .= $help_content;
1839
+		if ($display) {
1840
+			echo wp_kses($content, AllowedTags::getWithFormTags());
1841
+			return '';
1842
+		}
1843
+		return $content;
1844
+	}
1845
+
1846
+
1847
+	/**
1848
+	 * _add_global_screen_options
1849
+	 * Add any extra wp_screen_options within this method using built-in WP functions/methods for doing so.
1850
+	 * This particular method will add_screen_options on ALL EE_Admin Pages
1851
+	 *
1852
+	 * @link   http://chrismarslender.com/wp-tutorials/wordpress-screen-options-tutorial/
1853
+	 *         see also WP_Screen object documents...
1854
+	 * @link   http://codex.wordpress.org/Class_Reference/WP_Screen
1855
+	 * @abstract
1856
+	 * @return void
1857
+	 */
1858
+	private function _add_global_screen_options()
1859
+	{
1860
+	}
1861
+
1862
+
1863
+	/**
1864
+	 * _add_global_feature_pointers
1865
+	 * This method is used for implementing any "feature pointers" (using built-in WP styling js).
1866
+	 * This particular method will implement feature pointers for ALL EE_Admin pages.
1867
+	 * Note: this is just a placeholder for now.  Implementation will come down the road
1868
+	 *
1869
+	 * @see    WP_Internal_Pointers class in wp-admin/includes/template.php for example (its a final class so can't be
1870
+	 *         extended) also see:
1871
+	 * @link   http://eamann.com/tech/wordpress-portland/
1872
+	 * @abstract
1873
+	 * @return void
1874
+	 */
1875
+	private function _add_global_feature_pointers()
1876
+	{
1877
+	}
1878
+
1879
+
1880
+	/**
1881
+	 * load_global_scripts_styles
1882
+	 * The scripts and styles enqueued in here will be loaded on every EE Admin page
1883
+	 *
1884
+	 * @return void
1885
+	 */
1886
+	public function load_global_scripts_styles()
1887
+	{
1888
+		// add debugging styles
1889
+		if (WP_DEBUG) {
1890
+			add_action('admin_head', [$this, 'add_xdebug_style']);
1891
+		}
1892
+		// taking care of metaboxes
1893
+		if (
1894
+			empty($this->_cpt_route)
1895
+			&& (isset($this->_route_config['metaboxes']) || isset($this->_route_config['has_metaboxes']))
1896
+		) {
1897
+			wp_enqueue_script('dashboard');
1898
+		}
1899
+
1900
+		wp_enqueue_script(JqueryAssetManager::JS_HANDLE_JQUERY_UI_TOUCH_PUNCH);
1901
+		wp_enqueue_script(EspressoLegacyAdminAssetManager::JS_HANDLE_EE_ADMIN);
1902
+		// LOCALIZED DATA
1903
+		// localize script for ajax lazy loading
1904
+		wp_localize_script(
1905
+			EspressoLegacyAdminAssetManager::JS_HANDLE_EE_ADMIN,
1906
+			'eeLazyLoadingContainers',
1907
+			apply_filters(
1908
+				'FHEE__EE_Admin_Page_Core__load_global_scripts_styles__loader_containers',
1909
+				['espresso_news_post_box_content']
1910
+			)
1911
+		);
1912
+		StatusChangeNotice::loadAssets();
1913
+
1914
+		add_filter(
1915
+			'admin_body_class',
1916
+			function ($classes) {
1917
+				if (strpos($classes, 'espresso-admin') === false) {
1918
+					$classes .= ' espresso-admin';
1919
+				}
1920
+				return $classes;
1921
+			}
1922
+		);
1923
+	}
1924
+
1925
+
1926
+	/**
1927
+	 *        admin_footer_scripts_eei18n_js_strings
1928
+	 *
1929
+	 * @return        void
1930
+	 */
1931
+	public function admin_footer_scripts_eei18n_js_strings()
1932
+	{
1933
+		EE_Registry::$i18n_js_strings['ajax_url']       = WP_AJAX_URL;
1934
+		EE_Registry::$i18n_js_strings['confirm_delete'] = wp_strip_all_tags(
1935
+			__(
1936
+				'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!!!',
1937
+				'event_espresso'
1938
+			)
1939
+		);
1940
+		EE_Registry::$i18n_js_strings['January']        = wp_strip_all_tags(__('January', 'event_espresso'));
1941
+		EE_Registry::$i18n_js_strings['February']       = wp_strip_all_tags(__('February', 'event_espresso'));
1942
+		EE_Registry::$i18n_js_strings['March']          = wp_strip_all_tags(__('March', 'event_espresso'));
1943
+		EE_Registry::$i18n_js_strings['April']          = wp_strip_all_tags(__('April', 'event_espresso'));
1944
+		EE_Registry::$i18n_js_strings['May']            = wp_strip_all_tags(__('May', 'event_espresso'));
1945
+		EE_Registry::$i18n_js_strings['June']           = wp_strip_all_tags(__('June', 'event_espresso'));
1946
+		EE_Registry::$i18n_js_strings['July']           = wp_strip_all_tags(__('July', 'event_espresso'));
1947
+		EE_Registry::$i18n_js_strings['August']         = wp_strip_all_tags(__('August', 'event_espresso'));
1948
+		EE_Registry::$i18n_js_strings['September']      = wp_strip_all_tags(__('September', 'event_espresso'));
1949
+		EE_Registry::$i18n_js_strings['October']        = wp_strip_all_tags(__('October', 'event_espresso'));
1950
+		EE_Registry::$i18n_js_strings['November']       = wp_strip_all_tags(__('November', 'event_espresso'));
1951
+		EE_Registry::$i18n_js_strings['December']       = wp_strip_all_tags(__('December', 'event_espresso'));
1952
+		EE_Registry::$i18n_js_strings['Jan']            = wp_strip_all_tags(__('Jan', 'event_espresso'));
1953
+		EE_Registry::$i18n_js_strings['Feb']            = wp_strip_all_tags(__('Feb', 'event_espresso'));
1954
+		EE_Registry::$i18n_js_strings['Mar']            = wp_strip_all_tags(__('Mar', 'event_espresso'));
1955
+		EE_Registry::$i18n_js_strings['Apr']            = wp_strip_all_tags(__('Apr', 'event_espresso'));
1956
+		EE_Registry::$i18n_js_strings['May']            = wp_strip_all_tags(__('May', 'event_espresso'));
1957
+		EE_Registry::$i18n_js_strings['Jun']            = wp_strip_all_tags(__('Jun', 'event_espresso'));
1958
+		EE_Registry::$i18n_js_strings['Jul']            = wp_strip_all_tags(__('Jul', 'event_espresso'));
1959
+		EE_Registry::$i18n_js_strings['Aug']            = wp_strip_all_tags(__('Aug', 'event_espresso'));
1960
+		EE_Registry::$i18n_js_strings['Sep']            = wp_strip_all_tags(__('Sep', 'event_espresso'));
1961
+		EE_Registry::$i18n_js_strings['Oct']            = wp_strip_all_tags(__('Oct', 'event_espresso'));
1962
+		EE_Registry::$i18n_js_strings['Nov']            = wp_strip_all_tags(__('Nov', 'event_espresso'));
1963
+		EE_Registry::$i18n_js_strings['Dec']            = wp_strip_all_tags(__('Dec', 'event_espresso'));
1964
+		EE_Registry::$i18n_js_strings['Sunday']         = wp_strip_all_tags(__('Sunday', 'event_espresso'));
1965
+		EE_Registry::$i18n_js_strings['Monday']         = wp_strip_all_tags(__('Monday', 'event_espresso'));
1966
+		EE_Registry::$i18n_js_strings['Tuesday']        = wp_strip_all_tags(__('Tuesday', 'event_espresso'));
1967
+		EE_Registry::$i18n_js_strings['Wednesday']      = wp_strip_all_tags(__('Wednesday', 'event_espresso'));
1968
+		EE_Registry::$i18n_js_strings['Thursday']       = wp_strip_all_tags(__('Thursday', 'event_espresso'));
1969
+		EE_Registry::$i18n_js_strings['Friday']         = wp_strip_all_tags(__('Friday', 'event_espresso'));
1970
+		EE_Registry::$i18n_js_strings['Saturday']       = wp_strip_all_tags(__('Saturday', 'event_espresso'));
1971
+		EE_Registry::$i18n_js_strings['Sun']            = wp_strip_all_tags(__('Sun', 'event_espresso'));
1972
+		EE_Registry::$i18n_js_strings['Mon']            = wp_strip_all_tags(__('Mon', 'event_espresso'));
1973
+		EE_Registry::$i18n_js_strings['Tue']            = wp_strip_all_tags(__('Tue', 'event_espresso'));
1974
+		EE_Registry::$i18n_js_strings['Wed']            = wp_strip_all_tags(__('Wed', 'event_espresso'));
1975
+		EE_Registry::$i18n_js_strings['Thu']            = wp_strip_all_tags(__('Thu', 'event_espresso'));
1976
+		EE_Registry::$i18n_js_strings['Fri']            = wp_strip_all_tags(__('Fri', 'event_espresso'));
1977
+		EE_Registry::$i18n_js_strings['Sat']            = wp_strip_all_tags(__('Sat', 'event_espresso'));
1978
+	}
1979
+
1980
+
1981
+	/**
1982
+	 *        load enhanced xdebug styles for ppl with failing eyesight
1983
+	 *
1984
+	 * @return        void
1985
+	 */
1986
+	public function add_xdebug_style()
1987
+	{
1988
+		echo '<style>.xdebug-error { font-size:1.5em; }</style>';
1989
+	}
1990
+
1991
+
1992
+	/************************/
1993
+	/** LIST TABLE METHODS **/
1994
+	/************************/
1995
+	/**
1996
+	 * this sets up the list table if the current view requires it.
1997
+	 *
1998
+	 * @return void
1999
+	 * @throws EE_Error
2000
+	 * @throws InvalidArgumentException
2001
+	 * @throws InvalidDataTypeException
2002
+	 * @throws InvalidInterfaceException
2003
+	 */
2004
+	protected function _set_list_table()
2005
+	{
2006
+		// first is this a list_table view?
2007
+		if (! isset($this->_route_config['list_table'])) {
2008
+			return;
2009
+		} //not a list_table view so get out.
2010
+		// list table functions are per view specific (because some admin pages might have more than one list table!)
2011
+		$list_table_view = '_set_list_table_views_' . $this->_req_action;
2012
+		if (! method_exists($this, $list_table_view) || $this->{$list_table_view}() === false) {
2013
+			// user error msg
2014
+			$error_msg = esc_html__(
2015
+				'An error occurred. The requested list table views could not be found.',
2016
+				'event_espresso'
2017
+			);
2018
+			// developer error msg
2019
+			$error_msg .= '||'
2020
+						  . sprintf(
2021
+							  esc_html__(
2022
+								  '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.',
2023
+								  'event_espresso'
2024
+							  ),
2025
+							  $this->_req_action,
2026
+							  $list_table_view
2027
+						  );
2028
+			throw new EE_Error($error_msg);
2029
+		}
2030
+		// let's provide the ability to filter the views per PAGE AND ROUTE, per PAGE, and globally
2031
+		$this->_views = apply_filters(
2032
+			'FHEE_list_table_views_' . $this->page_slug . '_' . $this->_req_action,
2033
+			$this->_views
2034
+		);
2035
+		$this->_views = apply_filters('FHEE_list_table_views_' . $this->page_slug, $this->_views);
2036
+		$this->_views = apply_filters('FHEE_list_table_views', $this->_views);
2037
+		$this->_set_list_table_view();
2038
+		$this->_set_list_table_object();
2039
+	}
2040
+
2041
+
2042
+	/**
2043
+	 * set current view for List Table
2044
+	 *
2045
+	 * @return void
2046
+	 */
2047
+	protected function _set_list_table_view()
2048
+	{
2049
+		$this->_view = isset($this->_views['in_use']) ? 'in_use' : 'all';
2050
+		$status = $this->request->getRequestParam('status', null, 'key');
2051
+		$this->_view = $status && array_key_exists($status, $this->_views)
2052
+			? $status
2053
+			: $this->_view;
2054
+	}
2055
+
2056
+
2057
+	/**
2058
+	 * _set_list_table_object
2059
+	 * WP_List_Table objects need to be loaded fairly early so automatic stuff WP does is taken care of.
2060
+	 *
2061
+	 * @throws InvalidInterfaceException
2062
+	 * @throws InvalidArgumentException
2063
+	 * @throws InvalidDataTypeException
2064
+	 * @throws EE_Error
2065
+	 * @throws InvalidInterfaceException
2066
+	 */
2067
+	protected function _set_list_table_object()
2068
+	{
2069
+		if (isset($this->_route_config['list_table'])) {
2070
+			if (! class_exists($this->_route_config['list_table'])) {
2071
+				throw new EE_Error(
2072
+					sprintf(
2073
+						esc_html__(
2074
+							'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.',
2075
+							'event_espresso'
2076
+						),
2077
+						$this->_route_config['list_table'],
2078
+						$this->class_name
2079
+					)
2080
+				);
2081
+			}
2082
+			$this->_list_table_object = $this->loader->getShared(
2083
+				$this->_route_config['list_table'],
2084
+				[$this]
2085
+			);
2086
+		}
2087
+	}
2088
+
2089
+
2090
+	/**
2091
+	 * get_list_table_view_RLs - get it? View RL ?? VU-RL???  URL ??
2092
+	 *
2093
+	 * @param array $extra_query_args                     Optional. An array of extra query args to add to the generated
2094
+	 *                                                    urls.  The array should be indexed by the view it is being
2095
+	 *                                                    added to.
2096
+	 * @return array
2097
+	 */
2098
+	public function get_list_table_view_RLs($extra_query_args = [])
2099
+	{
2100
+		do_action('AHEE_log', __FILE__, __FUNCTION__, '');
2101
+		if (empty($this->_views)) {
2102
+			$this->_views = [];
2103
+		}
2104
+		// cycle thru views
2105
+		foreach ($this->_views as $key => $view) {
2106
+			$query_args = [];
2107
+			// check for current view
2108
+			$this->_views[ $key ]['class']               = $this->_view === $view['slug'] ? 'current' : '';
2109
+			$query_args['action']                        = $this->_req_action;
2110
+			$query_args[ $this->_req_action . '_nonce' ] = wp_create_nonce($query_args['action'] . '_nonce');
2111
+			$query_args['status']                        = $view['slug'];
2112
+			// merge any other arguments sent in.
2113
+			if (isset($extra_query_args[ $view['slug'] ])) {
2114
+				foreach ($extra_query_args[ $view['slug'] ] as $extra_query_arg) {
2115
+					$query_args[] = $extra_query_arg;
2116
+				}
2117
+			}
2118
+			$this->_views[ $key ]['url'] = EE_Admin_Page::add_query_args_and_nonce($query_args, $this->_admin_base_url);
2119
+		}
2120
+		return $this->_views;
2121
+	}
2122
+
2123
+
2124
+	/**
2125
+	 * _entries_per_page_dropdown
2126
+	 * generates a dropdown box for selecting the number of visible rows in an admin page list table
2127
+	 *
2128
+	 * @param int $max_entries total number of rows in the table
2129
+	 * @return string
2130
+	 * @todo   : Note: ideally this should be added to the screen options dropdown as that would be consistent with how
2131
+	 *         WP does it.
2132
+	 */
2133
+	protected function _entries_per_page_dropdown($max_entries = 0)
2134
+	{
2135
+		do_action('AHEE_log', __FILE__, __FUNCTION__, '');
2136
+		$values   = [10, 25, 50, 100];
2137
+		$per_page = $this->request->getRequestParam('per_page', 10, 'int');
2138
+		if ($max_entries) {
2139
+			$values[] = $max_entries;
2140
+			sort($values);
2141
+		}
2142
+		$entries_per_page_dropdown = '
2143 2143
 			<div id="entries-per-page-dv" class="alignleft actions">
2144 2144
 				<label class="hide-if-no-js">
2145 2145
 					Show
2146 2146
 					<select id="entries-per-page-slct" name="entries-per-page-slct">';
2147
-        foreach ($values as $value) {
2148
-            if ($value < $max_entries) {
2149
-                $selected                  = $value === $per_page ? ' selected="' . $per_page . '"' : '';
2150
-                $entries_per_page_dropdown .= '
2147
+		foreach ($values as $value) {
2148
+			if ($value < $max_entries) {
2149
+				$selected                  = $value === $per_page ? ' selected="' . $per_page . '"' : '';
2150
+				$entries_per_page_dropdown .= '
2151 2151
 						<option value="' . $value . '"' . $selected . '>' . $value . '&nbsp;&nbsp;</option>';
2152
-            }
2153
-        }
2154
-        $selected                  = $max_entries === $per_page ? ' selected="' . $per_page . '"' : '';
2155
-        $entries_per_page_dropdown .= '
2152
+			}
2153
+		}
2154
+		$selected                  = $max_entries === $per_page ? ' selected="' . $per_page . '"' : '';
2155
+		$entries_per_page_dropdown .= '
2156 2156
 						<option value="' . $max_entries . '"' . $selected . '>All&nbsp;&nbsp;</option>';
2157
-        $entries_per_page_dropdown .= '
2157
+		$entries_per_page_dropdown .= '
2158 2158
 					</select>
2159 2159
 					entries
2160 2160
 				</label>
2161 2161
 				<input id="entries-per-page-btn" class="button button--secondary" type="submit" value="Go" >
2162 2162
 			</div>
2163 2163
 		';
2164
-        return $entries_per_page_dropdown;
2165
-    }
2166
-
2167
-
2168
-    /**
2169
-     *        _set_search_attributes
2170
-     *
2171
-     * @return        void
2172
-     */
2173
-    public function _set_search_attributes()
2174
-    {
2175
-        $this->_template_args['search']['btn_label'] = sprintf(
2176
-            esc_html__('Search %s', 'event_espresso'),
2177
-            empty($this->_search_btn_label) ? $this->page_label
2178
-                : $this->_search_btn_label
2179
-        );
2180
-        $this->_template_args['search']['callback']  = 'search_' . $this->page_slug;
2181
-    }
2182
-
2183
-
2184
-
2185
-    /*** END LIST TABLE METHODS **/
2186
-
2187
-
2188
-    /**
2189
-     * _add_registered_metaboxes
2190
-     *  this loads any registered metaboxes via the 'metaboxes' index in the _page_config property array.
2191
-     *
2192
-     * @link   http://codex.wordpress.org/Function_Reference/add_meta_box
2193
-     * @return void
2194
-     * @throws EE_Error
2195
-     */
2196
-    private function _add_registered_meta_boxes()
2197
-    {
2198
-        do_action('AHEE_log', __FILE__, __FUNCTION__, '');
2199
-        // we only add meta boxes if the page_route calls for it
2200
-        if (
2201
-            is_array($this->_route_config) && isset($this->_route_config['metaboxes'])
2202
-            && is_array(
2203
-                $this->_route_config['metaboxes']
2204
-            )
2205
-        ) {
2206
-            // this simply loops through the callbacks provided
2207
-            // and checks if there is a corresponding callback registered by the child
2208
-            // if there is then we go ahead and process the metabox loader.
2209
-            foreach ($this->_route_config['metaboxes'] as $metabox_callback) {
2210
-                // first check for Closures
2211
-                if ($metabox_callback instanceof Closure) {
2212
-                    $result = $metabox_callback();
2213
-                } elseif (is_array($metabox_callback) && isset($metabox_callback[0], $metabox_callback[1])) {
2214
-                    $result = call_user_func([$metabox_callback[0], $metabox_callback[1]]);
2215
-                } else {
2216
-                    $result = $this->{$metabox_callback}();
2217
-                }
2218
-                if ($result === false) {
2219
-                    // user error msg
2220
-                    $error_msg = esc_html__(
2221
-                        'An error occurred. The  requested metabox could not be found.',
2222
-                        'event_espresso'
2223
-                    );
2224
-                    // developer error msg
2225
-                    $error_msg .= '||'
2226
-                                  . sprintf(
2227
-                                      esc_html__(
2228
-                                          '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.',
2229
-                                          'event_espresso'
2230
-                                      ),
2231
-                                      $metabox_callback
2232
-                                  );
2233
-                    throw new EE_Error($error_msg);
2234
-                }
2235
-            }
2236
-        }
2237
-    }
2238
-
2239
-
2240
-    /**
2241
-     * _add_screen_columns
2242
-     * This will check the _page_config array and if there is "columns" key index indicated, we'll set the template as
2243
-     * the dynamic column template and we'll setup the column options for the page.
2244
-     *
2245
-     * @return void
2246
-     */
2247
-    private function _add_screen_columns()
2248
-    {
2249
-        if (
2250
-            is_array($this->_route_config)
2251
-            && isset($this->_route_config['columns'])
2252
-            && is_array($this->_route_config['columns'])
2253
-            && count($this->_route_config['columns']) === 2
2254
-        ) {
2255
-            add_screen_option(
2256
-                'layout_columns',
2257
-                [
2258
-                    'max'     => (int) $this->_route_config['columns'][0],
2259
-                    'default' => (int) $this->_route_config['columns'][1],
2260
-                ]
2261
-            );
2262
-            $this->_template_args['num_columns']                 = $this->_route_config['columns'][0];
2263
-            $screen_id                                           = $this->_current_screen->id;
2264
-            $screen_columns                                      = (int) get_user_option("screen_layout_{$screen_id}");
2265
-            $total_columns                                       = ! empty($screen_columns)
2266
-                ? $screen_columns
2267
-                : $this->_route_config['columns'][1];
2268
-            $this->_template_args['current_screen_widget_class'] = 'columns-' . $total_columns;
2269
-            $this->_template_args['current_page']                = $this->_wp_page_slug;
2270
-            $this->_template_args['screen']                      = $this->_current_screen;
2271
-            $this->_column_template_path                         = EE_ADMIN_TEMPLATE
2272
-                                                                   . 'admin_details_metabox_column_wrapper.template.php';
2273
-            // finally if we don't have has_metaboxes set in the route config
2274
-            // let's make sure it IS set other wise the necessary hidden fields for this won't be loaded.
2275
-            $this->_route_config['has_metaboxes'] = true;
2276
-        }
2277
-    }
2278
-
2279
-
2280
-
2281
-    /** GLOBALLY AVAILABLE METABOXES **/
2282
-
2283
-
2284
-    /**
2285
-     * In this section we put any globally available EE metaboxes for all EE Admin pages.  They are called by simply
2286
-     * referencing the callback in the _page_config array property.  This way you can be very specific about what pages
2287
-     * these get loaded on.
2288
-     */
2289
-    private function _espresso_news_post_box()
2290
-    {
2291
-        $news_box_title = apply_filters(
2292
-            'FHEE__EE_Admin_Page___espresso_news_post_box__news_box_title',
2293
-            esc_html__('New @ Event Espresso', 'event_espresso')
2294
-        );
2295
-        $this->addMetaBox(
2296
-            'espresso_news_post_box',
2297
-            $news_box_title,
2298
-            [
2299
-                $this,
2300
-                'espresso_news_post_box',
2301
-            ],
2302
-            $this->_wp_page_slug,
2303
-            'side',
2304
-            'low'
2305
-        );
2306
-    }
2307
-
2308
-
2309
-    /**
2310
-     * Code for setting up espresso ratings request metabox.
2311
-     */
2312
-    protected function _espresso_ratings_request()
2313
-    {
2314
-        if (! apply_filters('FHEE_show_ratings_request_meta_box', true)) {
2315
-            return;
2316
-        }
2317
-        $ratings_box_title = apply_filters(
2318
-            'FHEE__EE_Admin_Page___espresso_news_post_box__news_box_title',
2319
-            esc_html__('Keep Event Espresso Decaf Free', 'event_espresso')
2320
-        );
2321
-        $this->addMetaBox(
2322
-            'espresso_ratings_request',
2323
-            $ratings_box_title,
2324
-            [
2325
-                $this,
2326
-                'espresso_ratings_request',
2327
-            ],
2328
-            $this->_wp_page_slug,
2329
-            'side'
2330
-        );
2331
-    }
2332
-
2333
-
2334
-    /**
2335
-     * Code for setting up espresso ratings request metabox content.
2336
-     *
2337
-     * @throws DomainException
2338
-     */
2339
-    public function espresso_ratings_request()
2340
-    {
2341
-        EEH_Template::display_template(EE_ADMIN_TEMPLATE . 'espresso_ratings_request_content.template.php');
2342
-    }
2343
-
2344
-
2345
-    public static function cached_rss_display($rss_id, $url)
2346
-    {
2347
-        $loading   = '<p class="widget-loading hide-if-no-js">'
2348
-                     . esc_html__('Loading&#8230;', 'event_espresso')
2349
-                     . '</p><p class="hide-if-js">'
2350
-                     . esc_html__('This widget requires JavaScript.', 'event_espresso')
2351
-                     . '</p>';
2352
-        $pre       = '<div class="espresso-rss-display">' . "\n\t";
2353
-        $pre       .= '<span id="' . esc_attr($rss_id) . '_url" class="hidden">' . esc_url_raw($url) . '</span>';
2354
-        $post      = '</div>' . "\n";
2355
-        $cache_key = 'ee_rss_' . md5($rss_id);
2356
-        $output    = get_transient($cache_key);
2357
-        if ($output !== false) {
2358
-            echo wp_kses($pre . $output . $post, AllowedTags::getWithFormTags());
2359
-            return true;
2360
-        }
2361
-        if (! (defined('DOING_AJAX') && DOING_AJAX)) {
2362
-            echo wp_kses($pre . $loading . $post, AllowedTags::getWithFormTags());
2363
-            return false;
2364
-        }
2365
-        ob_start();
2366
-        wp_widget_rss_output($url, ['show_date' => 0, 'items' => 5]);
2367
-        set_transient($cache_key, ob_get_flush(), 12 * HOUR_IN_SECONDS);
2368
-        return true;
2369
-    }
2370
-
2371
-
2372
-    public function espresso_news_post_box()
2373
-    {
2374
-        ?>
2164
+		return $entries_per_page_dropdown;
2165
+	}
2166
+
2167
+
2168
+	/**
2169
+	 *        _set_search_attributes
2170
+	 *
2171
+	 * @return        void
2172
+	 */
2173
+	public function _set_search_attributes()
2174
+	{
2175
+		$this->_template_args['search']['btn_label'] = sprintf(
2176
+			esc_html__('Search %s', 'event_espresso'),
2177
+			empty($this->_search_btn_label) ? $this->page_label
2178
+				: $this->_search_btn_label
2179
+		);
2180
+		$this->_template_args['search']['callback']  = 'search_' . $this->page_slug;
2181
+	}
2182
+
2183
+
2184
+
2185
+	/*** END LIST TABLE METHODS **/
2186
+
2187
+
2188
+	/**
2189
+	 * _add_registered_metaboxes
2190
+	 *  this loads any registered metaboxes via the 'metaboxes' index in the _page_config property array.
2191
+	 *
2192
+	 * @link   http://codex.wordpress.org/Function_Reference/add_meta_box
2193
+	 * @return void
2194
+	 * @throws EE_Error
2195
+	 */
2196
+	private function _add_registered_meta_boxes()
2197
+	{
2198
+		do_action('AHEE_log', __FILE__, __FUNCTION__, '');
2199
+		// we only add meta boxes if the page_route calls for it
2200
+		if (
2201
+			is_array($this->_route_config) && isset($this->_route_config['metaboxes'])
2202
+			&& is_array(
2203
+				$this->_route_config['metaboxes']
2204
+			)
2205
+		) {
2206
+			// this simply loops through the callbacks provided
2207
+			// and checks if there is a corresponding callback registered by the child
2208
+			// if there is then we go ahead and process the metabox loader.
2209
+			foreach ($this->_route_config['metaboxes'] as $metabox_callback) {
2210
+				// first check for Closures
2211
+				if ($metabox_callback instanceof Closure) {
2212
+					$result = $metabox_callback();
2213
+				} elseif (is_array($metabox_callback) && isset($metabox_callback[0], $metabox_callback[1])) {
2214
+					$result = call_user_func([$metabox_callback[0], $metabox_callback[1]]);
2215
+				} else {
2216
+					$result = $this->{$metabox_callback}();
2217
+				}
2218
+				if ($result === false) {
2219
+					// user error msg
2220
+					$error_msg = esc_html__(
2221
+						'An error occurred. The  requested metabox could not be found.',
2222
+						'event_espresso'
2223
+					);
2224
+					// developer error msg
2225
+					$error_msg .= '||'
2226
+								  . sprintf(
2227
+									  esc_html__(
2228
+										  '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.',
2229
+										  'event_espresso'
2230
+									  ),
2231
+									  $metabox_callback
2232
+								  );
2233
+					throw new EE_Error($error_msg);
2234
+				}
2235
+			}
2236
+		}
2237
+	}
2238
+
2239
+
2240
+	/**
2241
+	 * _add_screen_columns
2242
+	 * This will check the _page_config array and if there is "columns" key index indicated, we'll set the template as
2243
+	 * the dynamic column template and we'll setup the column options for the page.
2244
+	 *
2245
+	 * @return void
2246
+	 */
2247
+	private function _add_screen_columns()
2248
+	{
2249
+		if (
2250
+			is_array($this->_route_config)
2251
+			&& isset($this->_route_config['columns'])
2252
+			&& is_array($this->_route_config['columns'])
2253
+			&& count($this->_route_config['columns']) === 2
2254
+		) {
2255
+			add_screen_option(
2256
+				'layout_columns',
2257
+				[
2258
+					'max'     => (int) $this->_route_config['columns'][0],
2259
+					'default' => (int) $this->_route_config['columns'][1],
2260
+				]
2261
+			);
2262
+			$this->_template_args['num_columns']                 = $this->_route_config['columns'][0];
2263
+			$screen_id                                           = $this->_current_screen->id;
2264
+			$screen_columns                                      = (int) get_user_option("screen_layout_{$screen_id}");
2265
+			$total_columns                                       = ! empty($screen_columns)
2266
+				? $screen_columns
2267
+				: $this->_route_config['columns'][1];
2268
+			$this->_template_args['current_screen_widget_class'] = 'columns-' . $total_columns;
2269
+			$this->_template_args['current_page']                = $this->_wp_page_slug;
2270
+			$this->_template_args['screen']                      = $this->_current_screen;
2271
+			$this->_column_template_path                         = EE_ADMIN_TEMPLATE
2272
+																   . 'admin_details_metabox_column_wrapper.template.php';
2273
+			// finally if we don't have has_metaboxes set in the route config
2274
+			// let's make sure it IS set other wise the necessary hidden fields for this won't be loaded.
2275
+			$this->_route_config['has_metaboxes'] = true;
2276
+		}
2277
+	}
2278
+
2279
+
2280
+
2281
+	/** GLOBALLY AVAILABLE METABOXES **/
2282
+
2283
+
2284
+	/**
2285
+	 * In this section we put any globally available EE metaboxes for all EE Admin pages.  They are called by simply
2286
+	 * referencing the callback in the _page_config array property.  This way you can be very specific about what pages
2287
+	 * these get loaded on.
2288
+	 */
2289
+	private function _espresso_news_post_box()
2290
+	{
2291
+		$news_box_title = apply_filters(
2292
+			'FHEE__EE_Admin_Page___espresso_news_post_box__news_box_title',
2293
+			esc_html__('New @ Event Espresso', 'event_espresso')
2294
+		);
2295
+		$this->addMetaBox(
2296
+			'espresso_news_post_box',
2297
+			$news_box_title,
2298
+			[
2299
+				$this,
2300
+				'espresso_news_post_box',
2301
+			],
2302
+			$this->_wp_page_slug,
2303
+			'side',
2304
+			'low'
2305
+		);
2306
+	}
2307
+
2308
+
2309
+	/**
2310
+	 * Code for setting up espresso ratings request metabox.
2311
+	 */
2312
+	protected function _espresso_ratings_request()
2313
+	{
2314
+		if (! apply_filters('FHEE_show_ratings_request_meta_box', true)) {
2315
+			return;
2316
+		}
2317
+		$ratings_box_title = apply_filters(
2318
+			'FHEE__EE_Admin_Page___espresso_news_post_box__news_box_title',
2319
+			esc_html__('Keep Event Espresso Decaf Free', 'event_espresso')
2320
+		);
2321
+		$this->addMetaBox(
2322
+			'espresso_ratings_request',
2323
+			$ratings_box_title,
2324
+			[
2325
+				$this,
2326
+				'espresso_ratings_request',
2327
+			],
2328
+			$this->_wp_page_slug,
2329
+			'side'
2330
+		);
2331
+	}
2332
+
2333
+
2334
+	/**
2335
+	 * Code for setting up espresso ratings request metabox content.
2336
+	 *
2337
+	 * @throws DomainException
2338
+	 */
2339
+	public function espresso_ratings_request()
2340
+	{
2341
+		EEH_Template::display_template(EE_ADMIN_TEMPLATE . 'espresso_ratings_request_content.template.php');
2342
+	}
2343
+
2344
+
2345
+	public static function cached_rss_display($rss_id, $url)
2346
+	{
2347
+		$loading   = '<p class="widget-loading hide-if-no-js">'
2348
+					 . esc_html__('Loading&#8230;', 'event_espresso')
2349
+					 . '</p><p class="hide-if-js">'
2350
+					 . esc_html__('This widget requires JavaScript.', 'event_espresso')
2351
+					 . '</p>';
2352
+		$pre       = '<div class="espresso-rss-display">' . "\n\t";
2353
+		$pre       .= '<span id="' . esc_attr($rss_id) . '_url" class="hidden">' . esc_url_raw($url) . '</span>';
2354
+		$post      = '</div>' . "\n";
2355
+		$cache_key = 'ee_rss_' . md5($rss_id);
2356
+		$output    = get_transient($cache_key);
2357
+		if ($output !== false) {
2358
+			echo wp_kses($pre . $output . $post, AllowedTags::getWithFormTags());
2359
+			return true;
2360
+		}
2361
+		if (! (defined('DOING_AJAX') && DOING_AJAX)) {
2362
+			echo wp_kses($pre . $loading . $post, AllowedTags::getWithFormTags());
2363
+			return false;
2364
+		}
2365
+		ob_start();
2366
+		wp_widget_rss_output($url, ['show_date' => 0, 'items' => 5]);
2367
+		set_transient($cache_key, ob_get_flush(), 12 * HOUR_IN_SECONDS);
2368
+		return true;
2369
+	}
2370
+
2371
+
2372
+	public function espresso_news_post_box()
2373
+	{
2374
+		?>
2375 2375
         <div class="padding">
2376 2376
             <div id="espresso_news_post_box_content" class="infolinks">
2377 2377
                 <?php
2378
-                // Get RSS Feed(s)
2379
-                EE_Admin_Page::cached_rss_display(
2380
-                    'espresso_news_post_box_content',
2381
-                    esc_url_raw(
2382
-                        apply_filters(
2383
-                            'FHEE__EE_Admin_Page__espresso_news_post_box__feed_url',
2384
-                            'https://eventespresso.com/feed/'
2385
-                        )
2386
-                    )
2387
-                );
2388
-                ?>
2378
+				// Get RSS Feed(s)
2379
+				EE_Admin_Page::cached_rss_display(
2380
+					'espresso_news_post_box_content',
2381
+					esc_url_raw(
2382
+						apply_filters(
2383
+							'FHEE__EE_Admin_Page__espresso_news_post_box__feed_url',
2384
+							'https://eventespresso.com/feed/'
2385
+						)
2386
+					)
2387
+				);
2388
+				?>
2389 2389
             </div>
2390 2390
             <?php do_action('AHEE__EE_Admin_Page__espresso_news_post_box__after_content'); ?>
2391 2391
         </div>
2392 2392
         <?php
2393
-    }
2394
-
2395
-
2396
-    private function _espresso_links_post_box()
2397
-    {
2398
-        // Hiding until we actually have content to put in here...
2399
-        // $this->addMetaBox('espresso_links_post_box', esc_html__('Helpful Plugin Links', 'event_espresso'), array( $this, 'espresso_links_post_box'), $this->_wp_page_slug, 'side');
2400
-    }
2401
-
2402
-
2403
-    public function espresso_links_post_box()
2404
-    {
2405
-        // Hiding until we actually have content to put in here...
2406
-        // EEH_Template::display_template(
2407
-        //     EE_ADMIN_TEMPLATE . 'admin_general_metabox_contents_espresso_links.template.php'
2408
-        // );
2409
-    }
2410
-
2411
-
2412
-    protected function _espresso_sponsors_post_box()
2413
-    {
2414
-        if (apply_filters('FHEE_show_sponsors_meta_box', true)) {
2415
-            $this->addMetaBox(
2416
-                'espresso_sponsors_post_box',
2417
-                esc_html__('Event Espresso Highlights', 'event_espresso'),
2418
-                [$this, 'espresso_sponsors_post_box'],
2419
-                $this->_wp_page_slug,
2420
-                'side'
2421
-            );
2422
-        }
2423
-    }
2424
-
2425
-
2426
-    public function espresso_sponsors_post_box()
2427
-    {
2428
-        EEH_Template::display_template(
2429
-            EE_ADMIN_TEMPLATE . 'admin_general_metabox_contents_espresso_sponsors.template.php'
2430
-        );
2431
-    }
2432
-
2433
-
2434
-    private function _publish_post_box()
2435
-    {
2436
-        $meta_box_ref = 'espresso_' . $this->page_slug . '_editor_overview';
2437
-        // if there is a array('label' => array('publishbox' => 'some title') ) present in the _page_config array
2438
-        // then we'll use that for the metabox label.
2439
-        // Otherwise we'll just use publish (publishbox itself could be an array of labels indexed by routes)
2440
-        if (! empty($this->_labels['publishbox'])) {
2441
-            $box_label = is_array($this->_labels['publishbox']) ? $this->_labels['publishbox'][ $this->_req_action ]
2442
-                : $this->_labels['publishbox'];
2443
-        } else {
2444
-            $box_label = esc_html__('Publish', 'event_espresso');
2445
-        }
2446
-        $box_label = apply_filters(
2447
-            'FHEE__EE_Admin_Page___publish_post_box__box_label',
2448
-            $box_label,
2449
-            $this->_req_action,
2450
-            $this
2451
-        );
2452
-        $this->addMetaBox(
2453
-            $meta_box_ref,
2454
-            $box_label,
2455
-            [$this, 'editor_overview'],
2456
-            $this->_current_screen->id,
2457
-            'side',
2458
-            'high'
2459
-        );
2460
-    }
2461
-
2462
-
2463
-    public function editor_overview()
2464
-    {
2465
-        // if we have extra content set let's add it in if not make sure its empty
2466
-        $this->_template_args['publish_box_extra_content'] = isset($this->_template_args['publish_box_extra_content'])
2467
-            ? $this->_template_args['publish_box_extra_content']
2468
-            : '';
2469
-        echo EEH_Template::display_template(
2470
-            EE_ADMIN_TEMPLATE . 'admin_details_publish_metabox.template.php',
2471
-            $this->_template_args,
2472
-            true
2473
-        );
2474
-    }
2475
-
2476
-
2477
-    /** end of globally available metaboxes section **/
2478
-
2479
-
2480
-    /**
2481
-     * Public wrapper for the protected method.  Allows plugins/addons to externally call the
2482
-     * protected method.
2483
-     *
2484
-     * @param string $name
2485
-     * @param int    $id
2486
-     * @param bool   $delete
2487
-     * @param string $save_close_redirect_URL
2488
-     * @param bool   $both_btns
2489
-     * @throws EE_Error
2490
-     * @throws InvalidArgumentException
2491
-     * @throws InvalidDataTypeException
2492
-     * @throws InvalidInterfaceException
2493
-     * @see   $this->_set_publish_post_box_vars for param details
2494
-     * @since 4.6.0
2495
-     */
2496
-    public function set_publish_post_box_vars(
2497
-        $name = '',
2498
-        $id = 0,
2499
-        $delete = false,
2500
-        $save_close_redirect_URL = '',
2501
-        $both_btns = true
2502
-    ) {
2503
-        $this->_set_publish_post_box_vars(
2504
-            $name,
2505
-            $id,
2506
-            $delete,
2507
-            $save_close_redirect_URL,
2508
-            $both_btns
2509
-        );
2510
-    }
2511
-
2512
-
2513
-    /**
2514
-     * Sets the _template_args arguments used by the _publish_post_box shortcut
2515
-     * Note: currently there is no validation for this.  However if you want the delete button, the
2516
-     * save, and save and close buttons to work properly, then you will want to include a
2517
-     * values for the name and id arguments.
2518
-     *
2519
-     * @param string  $name                       key used for the action ID (i.e. event_id)
2520
-     * @param int     $id                         id attached to the item published
2521
-     * @param string  $delete                     page route callback for the delete action
2522
-     * @param string  $save_close_redirect_URL    custom URL to redirect to after Save & Close has been completed
2523
-     * @param boolean $both_btns                  whether to display BOTH the "Save & Close" and "Save" buttons or just
2524
-     *                                            the Save button
2525
-     * @throws EE_Error
2526
-     * @throws InvalidArgumentException
2527
-     * @throws InvalidDataTypeException
2528
-     * @throws InvalidInterfaceException
2529
-     * @todo  Add in validation for name/id arguments.
2530
-     */
2531
-    protected function _set_publish_post_box_vars(
2532
-        $name = '',
2533
-        $id = 0,
2534
-        $delete = '',
2535
-        $save_close_redirect_URL = '',
2536
-        $both_btns = true
2537
-    ) {
2538
-        // if Save & Close, use a custom redirect URL or default to the main page?
2539
-        $save_close_redirect_URL = ! empty($save_close_redirect_URL)
2540
-            ? $save_close_redirect_URL
2541
-            : $this->_admin_base_url;
2542
-        // create the Save & Close and Save buttons
2543
-        $this->_set_save_buttons($both_btns, [], [], $save_close_redirect_URL);
2544
-        // if we have extra content set let's add it in if not make sure its empty
2545
-        $this->_template_args['publish_box_extra_content'] = $this->_template_args['publish_box_extra_content'] ?? '';
2546
-        $delete_link = '';
2547
-        if ($delete && ! empty($id)) {
2548
-            // make sure we have a default if just true is sent.
2549
-            $delete           = ! empty($delete) ? $delete : 'delete';
2550
-            $delete_link      = $this->get_action_link_or_button(
2551
-                $delete,
2552
-                $delete,
2553
-                [$name => $id],
2554
-                'submitdelete deletion button button--outline button--caution'
2555
-            );
2556
-        }
2557
-        $this->_template_args['publish_delete_link'] = $delete_link;
2558
-        if (! empty($name) && ! empty($id)) {
2559
-            $hidden_field_arr[ $name ] = [
2560
-                'type'  => 'hidden',
2561
-                'value' => $id,
2562
-            ];
2563
-            $hf                        = $this->_generate_admin_form_fields($hidden_field_arr, 'array');
2564
-        } else {
2565
-            $hf = '';
2566
-        }
2567
-        // add hidden field
2568
-        $this->_template_args['publish_hidden_fields'] = is_array($hf) && ! empty($name)
2569
-            ? $hf[ $name ]['field']
2570
-            : $hf;
2571
-    }
2572
-
2573
-
2574
-    /**
2575
-     * displays an error message to ppl who have javascript disabled
2576
-     *
2577
-     * @return void
2578
-     */
2579
-    private function _display_no_javascript_warning()
2580
-    {
2581
-        ?>
2393
+	}
2394
+
2395
+
2396
+	private function _espresso_links_post_box()
2397
+	{
2398
+		// Hiding until we actually have content to put in here...
2399
+		// $this->addMetaBox('espresso_links_post_box', esc_html__('Helpful Plugin Links', 'event_espresso'), array( $this, 'espresso_links_post_box'), $this->_wp_page_slug, 'side');
2400
+	}
2401
+
2402
+
2403
+	public function espresso_links_post_box()
2404
+	{
2405
+		// Hiding until we actually have content to put in here...
2406
+		// EEH_Template::display_template(
2407
+		//     EE_ADMIN_TEMPLATE . 'admin_general_metabox_contents_espresso_links.template.php'
2408
+		// );
2409
+	}
2410
+
2411
+
2412
+	protected function _espresso_sponsors_post_box()
2413
+	{
2414
+		if (apply_filters('FHEE_show_sponsors_meta_box', true)) {
2415
+			$this->addMetaBox(
2416
+				'espresso_sponsors_post_box',
2417
+				esc_html__('Event Espresso Highlights', 'event_espresso'),
2418
+				[$this, 'espresso_sponsors_post_box'],
2419
+				$this->_wp_page_slug,
2420
+				'side'
2421
+			);
2422
+		}
2423
+	}
2424
+
2425
+
2426
+	public function espresso_sponsors_post_box()
2427
+	{
2428
+		EEH_Template::display_template(
2429
+			EE_ADMIN_TEMPLATE . 'admin_general_metabox_contents_espresso_sponsors.template.php'
2430
+		);
2431
+	}
2432
+
2433
+
2434
+	private function _publish_post_box()
2435
+	{
2436
+		$meta_box_ref = 'espresso_' . $this->page_slug . '_editor_overview';
2437
+		// if there is a array('label' => array('publishbox' => 'some title') ) present in the _page_config array
2438
+		// then we'll use that for the metabox label.
2439
+		// Otherwise we'll just use publish (publishbox itself could be an array of labels indexed by routes)
2440
+		if (! empty($this->_labels['publishbox'])) {
2441
+			$box_label = is_array($this->_labels['publishbox']) ? $this->_labels['publishbox'][ $this->_req_action ]
2442
+				: $this->_labels['publishbox'];
2443
+		} else {
2444
+			$box_label = esc_html__('Publish', 'event_espresso');
2445
+		}
2446
+		$box_label = apply_filters(
2447
+			'FHEE__EE_Admin_Page___publish_post_box__box_label',
2448
+			$box_label,
2449
+			$this->_req_action,
2450
+			$this
2451
+		);
2452
+		$this->addMetaBox(
2453
+			$meta_box_ref,
2454
+			$box_label,
2455
+			[$this, 'editor_overview'],
2456
+			$this->_current_screen->id,
2457
+			'side',
2458
+			'high'
2459
+		);
2460
+	}
2461
+
2462
+
2463
+	public function editor_overview()
2464
+	{
2465
+		// if we have extra content set let's add it in if not make sure its empty
2466
+		$this->_template_args['publish_box_extra_content'] = isset($this->_template_args['publish_box_extra_content'])
2467
+			? $this->_template_args['publish_box_extra_content']
2468
+			: '';
2469
+		echo EEH_Template::display_template(
2470
+			EE_ADMIN_TEMPLATE . 'admin_details_publish_metabox.template.php',
2471
+			$this->_template_args,
2472
+			true
2473
+		);
2474
+	}
2475
+
2476
+
2477
+	/** end of globally available metaboxes section **/
2478
+
2479
+
2480
+	/**
2481
+	 * Public wrapper for the protected method.  Allows plugins/addons to externally call the
2482
+	 * protected method.
2483
+	 *
2484
+	 * @param string $name
2485
+	 * @param int    $id
2486
+	 * @param bool   $delete
2487
+	 * @param string $save_close_redirect_URL
2488
+	 * @param bool   $both_btns
2489
+	 * @throws EE_Error
2490
+	 * @throws InvalidArgumentException
2491
+	 * @throws InvalidDataTypeException
2492
+	 * @throws InvalidInterfaceException
2493
+	 * @see   $this->_set_publish_post_box_vars for param details
2494
+	 * @since 4.6.0
2495
+	 */
2496
+	public function set_publish_post_box_vars(
2497
+		$name = '',
2498
+		$id = 0,
2499
+		$delete = false,
2500
+		$save_close_redirect_URL = '',
2501
+		$both_btns = true
2502
+	) {
2503
+		$this->_set_publish_post_box_vars(
2504
+			$name,
2505
+			$id,
2506
+			$delete,
2507
+			$save_close_redirect_URL,
2508
+			$both_btns
2509
+		);
2510
+	}
2511
+
2512
+
2513
+	/**
2514
+	 * Sets the _template_args arguments used by the _publish_post_box shortcut
2515
+	 * Note: currently there is no validation for this.  However if you want the delete button, the
2516
+	 * save, and save and close buttons to work properly, then you will want to include a
2517
+	 * values for the name and id arguments.
2518
+	 *
2519
+	 * @param string  $name                       key used for the action ID (i.e. event_id)
2520
+	 * @param int     $id                         id attached to the item published
2521
+	 * @param string  $delete                     page route callback for the delete action
2522
+	 * @param string  $save_close_redirect_URL    custom URL to redirect to after Save & Close has been completed
2523
+	 * @param boolean $both_btns                  whether to display BOTH the "Save & Close" and "Save" buttons or just
2524
+	 *                                            the Save button
2525
+	 * @throws EE_Error
2526
+	 * @throws InvalidArgumentException
2527
+	 * @throws InvalidDataTypeException
2528
+	 * @throws InvalidInterfaceException
2529
+	 * @todo  Add in validation for name/id arguments.
2530
+	 */
2531
+	protected function _set_publish_post_box_vars(
2532
+		$name = '',
2533
+		$id = 0,
2534
+		$delete = '',
2535
+		$save_close_redirect_URL = '',
2536
+		$both_btns = true
2537
+	) {
2538
+		// if Save & Close, use a custom redirect URL or default to the main page?
2539
+		$save_close_redirect_URL = ! empty($save_close_redirect_URL)
2540
+			? $save_close_redirect_URL
2541
+			: $this->_admin_base_url;
2542
+		// create the Save & Close and Save buttons
2543
+		$this->_set_save_buttons($both_btns, [], [], $save_close_redirect_URL);
2544
+		// if we have extra content set let's add it in if not make sure its empty
2545
+		$this->_template_args['publish_box_extra_content'] = $this->_template_args['publish_box_extra_content'] ?? '';
2546
+		$delete_link = '';
2547
+		if ($delete && ! empty($id)) {
2548
+			// make sure we have a default if just true is sent.
2549
+			$delete           = ! empty($delete) ? $delete : 'delete';
2550
+			$delete_link      = $this->get_action_link_or_button(
2551
+				$delete,
2552
+				$delete,
2553
+				[$name => $id],
2554
+				'submitdelete deletion button button--outline button--caution'
2555
+			);
2556
+		}
2557
+		$this->_template_args['publish_delete_link'] = $delete_link;
2558
+		if (! empty($name) && ! empty($id)) {
2559
+			$hidden_field_arr[ $name ] = [
2560
+				'type'  => 'hidden',
2561
+				'value' => $id,
2562
+			];
2563
+			$hf                        = $this->_generate_admin_form_fields($hidden_field_arr, 'array');
2564
+		} else {
2565
+			$hf = '';
2566
+		}
2567
+		// add hidden field
2568
+		$this->_template_args['publish_hidden_fields'] = is_array($hf) && ! empty($name)
2569
+			? $hf[ $name ]['field']
2570
+			: $hf;
2571
+	}
2572
+
2573
+
2574
+	/**
2575
+	 * displays an error message to ppl who have javascript disabled
2576
+	 *
2577
+	 * @return void
2578
+	 */
2579
+	private function _display_no_javascript_warning()
2580
+	{
2581
+		?>
2582 2582
         <noscript>
2583 2583
             <div id="no-js-message" class="error">
2584 2584
                 <p style="font-size:1.3em;">
2585 2585
                     <span style="color:red;"><?php esc_html_e('Warning!', 'event_espresso'); ?></span>
2586 2586
                     <?php esc_html_e(
2587
-                        '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.',
2588
-                        'event_espresso'
2589
-                    ); ?>
2587
+						'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.',
2588
+						'event_espresso'
2589
+					); ?>
2590 2590
                 </p>
2591 2591
             </div>
2592 2592
         </noscript>
2593 2593
         <?php
2594
-    }
2595
-
2596
-
2597
-    /**
2598
-     * displays espresso success and/or error notices
2599
-     *
2600
-     * @return void
2601
-     */
2602
-    protected function _display_espresso_notices()
2603
-    {
2604
-        $notices = $this->_get_transient(true);
2605
-        echo stripslashes($notices);
2606
-    }
2607
-
2608
-
2609
-    /**
2610
-     * spinny things pacify the masses
2611
-     *
2612
-     * @return void
2613
-     */
2614
-    protected function _add_admin_page_ajax_loading_img()
2615
-    {
2616
-        ?>
2594
+	}
2595
+
2596
+
2597
+	/**
2598
+	 * displays espresso success and/or error notices
2599
+	 *
2600
+	 * @return void
2601
+	 */
2602
+	protected function _display_espresso_notices()
2603
+	{
2604
+		$notices = $this->_get_transient(true);
2605
+		echo stripslashes($notices);
2606
+	}
2607
+
2608
+
2609
+	/**
2610
+	 * spinny things pacify the masses
2611
+	 *
2612
+	 * @return void
2613
+	 */
2614
+	protected function _add_admin_page_ajax_loading_img()
2615
+	{
2616
+		?>
2617 2617
         <div id="espresso-ajax-loading" class="ajax-loading-grey">
2618 2618
             <span class="ee-spinner ee-spin"></span><span class="hidden"><?php
2619
-                esc_html_e('loading...', 'event_espresso'); ?></span>
2619
+				esc_html_e('loading...', 'event_espresso'); ?></span>
2620 2620
         </div>
2621 2621
         <?php
2622
-    }
2622
+	}
2623 2623
 
2624 2624
 
2625
-    /**
2626
-     * add admin page overlay for modal boxes
2627
-     *
2628
-     * @return void
2629
-     */
2630
-    protected function _add_admin_page_overlay()
2631
-    {
2632
-        ?>
2625
+	/**
2626
+	 * add admin page overlay for modal boxes
2627
+	 *
2628
+	 * @return void
2629
+	 */
2630
+	protected function _add_admin_page_overlay()
2631
+	{
2632
+		?>
2633 2633
         <div id="espresso-admin-page-overlay-dv" class=""></div>
2634 2634
         <?php
2635
-    }
2636
-
2637
-
2638
-    /**
2639
-     * facade for $this->addMetaBox()
2640
-     *
2641
-     * @param string  $action        where the metabox gets displayed
2642
-     * @param string  $title         Title of Metabox (output in metabox header)
2643
-     * @param string  $callback      If not empty and $create_fun is set to false then we'll use a custom callback
2644
-     *                               instead of the one created in here.
2645
-     * @param array   $callback_args an array of args supplied for the metabox
2646
-     * @param string  $column        what metabox column
2647
-     * @param string  $priority      give this metabox a priority (using accepted priorities for wp meta boxes)
2648
-     * @param boolean $create_func   default is true.  Basically we can say we don't WANT to have the runtime function
2649
-     *                               created but just set our own callback for wp's add_meta_box.
2650
-     * @throws DomainException
2651
-     */
2652
-    public function _add_admin_page_meta_box(
2653
-        $action,
2654
-        $title,
2655
-        $callback,
2656
-        $callback_args,
2657
-        $column = 'normal',
2658
-        $priority = 'high',
2659
-        $create_func = true
2660
-    ) {
2661
-        do_action('AHEE_log', __FILE__, __FUNCTION__, $callback);
2662
-        // 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.
2663
-        if (empty($callback_args) && $create_func) {
2664
-            $callback_args = [
2665
-                'template_path' => $this->_template_path,
2666
-                'template_args' => $this->_template_args,
2667
-            ];
2668
-        }
2669
-        // 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)
2670
-        $call_back_func = $create_func
2671
-            ? static function ($post, $metabox) {
2672
-                do_action('AHEE_log', __FILE__, __FUNCTION__, '');
2673
-                echo EEH_Template::display_template(
2674
-                    $metabox['args']['template_path'],
2675
-                    $metabox['args']['template_args'],
2676
-                    true
2677
-                );
2678
-            }
2679
-            : $callback;
2680
-        $this->addMetaBox(
2681
-            str_replace('_', '-', $action) . '-mbox',
2682
-            $title,
2683
-            $call_back_func,
2684
-            $this->_wp_page_slug,
2685
-            $column,
2686
-            $priority,
2687
-            $callback_args
2688
-        );
2689
-    }
2690
-
2691
-
2692
-    /**
2693
-     * generates HTML wrapper for and admin details page that contains metaboxes in columns
2694
-     *
2695
-     * @throws DomainException
2696
-     * @throws EE_Error
2697
-     * @throws InvalidArgumentException
2698
-     * @throws InvalidDataTypeException
2699
-     * @throws InvalidInterfaceException
2700
-     */
2701
-    public function display_admin_page_with_metabox_columns()
2702
-    {
2703
-        $this->_template_args['post_body_content']  = $this->_template_args['admin_page_content'];
2704
-        $this->_template_args['admin_page_content'] = EEH_Template::display_template(
2705
-            $this->_column_template_path,
2706
-            $this->_template_args,
2707
-            true
2708
-        );
2709
-        // the final wrapper
2710
-        $this->admin_page_wrapper();
2711
-    }
2712
-
2713
-
2714
-    /**
2715
-     * generates  HTML wrapper for an admin details page
2716
-     *
2717
-     * @return void
2718
-     * @throws DomainException
2719
-     * @throws EE_Error
2720
-     * @throws InvalidArgumentException
2721
-     * @throws InvalidDataTypeException
2722
-     * @throws InvalidInterfaceException
2723
-     */
2724
-    public function display_admin_page_with_sidebar()
2725
-    {
2726
-        $this->_display_admin_page(true);
2727
-    }
2728
-
2729
-
2730
-    /**
2731
-     * generates  HTML wrapper for an admin details page (except no sidebar)
2732
-     *
2733
-     * @return void
2734
-     * @throws DomainException
2735
-     * @throws EE_Error
2736
-     * @throws InvalidArgumentException
2737
-     * @throws InvalidDataTypeException
2738
-     * @throws InvalidInterfaceException
2739
-     */
2740
-    public function display_admin_page_with_no_sidebar()
2741
-    {
2742
-        $this->_display_admin_page();
2743
-    }
2744
-
2745
-
2746
-    /**
2747
-     * generates HTML wrapper for an EE about admin page (no sidebar)
2748
-     *
2749
-     * @return void
2750
-     * @throws DomainException
2751
-     * @throws EE_Error
2752
-     * @throws InvalidArgumentException
2753
-     * @throws InvalidDataTypeException
2754
-     * @throws InvalidInterfaceException
2755
-     */
2756
-    public function display_about_admin_page()
2757
-    {
2758
-        $this->_display_admin_page(false, true);
2759
-    }
2760
-
2761
-
2762
-    /**
2763
-     * display_admin_page
2764
-     * contains the code for actually displaying an admin page
2765
-     *
2766
-     * @param boolean $sidebar true with sidebar, false without
2767
-     * @param boolean $about   use the about admin wrapper instead of the default.
2768
-     * @return void
2769
-     * @throws DomainException
2770
-     * @throws EE_Error
2771
-     * @throws InvalidArgumentException
2772
-     * @throws InvalidDataTypeException
2773
-     * @throws InvalidInterfaceException
2774
-     */
2775
-    private function _display_admin_page($sidebar = false, $about = false)
2776
-    {
2777
-        do_action('AHEE_log', __FILE__, __FUNCTION__, '');
2778
-        // custom remove metaboxes hook to add or remove any metaboxes to/from Admin pages.
2779
-        do_action('AHEE__EE_Admin_Page___display_admin_page__modify_metaboxes');
2780
-        // set current wp page slug - looks like: event-espresso_page_event_categories
2781
-        // keep in mind "event-espresso" COULD be something else if the top level menu label has been translated.
2782
-
2783
-        $post_body_content = $this->_template_args['before_admin_page_content'] ?? '';
2784
-
2785
-        $this->_template_args['add_page_frame'] = $this->_req_action !== 'system_status'
2786
-                                                 && $this->_req_action !== 'data_reset'
2787
-                                                 && $this->_wp_page_slug !== 'event-espresso_page_espresso_packages'
2788
-                                                 && strpos($post_body_content, 'wp-list-table') === false;
2789
-
2790
-        $this->_template_args['current_page']              = $this->_wp_page_slug;
2791
-        $this->_template_args['admin_page_wrapper_div_id'] = $this->_cpt_route
2792
-            ? 'poststuff'
2793
-            : 'espresso-default-admin';
2794
-        $this->_template_args['admin_page_wrapper_div_class'] = str_replace(
2795
-            'event-espresso_page_espresso_',
2796
-            '',
2797
-            $this->_wp_page_slug
2798
-        ) . ' ' . $this->_req_action . '-route';
2799
-
2800
-        $template_path = $sidebar
2801
-            ? EE_ADMIN_TEMPLATE . 'admin_details_wrapper.template.php'
2802
-            : EE_ADMIN_TEMPLATE . 'admin_details_wrapper_no_sidebar.template.php';
2803
-        if ($this->request->isAjax()) {
2804
-            $template_path = EE_ADMIN_TEMPLATE . 'admin_details_wrapper_no_sidebar_ajax.template.php';
2805
-        }
2806
-        $template_path = ! empty($this->_column_template_path) ? $this->_column_template_path : $template_path;
2807
-
2808
-        $this->_template_args['post_body_content']         = $this->_template_args['admin_page_content'] ?? '';
2809
-        $this->_template_args['before_admin_page_content'] = $post_body_content;
2810
-        $this->_template_args['after_admin_page_content']  = $this->_template_args['after_admin_page_content'] ?? '';
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($utm_campaign_source = '', $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 boolean $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($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 .= '
2635
+	}
2636
+
2637
+
2638
+	/**
2639
+	 * facade for $this->addMetaBox()
2640
+	 *
2641
+	 * @param string  $action        where the metabox gets displayed
2642
+	 * @param string  $title         Title of Metabox (output in metabox header)
2643
+	 * @param string  $callback      If not empty and $create_fun is set to false then we'll use a custom callback
2644
+	 *                               instead of the one created in here.
2645
+	 * @param array   $callback_args an array of args supplied for the metabox
2646
+	 * @param string  $column        what metabox column
2647
+	 * @param string  $priority      give this metabox a priority (using accepted priorities for wp meta boxes)
2648
+	 * @param boolean $create_func   default is true.  Basically we can say we don't WANT to have the runtime function
2649
+	 *                               created but just set our own callback for wp's add_meta_box.
2650
+	 * @throws DomainException
2651
+	 */
2652
+	public function _add_admin_page_meta_box(
2653
+		$action,
2654
+		$title,
2655
+		$callback,
2656
+		$callback_args,
2657
+		$column = 'normal',
2658
+		$priority = 'high',
2659
+		$create_func = true
2660
+	) {
2661
+		do_action('AHEE_log', __FILE__, __FUNCTION__, $callback);
2662
+		// 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.
2663
+		if (empty($callback_args) && $create_func) {
2664
+			$callback_args = [
2665
+				'template_path' => $this->_template_path,
2666
+				'template_args' => $this->_template_args,
2667
+			];
2668
+		}
2669
+		// 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)
2670
+		$call_back_func = $create_func
2671
+			? static function ($post, $metabox) {
2672
+				do_action('AHEE_log', __FILE__, __FUNCTION__, '');
2673
+				echo EEH_Template::display_template(
2674
+					$metabox['args']['template_path'],
2675
+					$metabox['args']['template_args'],
2676
+					true
2677
+				);
2678
+			}
2679
+			: $callback;
2680
+		$this->addMetaBox(
2681
+			str_replace('_', '-', $action) . '-mbox',
2682
+			$title,
2683
+			$call_back_func,
2684
+			$this->_wp_page_slug,
2685
+			$column,
2686
+			$priority,
2687
+			$callback_args
2688
+		);
2689
+	}
2690
+
2691
+
2692
+	/**
2693
+	 * generates HTML wrapper for and admin details page that contains metaboxes in columns
2694
+	 *
2695
+	 * @throws DomainException
2696
+	 * @throws EE_Error
2697
+	 * @throws InvalidArgumentException
2698
+	 * @throws InvalidDataTypeException
2699
+	 * @throws InvalidInterfaceException
2700
+	 */
2701
+	public function display_admin_page_with_metabox_columns()
2702
+	{
2703
+		$this->_template_args['post_body_content']  = $this->_template_args['admin_page_content'];
2704
+		$this->_template_args['admin_page_content'] = EEH_Template::display_template(
2705
+			$this->_column_template_path,
2706
+			$this->_template_args,
2707
+			true
2708
+		);
2709
+		// the final wrapper
2710
+		$this->admin_page_wrapper();
2711
+	}
2712
+
2713
+
2714
+	/**
2715
+	 * generates  HTML wrapper for an admin details page
2716
+	 *
2717
+	 * @return void
2718
+	 * @throws DomainException
2719
+	 * @throws EE_Error
2720
+	 * @throws InvalidArgumentException
2721
+	 * @throws InvalidDataTypeException
2722
+	 * @throws InvalidInterfaceException
2723
+	 */
2724
+	public function display_admin_page_with_sidebar()
2725
+	{
2726
+		$this->_display_admin_page(true);
2727
+	}
2728
+
2729
+
2730
+	/**
2731
+	 * generates  HTML wrapper for an admin details page (except no sidebar)
2732
+	 *
2733
+	 * @return void
2734
+	 * @throws DomainException
2735
+	 * @throws EE_Error
2736
+	 * @throws InvalidArgumentException
2737
+	 * @throws InvalidDataTypeException
2738
+	 * @throws InvalidInterfaceException
2739
+	 */
2740
+	public function display_admin_page_with_no_sidebar()
2741
+	{
2742
+		$this->_display_admin_page();
2743
+	}
2744
+
2745
+
2746
+	/**
2747
+	 * generates HTML wrapper for an EE about admin page (no sidebar)
2748
+	 *
2749
+	 * @return void
2750
+	 * @throws DomainException
2751
+	 * @throws EE_Error
2752
+	 * @throws InvalidArgumentException
2753
+	 * @throws InvalidDataTypeException
2754
+	 * @throws InvalidInterfaceException
2755
+	 */
2756
+	public function display_about_admin_page()
2757
+	{
2758
+		$this->_display_admin_page(false, true);
2759
+	}
2760
+
2761
+
2762
+	/**
2763
+	 * display_admin_page
2764
+	 * contains the code for actually displaying an admin page
2765
+	 *
2766
+	 * @param boolean $sidebar true with sidebar, false without
2767
+	 * @param boolean $about   use the about admin wrapper instead of the default.
2768
+	 * @return void
2769
+	 * @throws DomainException
2770
+	 * @throws EE_Error
2771
+	 * @throws InvalidArgumentException
2772
+	 * @throws InvalidDataTypeException
2773
+	 * @throws InvalidInterfaceException
2774
+	 */
2775
+	private function _display_admin_page($sidebar = false, $about = false)
2776
+	{
2777
+		do_action('AHEE_log', __FILE__, __FUNCTION__, '');
2778
+		// custom remove metaboxes hook to add or remove any metaboxes to/from Admin pages.
2779
+		do_action('AHEE__EE_Admin_Page___display_admin_page__modify_metaboxes');
2780
+		// set current wp page slug - looks like: event-espresso_page_event_categories
2781
+		// keep in mind "event-espresso" COULD be something else if the top level menu label has been translated.
2782
+
2783
+		$post_body_content = $this->_template_args['before_admin_page_content'] ?? '';
2784
+
2785
+		$this->_template_args['add_page_frame'] = $this->_req_action !== 'system_status'
2786
+												 && $this->_req_action !== 'data_reset'
2787
+												 && $this->_wp_page_slug !== 'event-espresso_page_espresso_packages'
2788
+												 && strpos($post_body_content, 'wp-list-table') === false;
2789
+
2790
+		$this->_template_args['current_page']              = $this->_wp_page_slug;
2791
+		$this->_template_args['admin_page_wrapper_div_id'] = $this->_cpt_route
2792
+			? 'poststuff'
2793
+			: 'espresso-default-admin';
2794
+		$this->_template_args['admin_page_wrapper_div_class'] = str_replace(
2795
+			'event-espresso_page_espresso_',
2796
+			'',
2797
+			$this->_wp_page_slug
2798
+		) . ' ' . $this->_req_action . '-route';
2799
+
2800
+		$template_path = $sidebar
2801
+			? EE_ADMIN_TEMPLATE . 'admin_details_wrapper.template.php'
2802
+			: EE_ADMIN_TEMPLATE . 'admin_details_wrapper_no_sidebar.template.php';
2803
+		if ($this->request->isAjax()) {
2804
+			$template_path = EE_ADMIN_TEMPLATE . 'admin_details_wrapper_no_sidebar_ajax.template.php';
2805
+		}
2806
+		$template_path = ! empty($this->_column_template_path) ? $this->_column_template_path : $template_path;
2807
+
2808
+		$this->_template_args['post_body_content']         = $this->_template_args['admin_page_content'] ?? '';
2809
+		$this->_template_args['before_admin_page_content'] = $post_body_content;
2810
+		$this->_template_args['after_admin_page_content']  = $this->_template_args['after_admin_page_content'] ?? '';
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($utm_campaign_source = '', $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 boolean $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($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($items)
3034
-    {
3035
-        $this->_template_args['items'] = apply_filters(
3036
-            'FHEE__EE_Admin_Page___display_legend__items',
3037
-            (array) $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($sticky_notices = false, $notices_arguments = [])
3080
-    {
3081
-        // make sure any EE_Error notices have been handled.
3082
-        $this->_process_notices($notices_arguments, true, $sticky_notices);
3083
-        $data = isset($this->_template_args['data']) ? $this->_template_args['data'] : [];
3084
-        unset($this->_template_args['data']);
3085
-        $json = [
3086
-            'error'     => isset($this->_template_args['error']) ? $this->_template_args['error'] : false,
3087
-            'success'   => isset($this->_template_args['success']) ? $this->_template_args['success'] : false,
3088
-            'errors'    => isset($this->_template_args['errors']) ? $this->_template_args['errors'] : false,
3089
-            'attention' => isset($this->_template_args['attention']) ? $this->_template_args['attention'] : false,
3090
-            'notices'   => EE_Error::get_notices(),
3091
-            'content'   => isset($this->_template_args['admin_page_content'])
3092
-                ? $this->_template_args['admin_page_content'] : '',
3093
-            'data'      => array_merge($data, ['template_args' => $this->_template_args]),
3094
-            'isEEajax'  => true
3095
-            // special flag so any ajax.Success methods in js can identify this return package as a EEajax package.
3096
-        ];
3097
-        // make sure there are no php errors or headers_sent.  Then we can set correct json header.
3098
-        if (null === error_get_last() || ! headers_sent()) {
3099
-            header('Content-Type: application/json; charset=UTF-8');
3100
-        }
3101
-        echo wp_json_encode($json);
3102
-        exit();
3103
-    }
3104
-
3105
-
3106
-    /**
3107
-     * Simply a wrapper for the protected method so we can call this outside the class (ONLY when doing ajax)
3108
-     *
3109
-     * @return void
3110
-     * @throws EE_Error
3111
-     * @throws InvalidArgumentException
3112
-     * @throws InvalidDataTypeException
3113
-     * @throws InvalidInterfaceException
3114
-     */
3115
-    public function return_json()
3116
-    {
3117
-        if ($this->request->isAjax()) {
3118
-            $this->_return_json();
3119
-        } else {
3120
-            throw new EE_Error(
3121
-                sprintf(
3122
-                    esc_html__('The public %s method can only be called when DOING_AJAX = TRUE', 'event_espresso'),
3123
-                    __FUNCTION__
3124
-                )
3125
-            );
3126
-        }
3127
-    }
3128
-
3129
-
3130
-    /**
3131
-     * This provides a way for child hook classes to send along themselves by reference so methods/properties within
3132
-     * them can be accessed by EE_Admin_child pages. This is assigned to the $_hook_obj property.
3133
-     *
3134
-     * @param EE_Admin_Hooks $hook_obj This will be the object for the EE_Admin_Hooks child
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 boolean $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($about = false)
3154
-    {
3155
-        do_action('AHEE_log', __FILE__, __FUNCTION__, '');
3156
-        $this->_nav_tabs                                   = $this->_get_main_nav_tabs();
3157
-        $this->_template_args['nav_tabs']                  = $this->_nav_tabs;
3158
-        $this->_template_args['admin_page_title']          = $this->_admin_page_title;
3159
-
3160
-        $this->_template_args['before_admin_page_content'] = apply_filters(
3161
-            "FHEE_before_admin_page_content{$this->_current_page}{$this->_current_view}",
3162
-            $this->_template_args['before_admin_page_content'] ?? ''
3163
-        );
3164
-
3165
-        $this->_template_args['after_admin_page_content']  = apply_filters(
3166
-            "FHEE_after_admin_page_content{$this->_current_page}{$this->_current_view}",
3167
-            $this->_template_args['after_admin_page_content'] ?? ''
3168
-        );
3169
-        $this->_template_args['after_admin_page_content']  .= $this->_set_help_popup_content();
3170
-
3171
-        if ($this->request->isAjax()) {
3172
-            $this->_template_args['admin_page_content'] = EEH_Template::display_template(
3173
-                // $template_path,
3174
-                EE_ADMIN_TEMPLATE . 'admin_wrapper_ajax.template.php',
3175
-                $this->_template_args,
3176
-                true
3177
-            );
3178
-            $this->_return_json();
3179
-        }
3180
-        // load settings page wrapper template
3181
-        $template_path = $about
3182
-            ? EE_ADMIN_TEMPLATE . 'about_admin_wrapper.template.php'
3183
-            : EE_ADMIN_TEMPLATE . 'admin_wrapper.template.php';
3184
-
3185
-        EEH_Template::display_template($template_path, $this->_template_args);
3186
-    }
3187
-
3188
-
3189
-    /**
3190
-     * This returns the admin_nav tabs html using the configuration in the _nav_tabs property
3191
-     *
3192
-     * @return string html
3193
-     * @throws EE_Error
3194
-     */
3195
-    protected function _get_main_nav_tabs()
3196
-    {
3197
-        // let's generate the html using the EEH_Tabbed_Content helper.
3198
-        // We do this here so that it's possible for child classes to add in nav tabs dynamically at the last minute
3199
-        // (rather than setting in the page_routes array)
3200
-        return EEH_Tabbed_Content::display_admin_nav_tabs($this->_nav_tabs, $this->page_slug);
3201
-    }
3202
-
3203
-
3204
-    /**
3205
-     *        sort nav tabs
3206
-     *
3207
-     * @param $a
3208
-     * @param $b
3209
-     * @return int
3210
-     */
3211
-    private function _sort_nav_tabs($a, $b)
3212
-    {
3213
-        if ($a['order'] === $b['order']) {
3214
-            return 0;
3215
-        }
3216
-        return ($a['order'] < $b['order']) ? -1 : 1;
3217
-    }
3218
-
3219
-
3220
-    /**
3221
-     * generates HTML for the forms used on admin pages
3222
-     *
3223
-     * @param array  $input_vars - array of input field details
3224
-     * @param string $generator  indicates which generator to use: options are 'string' or 'array'
3225
-     * @param bool   $id
3226
-     * @return array|string
3227
-     * @uses   EEH_Form_Fields::get_form_fields (/helper/EEH_Form_Fields.helper.php)
3228
-     * @uses   EEH_Form_Fields::get_form_fields_array (/helper/EEH_Form_Fields.helper.php)
3229
-     */
3230
-    protected function _generate_admin_form_fields($input_vars = [], $generator = 'string', $id = false)
3231
-    {
3232
-        return $generator === 'string'
3233
-            ? EEH_Form_Fields::get_form_fields($input_vars, $id)
3234
-            : EEH_Form_Fields::get_form_fields_array($input_vars);
3235
-    }
3236
-
3237
-
3238
-    /**
3239
-     * generates the "Save" and "Save & Close" buttons for edit forms
3240
-     *
3241
-     * @param bool             $both     if true then both buttons will be generated.  If false then just the "Save &
3242
-     *                                   Close" button.
3243
-     * @param array            $text     if included, generator will use the given text for the buttons ( array([0] =>
3244
-     *                                   'Save', [1] => 'save & close')
3245
-     * @param array            $actions  if included allows us to set the actions that each button will carry out (i.e.
3246
-     *                                   via the "name" value in the button).  We can also use this to just dump
3247
-     *                                   default actions by submitting some other value.
3248
-     * @param bool|string|null $referrer if false then we just do the default action on save and close.  Other wise it
3249
-     *                                   will use the $referrer string. IF null, then we don't do ANYTHING on save and
3250
-     *                                   close (normal form handling).
3251
-     */
3252
-    protected function _set_save_buttons($both = true, $text = [], $actions = [], $referrer = null)
3253
-    {
3254
-        // make sure $text and $actions are in an array
3255
-        $text          = (array) $text;
3256
-        $actions       = (array) $actions;
3257
-        $referrer_url  = ! empty($referrer) ? $referrer : $this->request->getServerParam('REQUEST_URI');
3258
-        $button_text   = ! empty($text)
3259
-            ? $text
3260
-            : [
3261
-                esc_html__('Save', 'event_espresso'),
3262
-                esc_html__('Save and Close', 'event_espresso'),
3263
-            ];
3264
-        $default_names = ['save', 'save_and_close'];
3265
-        $buttons = '';
3266
-        foreach ($button_text as $key => $button) {
3267
-            $ref     = $default_names[ $key ];
3268
-            $name    = ! empty($actions) ? $actions[ $key ] : $ref;
3269
-            $buttons .= '<input type="submit" class="button button--primary ' . $ref . '" '
3270
-                        . 'value="' . $button . '" name="' . $name . '" '
3271
-                        . 'id="' . $this->_current_view . '_' . $ref . '" />';
3272
-            if (! $both) {
3273
-                break;
3274
-            }
3275
-        }
3276
-        // add in a hidden index for the current page (so save and close redirects properly)
3277
-        $buttons .= '<input type="hidden" id="save_and_close_referrer" name="save_and_close_referrer" value="'
3278
-                   . $referrer_url
3279
-                   . '" />';
3280
-        $this->_template_args['save_buttons'] = $buttons;
3281
-    }
3282
-
3283
-
3284
-    /**
3285
-     * Wrapper for the protected function.  Allows plugins/addons to call this to set the form tags.
3286
-     *
3287
-     * @param string $route
3288
-     * @param array  $additional_hidden_fields
3289
-     * @see   $this->_set_add_edit_form_tags() for details on params
3290
-     * @since 4.6.0
3291
-     */
3292
-    public function set_add_edit_form_tags($route = '', $additional_hidden_fields = [])
3293
-    {
3294
-        $this->_set_add_edit_form_tags($route, $additional_hidden_fields);
3295
-    }
3296
-
3297
-
3298
-    /**
3299
-     * set form open and close tags on add/edit pages.
3300
-     *
3301
-     * @param string $route                    the route you want the form to direct to
3302
-     * @param array  $additional_hidden_fields any additional hidden fields required in the form header
3303
-     * @return void
3304
-     */
3305
-    protected function _set_add_edit_form_tags($route = '', $additional_hidden_fields = [])
3306
-    {
3307
-        if (empty($route)) {
3308
-            $user_msg = esc_html__(
3309
-                'An error occurred. No action was set for this page\'s form.',
3310
-                'event_espresso'
3311
-            );
3312
-            $dev_msg  = $user_msg . "\n"
3313
-                        . sprintf(
3314
-                            esc_html__('The $route argument is required for the %s->%s method.', 'event_espresso'),
3315
-                            __FUNCTION__,
3316
-                            __CLASS__
3317
-                        );
3318
-            EE_Error::add_error($user_msg . '||' . $dev_msg, __FILE__, __FUNCTION__, __LINE__);
3319
-        }
3320
-        // open form
3321
-        $action = $this->_admin_base_url;
3322
-        $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($items)
3034
+	{
3035
+		$this->_template_args['items'] = apply_filters(
3036
+			'FHEE__EE_Admin_Page___display_legend__items',
3037
+			(array) $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($sticky_notices = false, $notices_arguments = [])
3080
+	{
3081
+		// make sure any EE_Error notices have been handled.
3082
+		$this->_process_notices($notices_arguments, true, $sticky_notices);
3083
+		$data = isset($this->_template_args['data']) ? $this->_template_args['data'] : [];
3084
+		unset($this->_template_args['data']);
3085
+		$json = [
3086
+			'error'     => isset($this->_template_args['error']) ? $this->_template_args['error'] : false,
3087
+			'success'   => isset($this->_template_args['success']) ? $this->_template_args['success'] : false,
3088
+			'errors'    => isset($this->_template_args['errors']) ? $this->_template_args['errors'] : false,
3089
+			'attention' => isset($this->_template_args['attention']) ? $this->_template_args['attention'] : false,
3090
+			'notices'   => EE_Error::get_notices(),
3091
+			'content'   => isset($this->_template_args['admin_page_content'])
3092
+				? $this->_template_args['admin_page_content'] : '',
3093
+			'data'      => array_merge($data, ['template_args' => $this->_template_args]),
3094
+			'isEEajax'  => true
3095
+			// special flag so any ajax.Success methods in js can identify this return package as a EEajax package.
3096
+		];
3097
+		// make sure there are no php errors or headers_sent.  Then we can set correct json header.
3098
+		if (null === error_get_last() || ! headers_sent()) {
3099
+			header('Content-Type: application/json; charset=UTF-8');
3100
+		}
3101
+		echo wp_json_encode($json);
3102
+		exit();
3103
+	}
3104
+
3105
+
3106
+	/**
3107
+	 * Simply a wrapper for the protected method so we can call this outside the class (ONLY when doing ajax)
3108
+	 *
3109
+	 * @return void
3110
+	 * @throws EE_Error
3111
+	 * @throws InvalidArgumentException
3112
+	 * @throws InvalidDataTypeException
3113
+	 * @throws InvalidInterfaceException
3114
+	 */
3115
+	public function return_json()
3116
+	{
3117
+		if ($this->request->isAjax()) {
3118
+			$this->_return_json();
3119
+		} else {
3120
+			throw new EE_Error(
3121
+				sprintf(
3122
+					esc_html__('The public %s method can only be called when DOING_AJAX = TRUE', 'event_espresso'),
3123
+					__FUNCTION__
3124
+				)
3125
+			);
3126
+		}
3127
+	}
3128
+
3129
+
3130
+	/**
3131
+	 * This provides a way for child hook classes to send along themselves by reference so methods/properties within
3132
+	 * them can be accessed by EE_Admin_child pages. This is assigned to the $_hook_obj property.
3133
+	 *
3134
+	 * @param EE_Admin_Hooks $hook_obj This will be the object for the EE_Admin_Hooks child
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 boolean $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($about = false)
3154
+	{
3155
+		do_action('AHEE_log', __FILE__, __FUNCTION__, '');
3156
+		$this->_nav_tabs                                   = $this->_get_main_nav_tabs();
3157
+		$this->_template_args['nav_tabs']                  = $this->_nav_tabs;
3158
+		$this->_template_args['admin_page_title']          = $this->_admin_page_title;
3159
+
3160
+		$this->_template_args['before_admin_page_content'] = apply_filters(
3161
+			"FHEE_before_admin_page_content{$this->_current_page}{$this->_current_view}",
3162
+			$this->_template_args['before_admin_page_content'] ?? ''
3163
+		);
3164
+
3165
+		$this->_template_args['after_admin_page_content']  = apply_filters(
3166
+			"FHEE_after_admin_page_content{$this->_current_page}{$this->_current_view}",
3167
+			$this->_template_args['after_admin_page_content'] ?? ''
3168
+		);
3169
+		$this->_template_args['after_admin_page_content']  .= $this->_set_help_popup_content();
3170
+
3171
+		if ($this->request->isAjax()) {
3172
+			$this->_template_args['admin_page_content'] = EEH_Template::display_template(
3173
+				// $template_path,
3174
+				EE_ADMIN_TEMPLATE . 'admin_wrapper_ajax.template.php',
3175
+				$this->_template_args,
3176
+				true
3177
+			);
3178
+			$this->_return_json();
3179
+		}
3180
+		// load settings page wrapper template
3181
+		$template_path = $about
3182
+			? EE_ADMIN_TEMPLATE . 'about_admin_wrapper.template.php'
3183
+			: EE_ADMIN_TEMPLATE . 'admin_wrapper.template.php';
3184
+
3185
+		EEH_Template::display_template($template_path, $this->_template_args);
3186
+	}
3187
+
3188
+
3189
+	/**
3190
+	 * This returns the admin_nav tabs html using the configuration in the _nav_tabs property
3191
+	 *
3192
+	 * @return string html
3193
+	 * @throws EE_Error
3194
+	 */
3195
+	protected function _get_main_nav_tabs()
3196
+	{
3197
+		// let's generate the html using the EEH_Tabbed_Content helper.
3198
+		// We do this here so that it's possible for child classes to add in nav tabs dynamically at the last minute
3199
+		// (rather than setting in the page_routes array)
3200
+		return EEH_Tabbed_Content::display_admin_nav_tabs($this->_nav_tabs, $this->page_slug);
3201
+	}
3202
+
3203
+
3204
+	/**
3205
+	 *        sort nav tabs
3206
+	 *
3207
+	 * @param $a
3208
+	 * @param $b
3209
+	 * @return int
3210
+	 */
3211
+	private function _sort_nav_tabs($a, $b)
3212
+	{
3213
+		if ($a['order'] === $b['order']) {
3214
+			return 0;
3215
+		}
3216
+		return ($a['order'] < $b['order']) ? -1 : 1;
3217
+	}
3218
+
3219
+
3220
+	/**
3221
+	 * generates HTML for the forms used on admin pages
3222
+	 *
3223
+	 * @param array  $input_vars - array of input field details
3224
+	 * @param string $generator  indicates which generator to use: options are 'string' or 'array'
3225
+	 * @param bool   $id
3226
+	 * @return array|string
3227
+	 * @uses   EEH_Form_Fields::get_form_fields (/helper/EEH_Form_Fields.helper.php)
3228
+	 * @uses   EEH_Form_Fields::get_form_fields_array (/helper/EEH_Form_Fields.helper.php)
3229
+	 */
3230
+	protected function _generate_admin_form_fields($input_vars = [], $generator = 'string', $id = false)
3231
+	{
3232
+		return $generator === 'string'
3233
+			? EEH_Form_Fields::get_form_fields($input_vars, $id)
3234
+			: EEH_Form_Fields::get_form_fields_array($input_vars);
3235
+	}
3236
+
3237
+
3238
+	/**
3239
+	 * generates the "Save" and "Save & Close" buttons for edit forms
3240
+	 *
3241
+	 * @param bool             $both     if true then both buttons will be generated.  If false then just the "Save &
3242
+	 *                                   Close" button.
3243
+	 * @param array            $text     if included, generator will use the given text for the buttons ( array([0] =>
3244
+	 *                                   'Save', [1] => 'save & close')
3245
+	 * @param array            $actions  if included allows us to set the actions that each button will carry out (i.e.
3246
+	 *                                   via the "name" value in the button).  We can also use this to just dump
3247
+	 *                                   default actions by submitting some other value.
3248
+	 * @param bool|string|null $referrer if false then we just do the default action on save and close.  Other wise it
3249
+	 *                                   will use the $referrer string. IF null, then we don't do ANYTHING on save and
3250
+	 *                                   close (normal form handling).
3251
+	 */
3252
+	protected function _set_save_buttons($both = true, $text = [], $actions = [], $referrer = null)
3253
+	{
3254
+		// make sure $text and $actions are in an array
3255
+		$text          = (array) $text;
3256
+		$actions       = (array) $actions;
3257
+		$referrer_url  = ! empty($referrer) ? $referrer : $this->request->getServerParam('REQUEST_URI');
3258
+		$button_text   = ! empty($text)
3259
+			? $text
3260
+			: [
3261
+				esc_html__('Save', 'event_espresso'),
3262
+				esc_html__('Save and Close', 'event_espresso'),
3263
+			];
3264
+		$default_names = ['save', 'save_and_close'];
3265
+		$buttons = '';
3266
+		foreach ($button_text as $key => $button) {
3267
+			$ref     = $default_names[ $key ];
3268
+			$name    = ! empty($actions) ? $actions[ $key ] : $ref;
3269
+			$buttons .= '<input type="submit" class="button button--primary ' . $ref . '" '
3270
+						. 'value="' . $button . '" name="' . $name . '" '
3271
+						. 'id="' . $this->_current_view . '_' . $ref . '" />';
3272
+			if (! $both) {
3273
+				break;
3274
+			}
3275
+		}
3276
+		// add in a hidden index for the current page (so save and close redirects properly)
3277
+		$buttons .= '<input type="hidden" id="save_and_close_referrer" name="save_and_close_referrer" value="'
3278
+				   . $referrer_url
3279
+				   . '" />';
3280
+		$this->_template_args['save_buttons'] = $buttons;
3281
+	}
3282
+
3283
+
3284
+	/**
3285
+	 * Wrapper for the protected function.  Allows plugins/addons to call this to set the form tags.
3286
+	 *
3287
+	 * @param string $route
3288
+	 * @param array  $additional_hidden_fields
3289
+	 * @see   $this->_set_add_edit_form_tags() for details on params
3290
+	 * @since 4.6.0
3291
+	 */
3292
+	public function set_add_edit_form_tags($route = '', $additional_hidden_fields = [])
3293
+	{
3294
+		$this->_set_add_edit_form_tags($route, $additional_hidden_fields);
3295
+	}
3296
+
3297
+
3298
+	/**
3299
+	 * set form open and close tags on add/edit pages.
3300
+	 *
3301
+	 * @param string $route                    the route you want the form to direct to
3302
+	 * @param array  $additional_hidden_fields any additional hidden fields required in the form header
3303
+	 * @return void
3304
+	 */
3305
+	protected function _set_add_edit_form_tags($route = '', $additional_hidden_fields = [])
3306
+	{
3307
+		if (empty($route)) {
3308
+			$user_msg = esc_html__(
3309
+				'An error occurred. No action was set for this page\'s form.',
3310
+				'event_espresso'
3311
+			);
3312
+			$dev_msg  = $user_msg . "\n"
3313
+						. sprintf(
3314
+							esc_html__('The $route argument is required for the %s->%s method.', 'event_espresso'),
3315
+							__FUNCTION__,
3316
+							__CLASS__
3317
+						);
3318
+			EE_Error::add_error($user_msg . '||' . $dev_msg, __FILE__, __FUNCTION__, __LINE__);
3319
+		}
3320
+		// open form
3321
+		$action = $this->_admin_base_url;
3322
+		$this->_template_args['before_admin_page_content'] = "
3323 3323
             <form name='form' method='post' action='{$action}' id='{$route}_event_form' class='ee-admin-page-form' >
3324 3324
             ";
3325
-        // add nonce
3326
-        $nonce                                             =
3327
-            wp_nonce_field($route . '_nonce', $route . '_nonce', false, false);
3328
-        $this->_template_args['before_admin_page_content'] .= "\n\t" . $nonce;
3329
-        // add REQUIRED form action
3330
-        $hidden_fields = [
3331
-            'action' => ['type' => 'hidden', 'value' => $route],
3332
-        ];
3333
-        // merge arrays
3334
-        $hidden_fields = is_array($additional_hidden_fields)
3335
-            ? array_merge($hidden_fields, $additional_hidden_fields)
3336
-            : $hidden_fields;
3337
-        // generate form fields
3338
-        $form_fields = $this->_generate_admin_form_fields($hidden_fields, 'array');
3339
-        // add fields to form
3340
-        foreach ((array) $form_fields as $form_field) {
3341
-            $this->_template_args['before_admin_page_content'] .= "\n\t" . $form_field['field'];
3342
-        }
3343
-        // close form
3344
-        $this->_template_args['after_admin_page_content'] = '</form>';
3345
-    }
3346
-
3347
-
3348
-    /**
3349
-     * Public Wrapper for _redirect_after_action() method since its
3350
-     * discovered it would be useful for external code to have access.
3351
-     *
3352
-     * @param bool   $success
3353
-     * @param string $what
3354
-     * @param string $action_desc
3355
-     * @param array  $query_args
3356
-     * @param bool   $override_overwrite
3357
-     * @throws EE_Error
3358
-     * @see   EE_Admin_Page::_redirect_after_action() for params.
3359
-     * @since 4.5.0
3360
-     */
3361
-    public function redirect_after_action(
3362
-        $success = false,
3363
-        $what = 'item',
3364
-        $action_desc = 'processed',
3365
-        $query_args = [],
3366
-        $override_overwrite = false
3367
-    ) {
3368
-        $this->_redirect_after_action(
3369
-            $success,
3370
-            $what,
3371
-            $action_desc,
3372
-            $query_args,
3373
-            $override_overwrite
3374
-        );
3375
-    }
3376
-
3377
-
3378
-    /**
3379
-     * Helper method for merging existing request data with the returned redirect url.
3380
-     *
3381
-     * This is typically used for redirects after an action so that if the original view was a filtered view those
3382
-     * filters are still applied.
3383
-     *
3384
-     * @param array $new_route_data
3385
-     * @return array
3386
-     */
3387
-    protected function mergeExistingRequestParamsWithRedirectArgs(array $new_route_data)
3388
-    {
3389
-        foreach ($this->request->requestParams() as $ref => $value) {
3390
-            // unset nonces
3391
-            if (strpos($ref, 'nonce') !== false) {
3392
-                $this->request->unSetRequestParam($ref);
3393
-                continue;
3394
-            }
3395
-            // urlencode values.
3396
-            $value = is_array($value) ? array_map('urlencode', $value) : urlencode($value);
3397
-            $this->request->setRequestParam($ref, $value);
3398
-        }
3399
-        return array_merge($this->request->requestParams(), $new_route_data);
3400
-    }
3401
-
3402
-
3403
-    /**
3404
-     * @param int|float|string $success      - whether success was for two or more records, or just one, or none
3405
-     * @param string           $what         - what the action was performed on
3406
-     * @param string           $action_desc  - what was done ie: updated, deleted, etc
3407
-     * @param array $query_args              - an array of query_args to be added to the URL to redirect to
3408
-     * @param BOOL $override_overwrite       - by default all EE_Error::success messages are overwritten,
3409
-     *                                         this allows you to override this so that they show.
3410
-     * @return void
3411
-     * @throws EE_Error
3412
-     * @throws InvalidArgumentException
3413
-     * @throws InvalidDataTypeException
3414
-     * @throws InvalidInterfaceException
3415
-     */
3416
-    protected function _redirect_after_action(
3417
-        $success = 0,
3418
-        string $what = 'item',
3419
-        string $action_desc = 'processed',
3420
-        array $query_args = [],
3421
-        bool $override_overwrite = false
3422
-    ) {
3423
-        do_action('AHEE_log', __FILE__, __FUNCTION__, '');
3424
-        $notices      = EE_Error::get_notices(false);
3425
-        // overwrite default success messages //BUT ONLY if overwrite not overridden
3426
-        if (! $override_overwrite || ! empty($notices['errors'])) {
3427
-            EE_Error::overwrite_success();
3428
-        }
3429
-        if (! $override_overwrite && ! empty($what) && ! empty($action_desc) && empty($notices['errors'])) {
3430
-            // how many records affected ? more than one record ? or just one ?
3431
-            EE_Error::add_success(
3432
-                sprintf(
3433
-                    esc_html(
3434
-                        _n(
3435
-                            'The "%1$s" has been successfully %2$s.',
3436
-                            'The "%1$s" have been successfully %2$s.',
3437
-                            $success,
3438
-                            'event_espresso'
3439
-                        )
3440
-                    ),
3441
-                    $what,
3442
-                    $action_desc
3443
-                ),
3444
-                __FILE__,
3445
-                __FUNCTION__,
3446
-                __LINE__
3447
-            );
3448
-        }
3449
-        // check that $query_args isn't something crazy
3450
-        if (! is_array($query_args)) {
3451
-            $query_args = [];
3452
-        }
3453
-        /**
3454
-         * Allow injecting actions before the query_args are modified for possible different
3455
-         * redirections on save and close actions
3456
-         *
3457
-         * @param array $query_args       The original query_args array coming into the
3458
-         *                                method.
3459
-         * @since 4.2.0
3460
-         */
3461
-        do_action(
3462
-            "AHEE__{$this->class_name}___redirect_after_action__before_redirect_modification_{$this->_req_action}",
3463
-            $query_args
3464
-        );
3465
-        // set redirect url.
3466
-        // Note if there is a "page" index in the $query_args then we go with vanilla admin.php route,
3467
-        // otherwise we go with whatever is set as the _admin_base_url
3468
-        $redirect_url = isset($query_args['page']) ? admin_url('admin.php') : $this->_admin_base_url;
3469
-        // calculate where we're going (if we have a "save and close" button pushed)
3470
-        if (
3471
-            $this->request->requestParamIsSet('save_and_close')
3472
-            && $this->request->requestParamIsSet('save_and_close_referrer')
3473
-        ) {
3474
-            // even though we have the save_and_close referrer, we need to parse the url for the action in order to generate a nonce
3475
-            $parsed_url = parse_url($this->request->getRequestParam('save_and_close_referrer', '', 'url'));
3476
-            // regenerate query args array from referrer URL
3477
-            parse_str($parsed_url['query'], $query_args);
3478
-            // correct page and action will be in the query args now
3479
-            $redirect_url = admin_url('admin.php');
3480
-        }
3481
-        // merge any default query_args set in _default_route_query_args property
3482
-        if (! empty($this->_default_route_query_args) && ! $this->_is_UI_request) {
3483
-            $args_to_merge = [];
3484
-            foreach ($this->_default_route_query_args as $query_param => $query_value) {
3485
-                // is there a wp_referer array in our _default_route_query_args property?
3486
-                if ($query_param === 'wp_referer') {
3487
-                    $query_value = (array) $query_value;
3488
-                    foreach ($query_value as $reference => $value) {
3489
-                        if (strpos($reference, 'nonce') !== false) {
3490
-                            continue;
3491
-                        }
3492
-                        // finally we will override any arguments in the referer with
3493
-                        // what might be set on the _default_route_query_args array.
3494
-                        if (isset($this->_default_route_query_args[ $reference ])) {
3495
-                            $args_to_merge[ $reference ] = urlencode($this->_default_route_query_args[ $reference ]);
3496
-                        } else {
3497
-                            $args_to_merge[ $reference ] = urlencode($value);
3498
-                        }
3499
-                    }
3500
-                    continue;
3501
-                }
3502
-                $args_to_merge[ $query_param ] = $query_value;
3503
-            }
3504
-            // now let's merge these arguments but override with what was specifically sent in to the
3505
-            // redirect.
3506
-            $query_args = array_merge($args_to_merge, $query_args);
3507
-        }
3508
-        $this->_process_notices($query_args);
3509
-        // generate redirect url
3510
-        // if redirecting to anything other than the main page, add a nonce
3511
-        if (isset($query_args['action'])) {
3512
-            // manually generate wp_nonce and merge that with the query vars
3513
-            // becuz the wp_nonce_url function wrecks havoc on some vars
3514
-            $query_args['_wpnonce'] = wp_create_nonce($query_args['action'] . '_nonce');
3515
-        }
3516
-        // we're adding some hooks and filters in here for processing any things just before redirects
3517
-        // (example: an admin page has done an insert or update and we want to run something after that).
3518
-        do_action('AHEE_redirect_' . $this->class_name . $this->_req_action, $query_args);
3519
-        $redirect_url = apply_filters(
3520
-            'FHEE_redirect_' . $this->class_name . $this->_req_action,
3521
-            EE_Admin_Page::add_query_args_and_nonce($query_args, $redirect_url),
3522
-            $query_args
3523
-        );
3524
-        // check if we're doing ajax.  If we are then lets just return the results and js can handle how it wants.
3525
-        if ($this->request->isAjax()) {
3526
-            $default_data                    = [
3527
-                'close'        => true,
3528
-                'redirect_url' => $redirect_url,
3529
-                'where'        => 'main',
3530
-                'what'         => 'append',
3531
-            ];
3532
-            $this->_template_args['success'] = $success;
3533
-            $this->_template_args['data']    = ! empty($this->_template_args['data']) ? array_merge(
3534
-                $default_data,
3535
-                $this->_template_args['data']
3536
-            ) : $default_data;
3537
-            $this->_return_json();
3538
-        }
3539
-        wp_safe_redirect($redirect_url);
3540
-        exit();
3541
-    }
3542
-
3543
-
3544
-    /**
3545
-     * process any notices before redirecting (or returning ajax request)
3546
-     * This method sets the $this->_template_args['notices'] attribute;
3547
-     *
3548
-     * @param array $query_args         any query args that need to be used for notice transient ('action')
3549
-     * @param bool  $skip_route_verify  This is typically used when we are processing notices REALLY early and
3550
-     *                                  page_routes haven't been defined yet.
3551
-     * @param bool  $sticky_notices     This is used to flag that regardless of whether this is doing_ajax or not, we
3552
-     *                                  still save a transient for the notice.
3553
-     * @return void
3554
-     * @throws EE_Error
3555
-     * @throws InvalidArgumentException
3556
-     * @throws InvalidDataTypeException
3557
-     * @throws InvalidInterfaceException
3558
-     */
3559
-    protected function _process_notices($query_args = [], $skip_route_verify = false, $sticky_notices = true)
3560
-    {
3561
-        // first let's set individual error properties if doing_ajax and the properties aren't already set.
3562
-        if ($this->request->isAjax()) {
3563
-            $notices = EE_Error::get_notices(false);
3564
-            if (empty($this->_template_args['success'])) {
3565
-                $this->_template_args['success'] = isset($notices['success']) ? $notices['success'] : false;
3566
-            }
3567
-            if (empty($this->_template_args['errors'])) {
3568
-                $this->_template_args['errors'] = isset($notices['errors']) ? $notices['errors'] : false;
3569
-            }
3570
-            if (empty($this->_template_args['attention'])) {
3571
-                $this->_template_args['attention'] = isset($notices['attention']) ? $notices['attention'] : false;
3572
-            }
3573
-        }
3574
-        $this->_template_args['notices'] = EE_Error::get_notices();
3575
-        // IF this isn't ajax we need to create a transient for the notices using the route (however, overridden if $sticky_notices == true)
3576
-        if (! $this->request->isAjax() || $sticky_notices) {
3577
-            $route = isset($query_args['action']) ? $query_args['action'] : 'default';
3578
-            $this->_add_transient(
3579
-                $route,
3580
-                $this->_template_args['notices'],
3581
-                true,
3582
-                $skip_route_verify
3583
-            );
3584
-        }
3585
-    }
3586
-
3587
-
3588
-    /**
3589
-     * get_action_link_or_button
3590
-     * returns the button html for adding, editing, or deleting an item (depending on given type)
3591
-     *
3592
-     * @param string $action        use this to indicate which action the url is generated with.
3593
-     * @param string $type          accepted strings must be defined in the $_labels['button'] array(as the key)
3594
-     *                              property.
3595
-     * @param array  $extra_request if the button requires extra params you can include them in $key=>$value pairs.
3596
-     * @param string $class         Use this to give the class for the button. Defaults to 'button--primary'
3597
-     * @param string $base_url      If this is not provided
3598
-     *                              the _admin_base_url will be used as the default for the button base_url.
3599
-     *                              Otherwise this value will be used.
3600
-     * @param bool   $exclude_nonce If true then no nonce will be in the generated button link.
3601
-     * @return string
3602
-     * @throws InvalidArgumentException
3603
-     * @throws InvalidInterfaceException
3604
-     * @throws InvalidDataTypeException
3605
-     * @throws EE_Error
3606
-     */
3607
-    public function get_action_link_or_button(
3608
-        $action,
3609
-        $type = 'add',
3610
-        $extra_request = [],
3611
-        $class = 'button--primary',
3612
-        $base_url = '',
3613
-        $exclude_nonce = false
3614
-    ) {
3615
-        // first let's validate the action (if $base_url is FALSE otherwise validation will happen further along)
3616
-        if (empty($base_url) && ! isset($this->_page_routes[ $action ])) {
3617
-            throw new EE_Error(
3618
-                sprintf(
3619
-                    esc_html__(
3620
-                        'There is no page route for given action for the button.  This action was given: %s',
3621
-                        'event_espresso'
3622
-                    ),
3623
-                    $action
3624
-                )
3625
-            );
3626
-        }
3627
-        if (! isset($this->_labels['buttons'][ $type ])) {
3628
-            throw new EE_Error(
3629
-                sprintf(
3630
-                    esc_html__(
3631
-                        'There is no label for the given button type (%s). Labels are set in the <code>_page_config</code> property.',
3632
-                        'event_espresso'
3633
-                    ),
3634
-                    $type
3635
-                )
3636
-            );
3637
-        }
3638
-        // finally check user access for this button.
3639
-        $has_access = $this->check_user_access($action, true);
3640
-        if (! $has_access) {
3641
-            return '';
3642
-        }
3643
-        $_base_url  = ! $base_url ? $this->_admin_base_url : $base_url;
3644
-        $query_args = [
3645
-            'action' => $action,
3646
-        ];
3647
-        // merge extra_request args but make sure our original action takes precedence and doesn't get overwritten.
3648
-        if (! empty($extra_request)) {
3649
-            $query_args = array_merge($extra_request, $query_args);
3650
-        }
3651
-        $url = EE_Admin_Page::add_query_args_and_nonce($query_args, $_base_url, false, $exclude_nonce);
3652
-        return EEH_Template::get_button_or_link($url, $this->_labels['buttons'][ $type ], $class);
3653
-    }
3654
-
3655
-
3656
-    /**
3657
-     * _per_page_screen_option
3658
-     * Utility function for adding in a per_page_option in the screen_options_dropdown.
3659
-     *
3660
-     * @return void
3661
-     * @throws InvalidArgumentException
3662
-     * @throws InvalidInterfaceException
3663
-     * @throws InvalidDataTypeException
3664
-     */
3665
-    protected function _per_page_screen_option()
3666
-    {
3667
-        $option = 'per_page';
3668
-        $args   = [
3669
-            'label'   => apply_filters(
3670
-                'FHEE__EE_Admin_Page___per_page_screen_options___label',
3671
-                $this->_admin_page_title,
3672
-                $this
3673
-            ),
3674
-            'default' => (int) apply_filters(
3675
-                'FHEE__EE_Admin_Page___per_page_screen_options__default',
3676
-                20
3677
-            ),
3678
-            'option'  => $this->_current_page . '_' . $this->_current_view . '_per_page',
3679
-        ];
3680
-        // ONLY add the screen option if the user has access to it.
3681
-        if ($this->check_user_access($this->_current_view, true)) {
3682
-            add_screen_option($option, $args);
3683
-        }
3684
-    }
3685
-
3686
-
3687
-    /**
3688
-     * set_per_page_screen_option
3689
-     * All this does is make sure that WordPress saves any per_page screen options (if set) for the current page.
3690
-     * we have to do this rather than running inside the 'set-screen-options' hook because it runs earlier than
3691
-     * admin_menu.
3692
-     *
3693
-     * @return void
3694
-     */
3695
-    private function _set_per_page_screen_options()
3696
-    {
3697
-        if ($this->request->requestParamIsSet('wp_screen_options')) {
3698
-            check_admin_referer('screen-options-nonce', 'screenoptionnonce');
3699
-            if (! $user = wp_get_current_user()) {
3700
-                return;
3701
-            }
3702
-            $option = $this->request->getRequestParam('wp_screen_options[option]', '', 'key');
3703
-            if (! $option) {
3704
-                return;
3705
-            }
3706
-            $value  = $this->request->getRequestParam('wp_screen_options[value]', 0, 'int');
3707
-            $map_option = $option;
3708
-            $option     = str_replace('-', '_', $option);
3709
-            switch ($map_option) {
3710
-                case $this->_current_page . '_' . $this->_current_view . '_per_page':
3711
-                    $max_value = apply_filters(
3712
-                        'FHEE__EE_Admin_Page___set_per_page_screen_options__max_value',
3713
-                        999,
3714
-                        $this->_current_page,
3715
-                        $this->_current_view
3716
-                    );
3717
-                    if ($value < 1) {
3718
-                        return;
3719
-                    }
3720
-                    $value = min($value, $max_value);
3721
-                    break;
3722
-                default:
3723
-                    $value = apply_filters(
3724
-                        'FHEE__EE_Admin_Page___set_per_page_screen_options__value',
3725
-                        false,
3726
-                        $option,
3727
-                        $value
3728
-                    );
3729
-                    if (false === $value) {
3730
-                        return;
3731
-                    }
3732
-                    break;
3733
-            }
3734
-            update_user_meta($user->ID, $option, $value);
3735
-            wp_safe_redirect(remove_query_arg(['pagenum', 'apage', 'paged'], wp_get_referer()));
3736
-            exit;
3737
-        }
3738
-    }
3739
-
3740
-
3741
-    /**
3742
-     * This just allows for setting the $_template_args property if it needs to be set outside the object
3743
-     *
3744
-     * @param array $data array that will be assigned to template args.
3745
-     */
3746
-    public function set_template_args($data)
3747
-    {
3748
-        $this->_template_args = array_merge($this->_template_args, (array) $data);
3749
-    }
3750
-
3751
-
3752
-    /**
3753
-     * This makes available the WP transient system for temporarily moving data between routes
3754
-     *
3755
-     * @param string $route             the route that should receive the transient
3756
-     * @param array  $data              the data that gets sent
3757
-     * @param bool   $notices           If this is for notices then we use this to indicate so, otherwise its just a
3758
-     *                                  normal route transient.
3759
-     * @param bool   $skip_route_verify Used to indicate we want to skip route verification.  This is usually ONLY used
3760
-     *                                  when we are adding a transient before page_routes have been defined.
3761
-     * @return void
3762
-     * @throws EE_Error
3763
-     */
3764
-    protected function _add_transient($route, $data, $notices = false, $skip_route_verify = false)
3765
-    {
3766
-        $user_id = get_current_user_id();
3767
-        if (! $skip_route_verify) {
3768
-            $this->_verify_route($route);
3769
-        }
3770
-        // now let's set the string for what kind of transient we're setting
3771
-        $transient = $notices
3772
-            ? 'ee_rte_n_tx_' . $route . '_' . $user_id
3773
-            : 'rte_tx_' . $route . '_' . $user_id;
3774
-        $data      = $notices ? ['notices' => $data] : $data;
3775
-        // is there already a transient for this route?  If there is then let's ADD to that transient
3776
-        $existing = is_multisite() && is_network_admin()
3777
-            ? get_site_transient($transient)
3778
-            : get_transient($transient);
3779
-        if ($existing) {
3780
-            $data = array_merge((array) $data, (array) $existing);
3781
-        }
3782
-        if (is_multisite() && is_network_admin()) {
3783
-            set_site_transient($transient, $data, 8);
3784
-        } else {
3785
-            set_transient($transient, $data, 8);
3786
-        }
3787
-    }
3788
-
3789
-
3790
-    /**
3791
-     * this retrieves the temporary transient that has been set for moving data between routes.
3792
-     *
3793
-     * @param bool   $notices true we get notices transient. False we just return normal route transient
3794
-     * @param string $route
3795
-     * @return mixed data
3796
-     */
3797
-    protected function _get_transient($notices = false, $route = '')
3798
-    {
3799
-        $user_id   = get_current_user_id();
3800
-        $route     = ! $route ? $this->_req_action : $route;
3801
-        $transient = $notices
3802
-            ? 'ee_rte_n_tx_' . $route . '_' . $user_id
3803
-            : 'rte_tx_' . $route . '_' . $user_id;
3804
-        $data      = is_multisite() && is_network_admin()
3805
-            ? get_site_transient($transient)
3806
-            : get_transient($transient);
3807
-        // delete transient after retrieval (just in case it hasn't expired);
3808
-        if (is_multisite() && is_network_admin()) {
3809
-            delete_site_transient($transient);
3810
-        } else {
3811
-            delete_transient($transient);
3812
-        }
3813
-        return $notices && isset($data['notices']) ? $data['notices'] : $data;
3814
-    }
3815
-
3816
-
3817
-    /**
3818
-     * The purpose of this method is just to run garbage collection on any EE transients that might have expired but
3819
-     * would not be called later. This will be assigned to run on a specific EE Admin page. (place the method in the
3820
-     * default route callback on the EE_Admin page you want it run.)
3821
-     *
3822
-     * @return void
3823
-     */
3824
-    protected function _transient_garbage_collection()
3825
-    {
3826
-        global $wpdb;
3827
-        // retrieve all existing transients
3828
-        $query =
3829
-            "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE '%rte_tx_%' OR option_name LIKE '%rte_n_tx_%'";
3830
-        if ($results = $wpdb->get_results($query)) {
3831
-            foreach ($results as $result) {
3832
-                $transient = str_replace('_transient_', '', $result->option_name);
3833
-                get_transient($transient);
3834
-                if (is_multisite() && is_network_admin()) {
3835
-                    get_site_transient($transient);
3836
-                }
3837
-            }
3838
-        }
3839
-    }
3840
-
3841
-
3842
-    /**
3843
-     * get_view
3844
-     *
3845
-     * @return string content of _view property
3846
-     */
3847
-    public function get_view()
3848
-    {
3849
-        return $this->_view;
3850
-    }
3851
-
3852
-
3853
-    /**
3854
-     * getter for the protected $_views property
3855
-     *
3856
-     * @return array
3857
-     */
3858
-    public function get_views()
3859
-    {
3860
-        return $this->_views;
3861
-    }
3862
-
3863
-
3864
-    /**
3865
-     * get_current_page
3866
-     *
3867
-     * @return string _current_page property value
3868
-     */
3869
-    public function get_current_page()
3870
-    {
3871
-        return $this->_current_page;
3872
-    }
3873
-
3874
-
3875
-    /**
3876
-     * get_current_view
3877
-     *
3878
-     * @return string _current_view property value
3879
-     */
3880
-    public function get_current_view()
3881
-    {
3882
-        return $this->_current_view;
3883
-    }
3884
-
3885
-
3886
-    /**
3887
-     * get_current_screen
3888
-     *
3889
-     * @return object The current WP_Screen object
3890
-     */
3891
-    public function get_current_screen()
3892
-    {
3893
-        return $this->_current_screen;
3894
-    }
3895
-
3896
-
3897
-    /**
3898
-     * get_current_page_view_url
3899
-     *
3900
-     * @return string This returns the url for the current_page_view.
3901
-     */
3902
-    public function get_current_page_view_url()
3903
-    {
3904
-        return $this->_current_page_view_url;
3905
-    }
3906
-
3907
-
3908
-    /**
3909
-     * just returns the Request
3910
-     *
3911
-     * @return RequestInterface
3912
-     */
3913
-    public function get_request()
3914
-    {
3915
-        return $this->request;
3916
-    }
3917
-
3918
-
3919
-    /**
3920
-     * just returns the _req_data property
3921
-     *
3922
-     * @return array
3923
-     */
3924
-    public function get_request_data()
3925
-    {
3926
-        return $this->request->requestParams();
3927
-    }
3928
-
3929
-
3930
-    /**
3931
-     * returns the _req_data protected property
3932
-     *
3933
-     * @return string
3934
-     */
3935
-    public function get_req_action()
3936
-    {
3937
-        return $this->_req_action;
3938
-    }
3939
-
3940
-
3941
-    /**
3942
-     * @return bool  value of $_is_caf property
3943
-     */
3944
-    public function is_caf()
3945
-    {
3946
-        return $this->_is_caf;
3947
-    }
3948
-
3949
-
3950
-    /**
3951
-     * @return mixed
3952
-     */
3953
-    public function default_espresso_metaboxes()
3954
-    {
3955
-        return $this->_default_espresso_metaboxes;
3956
-    }
3957
-
3958
-
3959
-    /**
3960
-     * @return mixed
3961
-     */
3962
-    public function admin_base_url()
3963
-    {
3964
-        return $this->_admin_base_url;
3965
-    }
3966
-
3967
-
3968
-    /**
3969
-     * @return mixed
3970
-     */
3971
-    public function wp_page_slug()
3972
-    {
3973
-        return $this->_wp_page_slug;
3974
-    }
3975
-
3976
-
3977
-    /**
3978
-     * updates  espresso configuration settings
3979
-     *
3980
-     * @param string                   $tab
3981
-     * @param EE_Config_Base|EE_Config $config
3982
-     * @param string                   $file file where error occurred
3983
-     * @param string                   $func function  where error occurred
3984
-     * @param string                   $line line no where error occurred
3985
-     * @return boolean
3986
-     */
3987
-    protected function _update_espresso_configuration($tab, $config, $file = '', $func = '', $line = '')
3988
-    {
3989
-        // remove any options that are NOT going to be saved with the config settings.
3990
-        if (isset($config->core->ee_ueip_optin)) {
3991
-            // TODO: remove the following two lines and make sure values are migrated from 3.1
3992
-            update_option('ee_ueip_optin', $config->core->ee_ueip_optin);
3993
-            update_option('ee_ueip_has_notified', true);
3994
-        }
3995
-        // and save it (note we're also doing the network save here)
3996
-        $net_saved    = ! is_main_site() || EE_Network_Config::instance()->update_config(false, false);
3997
-        $config_saved = EE_Config::instance()->update_espresso_config(false, false);
3998
-        if ($config_saved && $net_saved) {
3999
-            EE_Error::add_success(sprintf(esc_html__('"%s" have been successfully updated.', 'event_espresso'), $tab));
4000
-            return true;
4001
-        }
4002
-        EE_Error::add_error(sprintf(esc_html__('The "%s" were not updated.', 'event_espresso'), $tab), $file, $func, $line);
4003
-        return false;
4004
-    }
4005
-
4006
-
4007
-    /**
4008
-     * Returns an array to be used for EE_FOrm_Fields.helper.php's select_input as the $values argument.
4009
-     *
4010
-     * @return array
4011
-     */
4012
-    public function get_yes_no_values()
4013
-    {
4014
-        return $this->_yes_no_values;
4015
-    }
4016
-
4017
-
4018
-    /**
4019
-     * @return string
4020
-     * @throws ReflectionException
4021
-     * @since $VID:$
4022
-     */
4023
-    protected function _get_dir()
4024
-    {
4025
-        $reflector = new ReflectionClass($this->class_name);
4026
-        return dirname($reflector->getFileName());
4027
-    }
4028
-
4029
-
4030
-    /**
4031
-     * A helper for getting a "next link".
4032
-     *
4033
-     * @param string $url   The url to link to
4034
-     * @param string $class The class to use.
4035
-     * @return string
4036
-     */
4037
-    protected function _next_link($url, $class = 'dashicons dashicons-arrow-right')
4038
-    {
4039
-        return '<a class="' . $class . '" href="' . $url . '"></a>';
4040
-    }
4041
-
4042
-
4043
-    /**
4044
-     * A helper for getting a "previous link".
4045
-     *
4046
-     * @param string $url   The url to link to
4047
-     * @param string $class The class to use.
4048
-     * @return string
4049
-     */
4050
-    protected function _previous_link($url, $class = 'dashicons dashicons-arrow-left')
4051
-    {
4052
-        return '<a class="' . $class . '" href="' . $url . '"></a>';
4053
-    }
4054
-
4055
-
4056
-
4057
-
4058
-
4059
-
4060
-
4061
-    // below are some messages related methods that should be available across the EE_Admin system.  Note, these methods are NOT page specific
4062
-
4063
-
4064
-    /**
4065
-     * This processes an request to resend a registration and assumes we have a _REG_ID for doing so. So if the caller
4066
-     * 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
4067
-     * _req_data array.
4068
-     *
4069
-     * @return bool success/fail
4070
-     * @throws EE_Error
4071
-     * @throws InvalidArgumentException
4072
-     * @throws ReflectionException
4073
-     * @throws InvalidDataTypeException
4074
-     * @throws InvalidInterfaceException
4075
-     */
4076
-    protected function _process_resend_registration()
4077
-    {
4078
-        $this->_template_args['success'] = EED_Messages::process_resend($this->_req_data);
4079
-        do_action(
4080
-            'AHEE__EE_Admin_Page___process_resend_registration',
4081
-            $this->_template_args['success'],
4082
-            $this->request->requestParams()
4083
-        );
4084
-        return $this->_template_args['success'];
4085
-    }
4086
-
4087
-
4088
-    /**
4089
-     * This automatically processes any payment message notifications when manual payment has been applied.
4090
-     *
4091
-     * @param EE_Payment $payment
4092
-     * @return bool success/fail
4093
-     */
4094
-    protected function _process_payment_notification(EE_Payment $payment)
4095
-    {
4096
-        add_filter('FHEE__EE_Payment_Processor__process_registration_payments__display_notifications', '__return_true');
4097
-        do_action('AHEE__EE_Admin_Page___process_admin_payment_notification', $payment);
4098
-        $this->_template_args['success'] = apply_filters(
4099
-            'FHEE__EE_Admin_Page___process_admin_payment_notification__success',
4100
-            false,
4101
-            $payment
4102
-        );
4103
-        return $this->_template_args['success'];
4104
-    }
4105
-
4106
-
4107
-    /**
4108
-     * @param EEM_Base      $entity_model
4109
-     * @param string        $entity_PK_name name of the primary key field used as a request param, ie: id, ID, etc
4110
-     * @param string        $action         one of the EE_Admin_List_Table::ACTION_* constants: delete, restore, trash
4111
-     * @param string        $delete_column  name of the field that denotes whether entity is trashed
4112
-     * @param callable|null $callback       called after entity is trashed, restored, or deleted
4113
-     * @return int|float
4114
-     * @throws EE_Error
4115
-     */
4116
-    protected function trashRestoreDeleteEntities(
4117
-        EEM_Base $entity_model,
4118
-        string $entity_PK_name,
4119
-        string $action = EE_Admin_List_Table::ACTION_DELETE,
4120
-        string $delete_column = '',
4121
-        callable $callback = null
4122
-    ) {
4123
-        $entity_PK      = $entity_model->get_primary_key_field();
4124
-        $entity_PK_name = $entity_PK_name ?: $entity_PK->get_name();
4125
-        $entity_PK_type = $this->resolveEntityFieldDataType($entity_PK);
4126
-        // grab ID if deleting a single entity
4127
-        if ($this->request->requestParamIsSet($entity_PK_name)) {
4128
-            $ID = $this->request->getRequestParam($entity_PK_name, 0, $entity_PK_type);
4129
-            return $this->trashRestoreDeleteEntity($entity_model, $ID, $action, $delete_column, $callback) ? 1 : 0;
4130
-        }
4131
-        // or grab checkbox array if bulk deleting
4132
-        $checkboxes = $this->request->getRequestParam('checkbox', [], $entity_PK_type, true);
4133
-        if (empty($checkboxes)) {
4134
-            return 0;
4135
-        }
4136
-        $success = 0;
4137
-        $IDs     = array_keys($checkboxes);
4138
-        // cycle thru bulk action checkboxes
4139
-        foreach ($IDs as $ID) {
4140
-            // increment $success
4141
-            if ($this->trashRestoreDeleteEntity($entity_model, $ID, $action, $delete_column, $callback)) {
4142
-                $success++;
4143
-            }
4144
-        }
4145
-        $count = (int) count($checkboxes);
4146
-        // if multiple entities were deleted successfully, then $deleted will be full count of deletions,
4147
-        // otherwise it will be a fraction of ( actual deletions / total entities to be deleted )
4148
-        return $success === $count ? $count : $success / $count;
4149
-    }
4150
-
4151
-
4152
-    /**
4153
-     * @param EE_Primary_Key_Field_Base $entity_PK
4154
-     * @return string
4155
-     * @throws EE_Error
4156
-     * @since   4.10.30.p
4157
-     */
4158
-    private function resolveEntityFieldDataType(EE_Primary_Key_Field_Base $entity_PK): string
4159
-    {
4160
-        $entity_PK_type = $entity_PK->getSchemaType();
4161
-        switch ($entity_PK_type) {
4162
-            case 'boolean':
4163
-                return 'bool';
4164
-            case 'integer':
4165
-                return 'int';
4166
-            case 'number':
4167
-                return 'float';
4168
-            case 'string':
4169
-                return 'string';
4170
-        }
4171
-        throw new RuntimeException(
4172
-            sprintf(
4173
-                esc_html__(
4174
-                    '"%1$s" is an invalid schema type for the %2$s primary key.',
4175
-                    'event_espresso'
4176
-                ),
4177
-                $entity_PK_type,
4178
-                $entity_PK->get_name()
4179
-            )
4180
-        );
4181
-    }
4182
-
4183
-
4184
-    /**
4185
-     * @param EEM_Base      $entity_model
4186
-     * @param int|string    $entity_ID
4187
-     * @param string        $action        one of the EE_Admin_List_Table::ACTION_* constants: delete, restore, trash
4188
-     * @param string        $delete_column name of the field that denotes whether entity is trashed
4189
-     * @param callable|null $callback      called after entity is trashed, restored, or deleted
4190
-     * @return bool
4191
-     */
4192
-    protected function trashRestoreDeleteEntity(
4193
-        EEM_Base $entity_model,
4194
-        $entity_ID,
4195
-        string $action,
4196
-        string $delete_column,
4197
-        ?callable $callback = null
4198
-    ): bool {
4199
-        $entity_ID = absint($entity_ID);
4200
-        if (! $entity_ID) {
4201
-            $this->trashRestoreDeleteError($action, $entity_model);
4202
-        }
4203
-        $result = 0;
4204
-        try {
4205
-            switch ($action) {
4206
-                case EE_Admin_List_Table::ACTION_DELETE:
4207
-                    $result = (bool) $entity_model->delete_permanently_by_ID($entity_ID);
4208
-                    break;
4209
-                case EE_Admin_List_Table::ACTION_RESTORE:
4210
-                    $this->validateDeleteColumn($entity_model, $delete_column);
4211
-                    $result = $entity_model->update_by_ID([$delete_column => 0], $entity_ID);
4212
-                    break;
4213
-                case EE_Admin_List_Table::ACTION_TRASH:
4214
-                    $this->validateDeleteColumn($entity_model, $delete_column);
4215
-                    $result = $entity_model->update_by_ID([$delete_column => 1], $entity_ID);
4216
-                    break;
4217
-            }
4218
-        } catch (Exception $exception) {
4219
-            $this->trashRestoreDeleteError($action, $entity_model, $exception);
4220
-        }
4221
-        if (is_callable($callback)) {
4222
-            call_user_func_array($callback, [$entity_model, $entity_ID, $action, $result, $delete_column]);
4223
-        }
4224
-        return $result;
4225
-    }
4226
-
4227
-
4228
-    /**
4229
-     * @param EEM_Base $entity_model
4230
-     * @param string   $delete_column
4231
-     * @since 4.10.30.p
4232
-     */
4233
-    private function validateDeleteColumn(EEM_Base $entity_model, string $delete_column)
4234
-    {
4235
-        if (empty($delete_column)) {
4236
-            throw new DomainException(
4237
-                sprintf(
4238
-                    esc_html__(
4239
-                        'You need to specify the name of the "delete column" on the %2$s model, in order to trash or restore an entity.',
4240
-                        'event_espresso'
4241
-                    ),
4242
-                    $entity_model->get_this_model_name()
4243
-                )
4244
-            );
4245
-        }
4246
-        if (! $entity_model->has_field($delete_column)) {
4247
-            throw new DomainException(
4248
-                sprintf(
4249
-                    esc_html__(
4250
-                        'The %1$s field does not exist on the %2$s model.',
4251
-                        'event_espresso'
4252
-                    ),
4253
-                    $delete_column,
4254
-                    $entity_model->get_this_model_name()
4255
-                )
4256
-            );
4257
-        }
4258
-    }
4259
-
4260
-
4261
-    /**
4262
-     * @param EEM_Base       $entity_model
4263
-     * @param Exception|null $exception
4264
-     * @param string         $action
4265
-     * @since 4.10.30.p
4266
-     */
4267
-    private function trashRestoreDeleteError(string $action, EEM_Base $entity_model, ?Exception $exception = null)
4268
-    {
4269
-        if ($exception instanceof Exception) {
4270
-            throw new RuntimeException(
4271
-                sprintf(
4272
-                    esc_html__(
4273
-                        'Could not %1$s the %2$s because the following error occurred: %3$s',
4274
-                        'event_espresso'
4275
-                    ),
4276
-                    $action,
4277
-                    $entity_model->get_this_model_name(),
4278
-                    $exception->getMessage()
4279
-                )
4280
-            );
4281
-        }
4282
-        throw new RuntimeException(
4283
-            sprintf(
4284
-                esc_html__(
4285
-                    'Could not %1$s the %2$s because an invalid ID was received.',
4286
-                    'event_espresso'
4287
-                ),
4288
-                $action,
4289
-                $entity_model->get_this_model_name()
4290
-            )
4291
-        );
4292
-    }
3325
+		// add nonce
3326
+		$nonce                                             =
3327
+			wp_nonce_field($route . '_nonce', $route . '_nonce', false, false);
3328
+		$this->_template_args['before_admin_page_content'] .= "\n\t" . $nonce;
3329
+		// add REQUIRED form action
3330
+		$hidden_fields = [
3331
+			'action' => ['type' => 'hidden', 'value' => $route],
3332
+		];
3333
+		// merge arrays
3334
+		$hidden_fields = is_array($additional_hidden_fields)
3335
+			? array_merge($hidden_fields, $additional_hidden_fields)
3336
+			: $hidden_fields;
3337
+		// generate form fields
3338
+		$form_fields = $this->_generate_admin_form_fields($hidden_fields, 'array');
3339
+		// add fields to form
3340
+		foreach ((array) $form_fields as $form_field) {
3341
+			$this->_template_args['before_admin_page_content'] .= "\n\t" . $form_field['field'];
3342
+		}
3343
+		// close form
3344
+		$this->_template_args['after_admin_page_content'] = '</form>';
3345
+	}
3346
+
3347
+
3348
+	/**
3349
+	 * Public Wrapper for _redirect_after_action() method since its
3350
+	 * discovered it would be useful for external code to have access.
3351
+	 *
3352
+	 * @param bool   $success
3353
+	 * @param string $what
3354
+	 * @param string $action_desc
3355
+	 * @param array  $query_args
3356
+	 * @param bool   $override_overwrite
3357
+	 * @throws EE_Error
3358
+	 * @see   EE_Admin_Page::_redirect_after_action() for params.
3359
+	 * @since 4.5.0
3360
+	 */
3361
+	public function redirect_after_action(
3362
+		$success = false,
3363
+		$what = 'item',
3364
+		$action_desc = 'processed',
3365
+		$query_args = [],
3366
+		$override_overwrite = false
3367
+	) {
3368
+		$this->_redirect_after_action(
3369
+			$success,
3370
+			$what,
3371
+			$action_desc,
3372
+			$query_args,
3373
+			$override_overwrite
3374
+		);
3375
+	}
3376
+
3377
+
3378
+	/**
3379
+	 * Helper method for merging existing request data with the returned redirect url.
3380
+	 *
3381
+	 * This is typically used for redirects after an action so that if the original view was a filtered view those
3382
+	 * filters are still applied.
3383
+	 *
3384
+	 * @param array $new_route_data
3385
+	 * @return array
3386
+	 */
3387
+	protected function mergeExistingRequestParamsWithRedirectArgs(array $new_route_data)
3388
+	{
3389
+		foreach ($this->request->requestParams() as $ref => $value) {
3390
+			// unset nonces
3391
+			if (strpos($ref, 'nonce') !== false) {
3392
+				$this->request->unSetRequestParam($ref);
3393
+				continue;
3394
+			}
3395
+			// urlencode values.
3396
+			$value = is_array($value) ? array_map('urlencode', $value) : urlencode($value);
3397
+			$this->request->setRequestParam($ref, $value);
3398
+		}
3399
+		return array_merge($this->request->requestParams(), $new_route_data);
3400
+	}
3401
+
3402
+
3403
+	/**
3404
+	 * @param int|float|string $success      - whether success was for two or more records, or just one, or none
3405
+	 * @param string           $what         - what the action was performed on
3406
+	 * @param string           $action_desc  - what was done ie: updated, deleted, etc
3407
+	 * @param array $query_args              - an array of query_args to be added to the URL to redirect to
3408
+	 * @param BOOL $override_overwrite       - by default all EE_Error::success messages are overwritten,
3409
+	 *                                         this allows you to override this so that they show.
3410
+	 * @return void
3411
+	 * @throws EE_Error
3412
+	 * @throws InvalidArgumentException
3413
+	 * @throws InvalidDataTypeException
3414
+	 * @throws InvalidInterfaceException
3415
+	 */
3416
+	protected function _redirect_after_action(
3417
+		$success = 0,
3418
+		string $what = 'item',
3419
+		string $action_desc = 'processed',
3420
+		array $query_args = [],
3421
+		bool $override_overwrite = false
3422
+	) {
3423
+		do_action('AHEE_log', __FILE__, __FUNCTION__, '');
3424
+		$notices      = EE_Error::get_notices(false);
3425
+		// overwrite default success messages //BUT ONLY if overwrite not overridden
3426
+		if (! $override_overwrite || ! empty($notices['errors'])) {
3427
+			EE_Error::overwrite_success();
3428
+		}
3429
+		if (! $override_overwrite && ! empty($what) && ! empty($action_desc) && empty($notices['errors'])) {
3430
+			// how many records affected ? more than one record ? or just one ?
3431
+			EE_Error::add_success(
3432
+				sprintf(
3433
+					esc_html(
3434
+						_n(
3435
+							'The "%1$s" has been successfully %2$s.',
3436
+							'The "%1$s" have been successfully %2$s.',
3437
+							$success,
3438
+							'event_espresso'
3439
+						)
3440
+					),
3441
+					$what,
3442
+					$action_desc
3443
+				),
3444
+				__FILE__,
3445
+				__FUNCTION__,
3446
+				__LINE__
3447
+			);
3448
+		}
3449
+		// check that $query_args isn't something crazy
3450
+		if (! is_array($query_args)) {
3451
+			$query_args = [];
3452
+		}
3453
+		/**
3454
+		 * Allow injecting actions before the query_args are modified for possible different
3455
+		 * redirections on save and close actions
3456
+		 *
3457
+		 * @param array $query_args       The original query_args array coming into the
3458
+		 *                                method.
3459
+		 * @since 4.2.0
3460
+		 */
3461
+		do_action(
3462
+			"AHEE__{$this->class_name}___redirect_after_action__before_redirect_modification_{$this->_req_action}",
3463
+			$query_args
3464
+		);
3465
+		// set redirect url.
3466
+		// Note if there is a "page" index in the $query_args then we go with vanilla admin.php route,
3467
+		// otherwise we go with whatever is set as the _admin_base_url
3468
+		$redirect_url = isset($query_args['page']) ? admin_url('admin.php') : $this->_admin_base_url;
3469
+		// calculate where we're going (if we have a "save and close" button pushed)
3470
+		if (
3471
+			$this->request->requestParamIsSet('save_and_close')
3472
+			&& $this->request->requestParamIsSet('save_and_close_referrer')
3473
+		) {
3474
+			// even though we have the save_and_close referrer, we need to parse the url for the action in order to generate a nonce
3475
+			$parsed_url = parse_url($this->request->getRequestParam('save_and_close_referrer', '', 'url'));
3476
+			// regenerate query args array from referrer URL
3477
+			parse_str($parsed_url['query'], $query_args);
3478
+			// correct page and action will be in the query args now
3479
+			$redirect_url = admin_url('admin.php');
3480
+		}
3481
+		// merge any default query_args set in _default_route_query_args property
3482
+		if (! empty($this->_default_route_query_args) && ! $this->_is_UI_request) {
3483
+			$args_to_merge = [];
3484
+			foreach ($this->_default_route_query_args as $query_param => $query_value) {
3485
+				// is there a wp_referer array in our _default_route_query_args property?
3486
+				if ($query_param === 'wp_referer') {
3487
+					$query_value = (array) $query_value;
3488
+					foreach ($query_value as $reference => $value) {
3489
+						if (strpos($reference, 'nonce') !== false) {
3490
+							continue;
3491
+						}
3492
+						// finally we will override any arguments in the referer with
3493
+						// what might be set on the _default_route_query_args array.
3494
+						if (isset($this->_default_route_query_args[ $reference ])) {
3495
+							$args_to_merge[ $reference ] = urlencode($this->_default_route_query_args[ $reference ]);
3496
+						} else {
3497
+							$args_to_merge[ $reference ] = urlencode($value);
3498
+						}
3499
+					}
3500
+					continue;
3501
+				}
3502
+				$args_to_merge[ $query_param ] = $query_value;
3503
+			}
3504
+			// now let's merge these arguments but override with what was specifically sent in to the
3505
+			// redirect.
3506
+			$query_args = array_merge($args_to_merge, $query_args);
3507
+		}
3508
+		$this->_process_notices($query_args);
3509
+		// generate redirect url
3510
+		// if redirecting to anything other than the main page, add a nonce
3511
+		if (isset($query_args['action'])) {
3512
+			// manually generate wp_nonce and merge that with the query vars
3513
+			// becuz the wp_nonce_url function wrecks havoc on some vars
3514
+			$query_args['_wpnonce'] = wp_create_nonce($query_args['action'] . '_nonce');
3515
+		}
3516
+		// we're adding some hooks and filters in here for processing any things just before redirects
3517
+		// (example: an admin page has done an insert or update and we want to run something after that).
3518
+		do_action('AHEE_redirect_' . $this->class_name . $this->_req_action, $query_args);
3519
+		$redirect_url = apply_filters(
3520
+			'FHEE_redirect_' . $this->class_name . $this->_req_action,
3521
+			EE_Admin_Page::add_query_args_and_nonce($query_args, $redirect_url),
3522
+			$query_args
3523
+		);
3524
+		// check if we're doing ajax.  If we are then lets just return the results and js can handle how it wants.
3525
+		if ($this->request->isAjax()) {
3526
+			$default_data                    = [
3527
+				'close'        => true,
3528
+				'redirect_url' => $redirect_url,
3529
+				'where'        => 'main',
3530
+				'what'         => 'append',
3531
+			];
3532
+			$this->_template_args['success'] = $success;
3533
+			$this->_template_args['data']    = ! empty($this->_template_args['data']) ? array_merge(
3534
+				$default_data,
3535
+				$this->_template_args['data']
3536
+			) : $default_data;
3537
+			$this->_return_json();
3538
+		}
3539
+		wp_safe_redirect($redirect_url);
3540
+		exit();
3541
+	}
3542
+
3543
+
3544
+	/**
3545
+	 * process any notices before redirecting (or returning ajax request)
3546
+	 * This method sets the $this->_template_args['notices'] attribute;
3547
+	 *
3548
+	 * @param array $query_args         any query args that need to be used for notice transient ('action')
3549
+	 * @param bool  $skip_route_verify  This is typically used when we are processing notices REALLY early and
3550
+	 *                                  page_routes haven't been defined yet.
3551
+	 * @param bool  $sticky_notices     This is used to flag that regardless of whether this is doing_ajax or not, we
3552
+	 *                                  still save a transient for the notice.
3553
+	 * @return void
3554
+	 * @throws EE_Error
3555
+	 * @throws InvalidArgumentException
3556
+	 * @throws InvalidDataTypeException
3557
+	 * @throws InvalidInterfaceException
3558
+	 */
3559
+	protected function _process_notices($query_args = [], $skip_route_verify = false, $sticky_notices = true)
3560
+	{
3561
+		// first let's set individual error properties if doing_ajax and the properties aren't already set.
3562
+		if ($this->request->isAjax()) {
3563
+			$notices = EE_Error::get_notices(false);
3564
+			if (empty($this->_template_args['success'])) {
3565
+				$this->_template_args['success'] = isset($notices['success']) ? $notices['success'] : false;
3566
+			}
3567
+			if (empty($this->_template_args['errors'])) {
3568
+				$this->_template_args['errors'] = isset($notices['errors']) ? $notices['errors'] : false;
3569
+			}
3570
+			if (empty($this->_template_args['attention'])) {
3571
+				$this->_template_args['attention'] = isset($notices['attention']) ? $notices['attention'] : false;
3572
+			}
3573
+		}
3574
+		$this->_template_args['notices'] = EE_Error::get_notices();
3575
+		// IF this isn't ajax we need to create a transient for the notices using the route (however, overridden if $sticky_notices == true)
3576
+		if (! $this->request->isAjax() || $sticky_notices) {
3577
+			$route = isset($query_args['action']) ? $query_args['action'] : 'default';
3578
+			$this->_add_transient(
3579
+				$route,
3580
+				$this->_template_args['notices'],
3581
+				true,
3582
+				$skip_route_verify
3583
+			);
3584
+		}
3585
+	}
3586
+
3587
+
3588
+	/**
3589
+	 * get_action_link_or_button
3590
+	 * returns the button html for adding, editing, or deleting an item (depending on given type)
3591
+	 *
3592
+	 * @param string $action        use this to indicate which action the url is generated with.
3593
+	 * @param string $type          accepted strings must be defined in the $_labels['button'] array(as the key)
3594
+	 *                              property.
3595
+	 * @param array  $extra_request if the button requires extra params you can include them in $key=>$value pairs.
3596
+	 * @param string $class         Use this to give the class for the button. Defaults to 'button--primary'
3597
+	 * @param string $base_url      If this is not provided
3598
+	 *                              the _admin_base_url will be used as the default for the button base_url.
3599
+	 *                              Otherwise this value will be used.
3600
+	 * @param bool   $exclude_nonce If true then no nonce will be in the generated button link.
3601
+	 * @return string
3602
+	 * @throws InvalidArgumentException
3603
+	 * @throws InvalidInterfaceException
3604
+	 * @throws InvalidDataTypeException
3605
+	 * @throws EE_Error
3606
+	 */
3607
+	public function get_action_link_or_button(
3608
+		$action,
3609
+		$type = 'add',
3610
+		$extra_request = [],
3611
+		$class = 'button--primary',
3612
+		$base_url = '',
3613
+		$exclude_nonce = false
3614
+	) {
3615
+		// first let's validate the action (if $base_url is FALSE otherwise validation will happen further along)
3616
+		if (empty($base_url) && ! isset($this->_page_routes[ $action ])) {
3617
+			throw new EE_Error(
3618
+				sprintf(
3619
+					esc_html__(
3620
+						'There is no page route for given action for the button.  This action was given: %s',
3621
+						'event_espresso'
3622
+					),
3623
+					$action
3624
+				)
3625
+			);
3626
+		}
3627
+		if (! isset($this->_labels['buttons'][ $type ])) {
3628
+			throw new EE_Error(
3629
+				sprintf(
3630
+					esc_html__(
3631
+						'There is no label for the given button type (%s). Labels are set in the <code>_page_config</code> property.',
3632
+						'event_espresso'
3633
+					),
3634
+					$type
3635
+				)
3636
+			);
3637
+		}
3638
+		// finally check user access for this button.
3639
+		$has_access = $this->check_user_access($action, true);
3640
+		if (! $has_access) {
3641
+			return '';
3642
+		}
3643
+		$_base_url  = ! $base_url ? $this->_admin_base_url : $base_url;
3644
+		$query_args = [
3645
+			'action' => $action,
3646
+		];
3647
+		// merge extra_request args but make sure our original action takes precedence and doesn't get overwritten.
3648
+		if (! empty($extra_request)) {
3649
+			$query_args = array_merge($extra_request, $query_args);
3650
+		}
3651
+		$url = EE_Admin_Page::add_query_args_and_nonce($query_args, $_base_url, false, $exclude_nonce);
3652
+		return EEH_Template::get_button_or_link($url, $this->_labels['buttons'][ $type ], $class);
3653
+	}
3654
+
3655
+
3656
+	/**
3657
+	 * _per_page_screen_option
3658
+	 * Utility function for adding in a per_page_option in the screen_options_dropdown.
3659
+	 *
3660
+	 * @return void
3661
+	 * @throws InvalidArgumentException
3662
+	 * @throws InvalidInterfaceException
3663
+	 * @throws InvalidDataTypeException
3664
+	 */
3665
+	protected function _per_page_screen_option()
3666
+	{
3667
+		$option = 'per_page';
3668
+		$args   = [
3669
+			'label'   => apply_filters(
3670
+				'FHEE__EE_Admin_Page___per_page_screen_options___label',
3671
+				$this->_admin_page_title,
3672
+				$this
3673
+			),
3674
+			'default' => (int) apply_filters(
3675
+				'FHEE__EE_Admin_Page___per_page_screen_options__default',
3676
+				20
3677
+			),
3678
+			'option'  => $this->_current_page . '_' . $this->_current_view . '_per_page',
3679
+		];
3680
+		// ONLY add the screen option if the user has access to it.
3681
+		if ($this->check_user_access($this->_current_view, true)) {
3682
+			add_screen_option($option, $args);
3683
+		}
3684
+	}
3685
+
3686
+
3687
+	/**
3688
+	 * set_per_page_screen_option
3689
+	 * All this does is make sure that WordPress saves any per_page screen options (if set) for the current page.
3690
+	 * we have to do this rather than running inside the 'set-screen-options' hook because it runs earlier than
3691
+	 * admin_menu.
3692
+	 *
3693
+	 * @return void
3694
+	 */
3695
+	private function _set_per_page_screen_options()
3696
+	{
3697
+		if ($this->request->requestParamIsSet('wp_screen_options')) {
3698
+			check_admin_referer('screen-options-nonce', 'screenoptionnonce');
3699
+			if (! $user = wp_get_current_user()) {
3700
+				return;
3701
+			}
3702
+			$option = $this->request->getRequestParam('wp_screen_options[option]', '', 'key');
3703
+			if (! $option) {
3704
+				return;
3705
+			}
3706
+			$value  = $this->request->getRequestParam('wp_screen_options[value]', 0, 'int');
3707
+			$map_option = $option;
3708
+			$option     = str_replace('-', '_', $option);
3709
+			switch ($map_option) {
3710
+				case $this->_current_page . '_' . $this->_current_view . '_per_page':
3711
+					$max_value = apply_filters(
3712
+						'FHEE__EE_Admin_Page___set_per_page_screen_options__max_value',
3713
+						999,
3714
+						$this->_current_page,
3715
+						$this->_current_view
3716
+					);
3717
+					if ($value < 1) {
3718
+						return;
3719
+					}
3720
+					$value = min($value, $max_value);
3721
+					break;
3722
+				default:
3723
+					$value = apply_filters(
3724
+						'FHEE__EE_Admin_Page___set_per_page_screen_options__value',
3725
+						false,
3726
+						$option,
3727
+						$value
3728
+					);
3729
+					if (false === $value) {
3730
+						return;
3731
+					}
3732
+					break;
3733
+			}
3734
+			update_user_meta($user->ID, $option, $value);
3735
+			wp_safe_redirect(remove_query_arg(['pagenum', 'apage', 'paged'], wp_get_referer()));
3736
+			exit;
3737
+		}
3738
+	}
3739
+
3740
+
3741
+	/**
3742
+	 * This just allows for setting the $_template_args property if it needs to be set outside the object
3743
+	 *
3744
+	 * @param array $data array that will be assigned to template args.
3745
+	 */
3746
+	public function set_template_args($data)
3747
+	{
3748
+		$this->_template_args = array_merge($this->_template_args, (array) $data);
3749
+	}
3750
+
3751
+
3752
+	/**
3753
+	 * This makes available the WP transient system for temporarily moving data between routes
3754
+	 *
3755
+	 * @param string $route             the route that should receive the transient
3756
+	 * @param array  $data              the data that gets sent
3757
+	 * @param bool   $notices           If this is for notices then we use this to indicate so, otherwise its just a
3758
+	 *                                  normal route transient.
3759
+	 * @param bool   $skip_route_verify Used to indicate we want to skip route verification.  This is usually ONLY used
3760
+	 *                                  when we are adding a transient before page_routes have been defined.
3761
+	 * @return void
3762
+	 * @throws EE_Error
3763
+	 */
3764
+	protected function _add_transient($route, $data, $notices = false, $skip_route_verify = false)
3765
+	{
3766
+		$user_id = get_current_user_id();
3767
+		if (! $skip_route_verify) {
3768
+			$this->_verify_route($route);
3769
+		}
3770
+		// now let's set the string for what kind of transient we're setting
3771
+		$transient = $notices
3772
+			? 'ee_rte_n_tx_' . $route . '_' . $user_id
3773
+			: 'rte_tx_' . $route . '_' . $user_id;
3774
+		$data      = $notices ? ['notices' => $data] : $data;
3775
+		// is there already a transient for this route?  If there is then let's ADD to that transient
3776
+		$existing = is_multisite() && is_network_admin()
3777
+			? get_site_transient($transient)
3778
+			: get_transient($transient);
3779
+		if ($existing) {
3780
+			$data = array_merge((array) $data, (array) $existing);
3781
+		}
3782
+		if (is_multisite() && is_network_admin()) {
3783
+			set_site_transient($transient, $data, 8);
3784
+		} else {
3785
+			set_transient($transient, $data, 8);
3786
+		}
3787
+	}
3788
+
3789
+
3790
+	/**
3791
+	 * this retrieves the temporary transient that has been set for moving data between routes.
3792
+	 *
3793
+	 * @param bool   $notices true we get notices transient. False we just return normal route transient
3794
+	 * @param string $route
3795
+	 * @return mixed data
3796
+	 */
3797
+	protected function _get_transient($notices = false, $route = '')
3798
+	{
3799
+		$user_id   = get_current_user_id();
3800
+		$route     = ! $route ? $this->_req_action : $route;
3801
+		$transient = $notices
3802
+			? 'ee_rte_n_tx_' . $route . '_' . $user_id
3803
+			: 'rte_tx_' . $route . '_' . $user_id;
3804
+		$data      = is_multisite() && is_network_admin()
3805
+			? get_site_transient($transient)
3806
+			: get_transient($transient);
3807
+		// delete transient after retrieval (just in case it hasn't expired);
3808
+		if (is_multisite() && is_network_admin()) {
3809
+			delete_site_transient($transient);
3810
+		} else {
3811
+			delete_transient($transient);
3812
+		}
3813
+		return $notices && isset($data['notices']) ? $data['notices'] : $data;
3814
+	}
3815
+
3816
+
3817
+	/**
3818
+	 * The purpose of this method is just to run garbage collection on any EE transients that might have expired but
3819
+	 * would not be called later. This will be assigned to run on a specific EE Admin page. (place the method in the
3820
+	 * default route callback on the EE_Admin page you want it run.)
3821
+	 *
3822
+	 * @return void
3823
+	 */
3824
+	protected function _transient_garbage_collection()
3825
+	{
3826
+		global $wpdb;
3827
+		// retrieve all existing transients
3828
+		$query =
3829
+			"SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE '%rte_tx_%' OR option_name LIKE '%rte_n_tx_%'";
3830
+		if ($results = $wpdb->get_results($query)) {
3831
+			foreach ($results as $result) {
3832
+				$transient = str_replace('_transient_', '', $result->option_name);
3833
+				get_transient($transient);
3834
+				if (is_multisite() && is_network_admin()) {
3835
+					get_site_transient($transient);
3836
+				}
3837
+			}
3838
+		}
3839
+	}
3840
+
3841
+
3842
+	/**
3843
+	 * get_view
3844
+	 *
3845
+	 * @return string content of _view property
3846
+	 */
3847
+	public function get_view()
3848
+	{
3849
+		return $this->_view;
3850
+	}
3851
+
3852
+
3853
+	/**
3854
+	 * getter for the protected $_views property
3855
+	 *
3856
+	 * @return array
3857
+	 */
3858
+	public function get_views()
3859
+	{
3860
+		return $this->_views;
3861
+	}
3862
+
3863
+
3864
+	/**
3865
+	 * get_current_page
3866
+	 *
3867
+	 * @return string _current_page property value
3868
+	 */
3869
+	public function get_current_page()
3870
+	{
3871
+		return $this->_current_page;
3872
+	}
3873
+
3874
+
3875
+	/**
3876
+	 * get_current_view
3877
+	 *
3878
+	 * @return string _current_view property value
3879
+	 */
3880
+	public function get_current_view()
3881
+	{
3882
+		return $this->_current_view;
3883
+	}
3884
+
3885
+
3886
+	/**
3887
+	 * get_current_screen
3888
+	 *
3889
+	 * @return object The current WP_Screen object
3890
+	 */
3891
+	public function get_current_screen()
3892
+	{
3893
+		return $this->_current_screen;
3894
+	}
3895
+
3896
+
3897
+	/**
3898
+	 * get_current_page_view_url
3899
+	 *
3900
+	 * @return string This returns the url for the current_page_view.
3901
+	 */
3902
+	public function get_current_page_view_url()
3903
+	{
3904
+		return $this->_current_page_view_url;
3905
+	}
3906
+
3907
+
3908
+	/**
3909
+	 * just returns the Request
3910
+	 *
3911
+	 * @return RequestInterface
3912
+	 */
3913
+	public function get_request()
3914
+	{
3915
+		return $this->request;
3916
+	}
3917
+
3918
+
3919
+	/**
3920
+	 * just returns the _req_data property
3921
+	 *
3922
+	 * @return array
3923
+	 */
3924
+	public function get_request_data()
3925
+	{
3926
+		return $this->request->requestParams();
3927
+	}
3928
+
3929
+
3930
+	/**
3931
+	 * returns the _req_data protected property
3932
+	 *
3933
+	 * @return string
3934
+	 */
3935
+	public function get_req_action()
3936
+	{
3937
+		return $this->_req_action;
3938
+	}
3939
+
3940
+
3941
+	/**
3942
+	 * @return bool  value of $_is_caf property
3943
+	 */
3944
+	public function is_caf()
3945
+	{
3946
+		return $this->_is_caf;
3947
+	}
3948
+
3949
+
3950
+	/**
3951
+	 * @return mixed
3952
+	 */
3953
+	public function default_espresso_metaboxes()
3954
+	{
3955
+		return $this->_default_espresso_metaboxes;
3956
+	}
3957
+
3958
+
3959
+	/**
3960
+	 * @return mixed
3961
+	 */
3962
+	public function admin_base_url()
3963
+	{
3964
+		return $this->_admin_base_url;
3965
+	}
3966
+
3967
+
3968
+	/**
3969
+	 * @return mixed
3970
+	 */
3971
+	public function wp_page_slug()
3972
+	{
3973
+		return $this->_wp_page_slug;
3974
+	}
3975
+
3976
+
3977
+	/**
3978
+	 * updates  espresso configuration settings
3979
+	 *
3980
+	 * @param string                   $tab
3981
+	 * @param EE_Config_Base|EE_Config $config
3982
+	 * @param string                   $file file where error occurred
3983
+	 * @param string                   $func function  where error occurred
3984
+	 * @param string                   $line line no where error occurred
3985
+	 * @return boolean
3986
+	 */
3987
+	protected function _update_espresso_configuration($tab, $config, $file = '', $func = '', $line = '')
3988
+	{
3989
+		// remove any options that are NOT going to be saved with the config settings.
3990
+		if (isset($config->core->ee_ueip_optin)) {
3991
+			// TODO: remove the following two lines and make sure values are migrated from 3.1
3992
+			update_option('ee_ueip_optin', $config->core->ee_ueip_optin);
3993
+			update_option('ee_ueip_has_notified', true);
3994
+		}
3995
+		// and save it (note we're also doing the network save here)
3996
+		$net_saved    = ! is_main_site() || EE_Network_Config::instance()->update_config(false, false);
3997
+		$config_saved = EE_Config::instance()->update_espresso_config(false, false);
3998
+		if ($config_saved && $net_saved) {
3999
+			EE_Error::add_success(sprintf(esc_html__('"%s" have been successfully updated.', 'event_espresso'), $tab));
4000
+			return true;
4001
+		}
4002
+		EE_Error::add_error(sprintf(esc_html__('The "%s" were not updated.', 'event_espresso'), $tab), $file, $func, $line);
4003
+		return false;
4004
+	}
4005
+
4006
+
4007
+	/**
4008
+	 * Returns an array to be used for EE_FOrm_Fields.helper.php's select_input as the $values argument.
4009
+	 *
4010
+	 * @return array
4011
+	 */
4012
+	public function get_yes_no_values()
4013
+	{
4014
+		return $this->_yes_no_values;
4015
+	}
4016
+
4017
+
4018
+	/**
4019
+	 * @return string
4020
+	 * @throws ReflectionException
4021
+	 * @since $VID:$
4022
+	 */
4023
+	protected function _get_dir()
4024
+	{
4025
+		$reflector = new ReflectionClass($this->class_name);
4026
+		return dirname($reflector->getFileName());
4027
+	}
4028
+
4029
+
4030
+	/**
4031
+	 * A helper for getting a "next link".
4032
+	 *
4033
+	 * @param string $url   The url to link to
4034
+	 * @param string $class The class to use.
4035
+	 * @return string
4036
+	 */
4037
+	protected function _next_link($url, $class = 'dashicons dashicons-arrow-right')
4038
+	{
4039
+		return '<a class="' . $class . '" href="' . $url . '"></a>';
4040
+	}
4041
+
4042
+
4043
+	/**
4044
+	 * A helper for getting a "previous link".
4045
+	 *
4046
+	 * @param string $url   The url to link to
4047
+	 * @param string $class The class to use.
4048
+	 * @return string
4049
+	 */
4050
+	protected function _previous_link($url, $class = 'dashicons dashicons-arrow-left')
4051
+	{
4052
+		return '<a class="' . $class . '" href="' . $url . '"></a>';
4053
+	}
4054
+
4055
+
4056
+
4057
+
4058
+
4059
+
4060
+
4061
+	// below are some messages related methods that should be available across the EE_Admin system.  Note, these methods are NOT page specific
4062
+
4063
+
4064
+	/**
4065
+	 * This processes an request to resend a registration and assumes we have a _REG_ID for doing so. So if the caller
4066
+	 * 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
4067
+	 * _req_data array.
4068
+	 *
4069
+	 * @return bool success/fail
4070
+	 * @throws EE_Error
4071
+	 * @throws InvalidArgumentException
4072
+	 * @throws ReflectionException
4073
+	 * @throws InvalidDataTypeException
4074
+	 * @throws InvalidInterfaceException
4075
+	 */
4076
+	protected function _process_resend_registration()
4077
+	{
4078
+		$this->_template_args['success'] = EED_Messages::process_resend($this->_req_data);
4079
+		do_action(
4080
+			'AHEE__EE_Admin_Page___process_resend_registration',
4081
+			$this->_template_args['success'],
4082
+			$this->request->requestParams()
4083
+		);
4084
+		return $this->_template_args['success'];
4085
+	}
4086
+
4087
+
4088
+	/**
4089
+	 * This automatically processes any payment message notifications when manual payment has been applied.
4090
+	 *
4091
+	 * @param EE_Payment $payment
4092
+	 * @return bool success/fail
4093
+	 */
4094
+	protected function _process_payment_notification(EE_Payment $payment)
4095
+	{
4096
+		add_filter('FHEE__EE_Payment_Processor__process_registration_payments__display_notifications', '__return_true');
4097
+		do_action('AHEE__EE_Admin_Page___process_admin_payment_notification', $payment);
4098
+		$this->_template_args['success'] = apply_filters(
4099
+			'FHEE__EE_Admin_Page___process_admin_payment_notification__success',
4100
+			false,
4101
+			$payment
4102
+		);
4103
+		return $this->_template_args['success'];
4104
+	}
4105
+
4106
+
4107
+	/**
4108
+	 * @param EEM_Base      $entity_model
4109
+	 * @param string        $entity_PK_name name of the primary key field used as a request param, ie: id, ID, etc
4110
+	 * @param string        $action         one of the EE_Admin_List_Table::ACTION_* constants: delete, restore, trash
4111
+	 * @param string        $delete_column  name of the field that denotes whether entity is trashed
4112
+	 * @param callable|null $callback       called after entity is trashed, restored, or deleted
4113
+	 * @return int|float
4114
+	 * @throws EE_Error
4115
+	 */
4116
+	protected function trashRestoreDeleteEntities(
4117
+		EEM_Base $entity_model,
4118
+		string $entity_PK_name,
4119
+		string $action = EE_Admin_List_Table::ACTION_DELETE,
4120
+		string $delete_column = '',
4121
+		callable $callback = null
4122
+	) {
4123
+		$entity_PK      = $entity_model->get_primary_key_field();
4124
+		$entity_PK_name = $entity_PK_name ?: $entity_PK->get_name();
4125
+		$entity_PK_type = $this->resolveEntityFieldDataType($entity_PK);
4126
+		// grab ID if deleting a single entity
4127
+		if ($this->request->requestParamIsSet($entity_PK_name)) {
4128
+			$ID = $this->request->getRequestParam($entity_PK_name, 0, $entity_PK_type);
4129
+			return $this->trashRestoreDeleteEntity($entity_model, $ID, $action, $delete_column, $callback) ? 1 : 0;
4130
+		}
4131
+		// or grab checkbox array if bulk deleting
4132
+		$checkboxes = $this->request->getRequestParam('checkbox', [], $entity_PK_type, true);
4133
+		if (empty($checkboxes)) {
4134
+			return 0;
4135
+		}
4136
+		$success = 0;
4137
+		$IDs     = array_keys($checkboxes);
4138
+		// cycle thru bulk action checkboxes
4139
+		foreach ($IDs as $ID) {
4140
+			// increment $success
4141
+			if ($this->trashRestoreDeleteEntity($entity_model, $ID, $action, $delete_column, $callback)) {
4142
+				$success++;
4143
+			}
4144
+		}
4145
+		$count = (int) count($checkboxes);
4146
+		// if multiple entities were deleted successfully, then $deleted will be full count of deletions,
4147
+		// otherwise it will be a fraction of ( actual deletions / total entities to be deleted )
4148
+		return $success === $count ? $count : $success / $count;
4149
+	}
4150
+
4151
+
4152
+	/**
4153
+	 * @param EE_Primary_Key_Field_Base $entity_PK
4154
+	 * @return string
4155
+	 * @throws EE_Error
4156
+	 * @since   4.10.30.p
4157
+	 */
4158
+	private function resolveEntityFieldDataType(EE_Primary_Key_Field_Base $entity_PK): string
4159
+	{
4160
+		$entity_PK_type = $entity_PK->getSchemaType();
4161
+		switch ($entity_PK_type) {
4162
+			case 'boolean':
4163
+				return 'bool';
4164
+			case 'integer':
4165
+				return 'int';
4166
+			case 'number':
4167
+				return 'float';
4168
+			case 'string':
4169
+				return 'string';
4170
+		}
4171
+		throw new RuntimeException(
4172
+			sprintf(
4173
+				esc_html__(
4174
+					'"%1$s" is an invalid schema type for the %2$s primary key.',
4175
+					'event_espresso'
4176
+				),
4177
+				$entity_PK_type,
4178
+				$entity_PK->get_name()
4179
+			)
4180
+		);
4181
+	}
4182
+
4183
+
4184
+	/**
4185
+	 * @param EEM_Base      $entity_model
4186
+	 * @param int|string    $entity_ID
4187
+	 * @param string        $action        one of the EE_Admin_List_Table::ACTION_* constants: delete, restore, trash
4188
+	 * @param string        $delete_column name of the field that denotes whether entity is trashed
4189
+	 * @param callable|null $callback      called after entity is trashed, restored, or deleted
4190
+	 * @return bool
4191
+	 */
4192
+	protected function trashRestoreDeleteEntity(
4193
+		EEM_Base $entity_model,
4194
+		$entity_ID,
4195
+		string $action,
4196
+		string $delete_column,
4197
+		?callable $callback = null
4198
+	): bool {
4199
+		$entity_ID = absint($entity_ID);
4200
+		if (! $entity_ID) {
4201
+			$this->trashRestoreDeleteError($action, $entity_model);
4202
+		}
4203
+		$result = 0;
4204
+		try {
4205
+			switch ($action) {
4206
+				case EE_Admin_List_Table::ACTION_DELETE:
4207
+					$result = (bool) $entity_model->delete_permanently_by_ID($entity_ID);
4208
+					break;
4209
+				case EE_Admin_List_Table::ACTION_RESTORE:
4210
+					$this->validateDeleteColumn($entity_model, $delete_column);
4211
+					$result = $entity_model->update_by_ID([$delete_column => 0], $entity_ID);
4212
+					break;
4213
+				case EE_Admin_List_Table::ACTION_TRASH:
4214
+					$this->validateDeleteColumn($entity_model, $delete_column);
4215
+					$result = $entity_model->update_by_ID([$delete_column => 1], $entity_ID);
4216
+					break;
4217
+			}
4218
+		} catch (Exception $exception) {
4219
+			$this->trashRestoreDeleteError($action, $entity_model, $exception);
4220
+		}
4221
+		if (is_callable($callback)) {
4222
+			call_user_func_array($callback, [$entity_model, $entity_ID, $action, $result, $delete_column]);
4223
+		}
4224
+		return $result;
4225
+	}
4226
+
4227
+
4228
+	/**
4229
+	 * @param EEM_Base $entity_model
4230
+	 * @param string   $delete_column
4231
+	 * @since 4.10.30.p
4232
+	 */
4233
+	private function validateDeleteColumn(EEM_Base $entity_model, string $delete_column)
4234
+	{
4235
+		if (empty($delete_column)) {
4236
+			throw new DomainException(
4237
+				sprintf(
4238
+					esc_html__(
4239
+						'You need to specify the name of the "delete column" on the %2$s model, in order to trash or restore an entity.',
4240
+						'event_espresso'
4241
+					),
4242
+					$entity_model->get_this_model_name()
4243
+				)
4244
+			);
4245
+		}
4246
+		if (! $entity_model->has_field($delete_column)) {
4247
+			throw new DomainException(
4248
+				sprintf(
4249
+					esc_html__(
4250
+						'The %1$s field does not exist on the %2$s model.',
4251
+						'event_espresso'
4252
+					),
4253
+					$delete_column,
4254
+					$entity_model->get_this_model_name()
4255
+				)
4256
+			);
4257
+		}
4258
+	}
4259
+
4260
+
4261
+	/**
4262
+	 * @param EEM_Base       $entity_model
4263
+	 * @param Exception|null $exception
4264
+	 * @param string         $action
4265
+	 * @since 4.10.30.p
4266
+	 */
4267
+	private function trashRestoreDeleteError(string $action, EEM_Base $entity_model, ?Exception $exception = null)
4268
+	{
4269
+		if ($exception instanceof Exception) {
4270
+			throw new RuntimeException(
4271
+				sprintf(
4272
+					esc_html__(
4273
+						'Could not %1$s the %2$s because the following error occurred: %3$s',
4274
+						'event_espresso'
4275
+					),
4276
+					$action,
4277
+					$entity_model->get_this_model_name(),
4278
+					$exception->getMessage()
4279
+				)
4280
+			);
4281
+		}
4282
+		throw new RuntimeException(
4283
+			sprintf(
4284
+				esc_html__(
4285
+					'Could not %1$s the %2$s because an invalid ID was received.',
4286
+					'event_espresso'
4287
+				),
4288
+				$action,
4289
+				$entity_model->get_this_model_name()
4290
+			)
4291
+		);
4292
+	}
4293 4293
 }
Please login to merge, or discard this patch.
Spacing   +179 added lines, -179 removed lines patch added patch discarded remove patch
@@ -629,7 +629,7 @@  discard block
 block discarded – undo
629 629
         $ee_menu_slugs = (array) $ee_menu_slugs;
630 630
         if (
631 631
             ! $this->request->isAjax()
632
-            && (! $this->_current_page || ! isset($ee_menu_slugs[ $this->_current_page ]))
632
+            && ( ! $this->_current_page || ! isset($ee_menu_slugs[$this->_current_page]))
633 633
         ) {
634 634
             return;
635 635
         }
@@ -649,7 +649,7 @@  discard block
 block discarded – undo
649 649
             : $req_action;
650 650
 
651 651
         $this->_current_view = $this->_req_action;
652
-        $this->_req_nonce    = $this->_req_action . '_nonce';
652
+        $this->_req_nonce    = $this->_req_action.'_nonce';
653 653
         $this->_define_page_props();
654 654
         $this->_current_page_view_url = add_query_arg(
655 655
             ['page' => $this->_current_page, 'action' => $this->_current_view],
@@ -679,33 +679,33 @@  discard block
 block discarded – undo
679 679
         }
680 680
         // filter routes and page_config so addons can add their stuff. Filtering done per class
681 681
         $this->_page_routes = apply_filters(
682
-            'FHEE__' . $this->class_name . '__page_setup__page_routes',
682
+            'FHEE__'.$this->class_name.'__page_setup__page_routes',
683 683
             $this->_page_routes,
684 684
             $this
685 685
         );
686 686
         $this->_page_config = apply_filters(
687
-            'FHEE__' . $this->class_name . '__page_setup__page_config',
687
+            'FHEE__'.$this->class_name.'__page_setup__page_config',
688 688
             $this->_page_config,
689 689
             $this
690 690
         );
691 691
         if ($this->base_class_name !== '') {
692 692
             $this->_page_routes = apply_filters(
693
-                'FHEE__' . $this->base_class_name . '__page_setup__page_routes',
693
+                'FHEE__'.$this->base_class_name.'__page_setup__page_routes',
694 694
                 $this->_page_routes,
695 695
                 $this
696 696
             );
697 697
             $this->_page_config = apply_filters(
698
-                'FHEE__' . $this->base_class_name . '__page_setup__page_config',
698
+                'FHEE__'.$this->base_class_name.'__page_setup__page_config',
699 699
                 $this->_page_config,
700 700
                 $this
701 701
             );
702 702
         }
703 703
         // if AHEE__EE_Admin_Page__route_admin_request_$this->_current_view method is present
704 704
         // then we call it hooked into the AHEE__EE_Admin_Page__route_admin_request action
705
-        if (method_exists($this, 'AHEE__EE_Admin_Page__route_admin_request_' . $this->_current_view)) {
705
+        if (method_exists($this, 'AHEE__EE_Admin_Page__route_admin_request_'.$this->_current_view)) {
706 706
             add_action(
707 707
                 'AHEE__EE_Admin_Page__route_admin_request',
708
-                [$this, 'AHEE__EE_Admin_Page__route_admin_request_' . $this->_current_view],
708
+                [$this, 'AHEE__EE_Admin_Page__route_admin_request_'.$this->_current_view],
709 709
                 10,
710 710
                 2
711 711
             );
@@ -718,8 +718,8 @@  discard block
 block discarded – undo
718 718
             if ($this->_is_UI_request) {
719 719
                 // admin_init stuff - global, all views for this page class, specific view
720 720
                 add_action('admin_init', [$this, 'admin_init'], 10);
721
-                if (method_exists($this, 'admin_init_' . $this->_current_view)) {
722
-                    add_action('admin_init', [$this, 'admin_init_' . $this->_current_view], 15);
721
+                if (method_exists($this, 'admin_init_'.$this->_current_view)) {
722
+                    add_action('admin_init', [$this, 'admin_init_'.$this->_current_view], 15);
723 723
                 }
724 724
             } else {
725 725
                 // hijack regular WP loading and route admin request immediately
@@ -738,12 +738,12 @@  discard block
 block discarded – undo
738 738
      */
739 739
     private function _do_other_page_hooks()
740 740
     {
741
-        $registered_pages = apply_filters('FHEE_do_other_page_hooks_' . $this->page_slug, []);
741
+        $registered_pages = apply_filters('FHEE_do_other_page_hooks_'.$this->page_slug, []);
742 742
         foreach ($registered_pages as $page) {
743 743
             // now let's setup the file name and class that should be present
744 744
             $classname = str_replace('.class.php', '', $page);
745 745
             // autoloaders should take care of loading file
746
-            if (! class_exists($classname)) {
746
+            if ( ! class_exists($classname)) {
747 747
                 $error_msg[] = sprintf(
748 748
                     esc_html__(
749 749
                         'Something went wrong with loading the %s admin hooks page.',
@@ -760,7 +760,7 @@  discard block
 block discarded – undo
760 760
                                    ),
761 761
                                    $page,
762 762
                                    '<br />',
763
-                                   '<strong>' . $classname . '</strong>'
763
+                                   '<strong>'.$classname.'</strong>'
764 764
                                );
765 765
                 throw new EE_Error(implode('||', $error_msg));
766 766
             }
@@ -802,13 +802,13 @@  discard block
 block discarded – undo
802 802
         // load admin_notices - global, page class, and view specific
803 803
         add_action('admin_notices', [$this, 'admin_notices_global'], 5);
804 804
         add_action('admin_notices', [$this, 'admin_notices'], 10);
805
-        if (method_exists($this, 'admin_notices_' . $this->_current_view)) {
806
-            add_action('admin_notices', [$this, 'admin_notices_' . $this->_current_view], 15);
805
+        if (method_exists($this, 'admin_notices_'.$this->_current_view)) {
806
+            add_action('admin_notices', [$this, 'admin_notices_'.$this->_current_view], 15);
807 807
         }
808 808
         // load network admin_notices - global, page class, and view specific
809 809
         add_action('network_admin_notices', [$this, 'network_admin_notices_global'], 5);
810
-        if (method_exists($this, 'network_admin_notices_' . $this->_current_view)) {
811
-            add_action('network_admin_notices', [$this, 'network_admin_notices_' . $this->_current_view]);
810
+        if (method_exists($this, 'network_admin_notices_'.$this->_current_view)) {
811
+            add_action('network_admin_notices', [$this, 'network_admin_notices_'.$this->_current_view]);
812 812
         }
813 813
         // this will save any per_page screen options if they are present
814 814
         $this->_set_per_page_screen_options();
@@ -929,7 +929,7 @@  discard block
 block discarded – undo
929 929
     protected function _verify_routes()
930 930
     {
931 931
         do_action('AHEE_log', __FILE__, __FUNCTION__, '');
932
-        if (! $this->_current_page && ! $this->request->isAjax()) {
932
+        if ( ! $this->_current_page && ! $this->request->isAjax()) {
933 933
             return false;
934 934
         }
935 935
         $this->_route = false;
@@ -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
                           . esc_html__(
946 946
                               ' Make sure the "set_page_routes()" method exists, and is setting the "_page_routes" array properly.',
947 947
                               'event_espresso'
@@ -950,8 +950,8 @@  discard block
 block discarded – undo
950 950
         }
951 951
         // and that the requested page route exists
952 952
         if (array_key_exists($this->_req_action, $this->_page_routes)) {
953
-            $this->_route        = $this->_page_routes[ $this->_req_action ];
954
-            $this->_route_config = $this->_page_config[ $this->_req_action ] ?? [];
953
+            $this->_route        = $this->_page_routes[$this->_req_action];
954
+            $this->_route_config = $this->_page_config[$this->_req_action] ?? [];
955 955
         } else {
956 956
             // user error msg
957 957
             $error_msg = sprintf(
@@ -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
                           . sprintf(
967 967
                               esc_html__(
968 968
                                   ' Create a key in the "_page_routes" array named "%s" and set its value to the appropriate method.',
@@ -973,7 +973,7 @@  discard block
 block discarded – undo
973 973
             throw new EE_Error($error_msg);
974 974
         }
975 975
         // and that a default route exists
976
-        if (! array_key_exists('default', $this->_page_routes)) {
976
+        if ( ! array_key_exists('default', $this->_page_routes)) {
977 977
             // user error msg
978 978
             $error_msg = sprintf(
979 979
                 esc_html__(
@@ -983,7 +983,7 @@  discard block
 block discarded – undo
983 983
                 $this->_admin_page_title
984 984
             );
985 985
             // developer error msg
986
-            $error_msg .= '||' . $error_msg
986
+            $error_msg .= '||'.$error_msg
987 987
                           . esc_html__(
988 988
                               ' Create a key in the "_page_routes" array named "default" and set its value to your default page method.',
989 989
                               'event_espresso'
@@ -1025,7 +1025,7 @@  discard block
 block discarded – undo
1025 1025
             $this->_admin_page_title
1026 1026
         );
1027 1027
         // developer error msg
1028
-        $error_msg .= '||' . $error_msg
1028
+        $error_msg .= '||'.$error_msg
1029 1029
                       . sprintf(
1030 1030
                           esc_html__(
1031 1031
                               ' Check the route you are using in your method (%s) and make sure it matches a route set in your "_page_routes" array property',
@@ -1053,7 +1053,7 @@  discard block
 block discarded – undo
1053 1053
     protected function _verify_nonce($nonce, $nonce_ref)
1054 1054
     {
1055 1055
         // verify nonce against expected value
1056
-        if (! wp_verify_nonce($nonce, $nonce_ref)) {
1056
+        if ( ! wp_verify_nonce($nonce, $nonce_ref)) {
1057 1057
             // these are not the droids you are looking for !!!
1058 1058
             $msg = sprintf(
1059 1059
                 esc_html__('%sNonce Fail.%s', 'event_espresso'),
@@ -1070,7 +1070,7 @@  discard block
 block discarded – undo
1070 1070
                     __CLASS__
1071 1071
                 );
1072 1072
             }
1073
-            if (! $this->request->isAjax()) {
1073
+            if ( ! $this->request->isAjax()) {
1074 1074
                 wp_die($msg);
1075 1075
             }
1076 1076
             EE_Error::add_error($msg, __FILE__, __FUNCTION__, __LINE__);
@@ -1094,7 +1094,7 @@  discard block
 block discarded – undo
1094 1094
      */
1095 1095
     protected function _route_admin_request()
1096 1096
     {
1097
-        if (! $this->_is_UI_request) {
1097
+        if ( ! $this->_is_UI_request) {
1098 1098
             $this->_verify_routes();
1099 1099
         }
1100 1100
         $nonce_check = ! isset($this->_route_config['require_nonce']) || $this->_route_config['require_nonce'];
@@ -1114,7 +1114,7 @@  discard block
 block discarded – undo
1114 1114
         $error_msg = '';
1115 1115
         // action right before calling route
1116 1116
         // (hook is something like 'AHEE__Registrations_Admin_Page__route_admin_request')
1117
-        if (! did_action('AHEE__EE_Admin_Page__route_admin_request')) {
1117
+        if ( ! did_action('AHEE__EE_Admin_Page__route_admin_request')) {
1118 1118
             do_action('AHEE__EE_Admin_Page__route_admin_request', $this->_current_view, $this);
1119 1119
         }
1120 1120
         // strip _wp_http_referer from the server REQUEST_URI
@@ -1126,7 +1126,7 @@  discard block
 block discarded – undo
1126 1126
         );
1127 1127
         // set new value in both our Request object and the super global
1128 1128
         $this->request->setServerParam('REQUEST_URI', $request_uri, true);
1129
-        if (! empty($func)) {
1129
+        if ( ! empty($func)) {
1130 1130
             if (is_array($func)) {
1131 1131
                 [$class, $method] = $func;
1132 1132
             } elseif (strpos($func, '::') !== false) {
@@ -1135,7 +1135,7 @@  discard block
 block discarded – undo
1135 1135
                 $class  = $this;
1136 1136
                 $method = $func;
1137 1137
             }
1138
-            if (! (is_object($class) && $class === $this)) {
1138
+            if ( ! (is_object($class) && $class === $this)) {
1139 1139
                 // send along this admin page object for access by addons.
1140 1140
                 $args['admin_page_object'] = $this;
1141 1141
             }
@@ -1176,7 +1176,7 @@  discard block
 block discarded – undo
1176 1176
                     $method
1177 1177
                 );
1178 1178
             }
1179
-            if (! empty($error_msg)) {
1179
+            if ( ! empty($error_msg)) {
1180 1180
                 throw new EE_Error($error_msg);
1181 1181
             }
1182 1182
         }
@@ -1262,7 +1262,7 @@  discard block
 block discarded – undo
1262 1262
                 if (strpos($key, 'nonce') !== false) {
1263 1263
                     continue;
1264 1264
                 }
1265
-                $args[ 'wp_referer[' . $key . ']' ] = is_string($value) ? htmlspecialchars($value) : $value;
1265
+                $args['wp_referer['.$key.']'] = is_string($value) ? htmlspecialchars($value) : $value;
1266 1266
             }
1267 1267
         }
1268 1268
         return EEH_URL::add_query_args_and_nonce($args, $url, $exclude_nonce);
@@ -1302,12 +1302,12 @@  discard block
 block discarded – undo
1302 1302
      */
1303 1303
     protected function _add_help_tabs()
1304 1304
     {
1305
-        if (isset($this->_page_config[ $this->_req_action ])) {
1306
-            $config = $this->_page_config[ $this->_req_action ];
1305
+        if (isset($this->_page_config[$this->_req_action])) {
1306
+            $config = $this->_page_config[$this->_req_action];
1307 1307
             // let's see if there is a help_sidebar set for the current route and we'll set that up for usage as well.
1308 1308
             if (is_array($config) && isset($config['help_sidebar'])) {
1309 1309
                 // check that the callback given is valid
1310
-                if (! method_exists($this, $config['help_sidebar'])) {
1310
+                if ( ! method_exists($this, $config['help_sidebar'])) {
1311 1311
                     throw new EE_Error(
1312 1312
                         sprintf(
1313 1313
                             esc_html__(
@@ -1320,18 +1320,18 @@  discard block
 block discarded – undo
1320 1320
                     );
1321 1321
                 }
1322 1322
                 $content = apply_filters(
1323
-                    'FHEE__' . $this->class_name . '__add_help_tabs__help_sidebar',
1323
+                    'FHEE__'.$this->class_name.'__add_help_tabs__help_sidebar',
1324 1324
                     $this->{$config['help_sidebar']}()
1325 1325
                 );
1326 1326
                 $this->_current_screen->set_help_sidebar($content);
1327 1327
             }
1328
-            if (! isset($config['help_tabs'])) {
1328
+            if ( ! isset($config['help_tabs'])) {
1329 1329
                 return;
1330 1330
             } //no help tabs for this route
1331 1331
             foreach ((array) $config['help_tabs'] as $tab_id => $cfg) {
1332 1332
                 // we're here so there ARE help tabs!
1333 1333
                 // make sure we've got what we need
1334
-                if (! isset($cfg['title'])) {
1334
+                if ( ! isset($cfg['title'])) {
1335 1335
                     throw new EE_Error(
1336 1336
                         esc_html__(
1337 1337
                             'The _page_config array is not set up properly for help tabs.  It is missing a title',
@@ -1339,7 +1339,7 @@  discard block
 block discarded – undo
1339 1339
                         )
1340 1340
                     );
1341 1341
                 }
1342
-                if (! isset($cfg['filename']) && ! isset($cfg['callback']) && ! isset($cfg['content'])) {
1342
+                if ( ! isset($cfg['filename']) && ! isset($cfg['callback']) && ! isset($cfg['content'])) {
1343 1343
                     throw new EE_Error(
1344 1344
                         esc_html__(
1345 1345
                             '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',
@@ -1348,11 +1348,11 @@  discard block
 block discarded – undo
1348 1348
                     );
1349 1349
                 }
1350 1350
                 // first priority goes to content.
1351
-                if (! empty($cfg['content'])) {
1351
+                if ( ! empty($cfg['content'])) {
1352 1352
                     $content = ! empty($cfg['content']) ? $cfg['content'] : null;
1353 1353
                     // second priority goes to filename
1354
-                } elseif (! empty($cfg['filename'])) {
1355
-                    $file_path = $this->_get_dir() . '/help_tabs/' . $cfg['filename'] . '.help_tab.php';
1354
+                } elseif ( ! empty($cfg['filename'])) {
1355
+                    $file_path = $this->_get_dir().'/help_tabs/'.$cfg['filename'].'.help_tab.php';
1356 1356
                     // 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)
1357 1357
                     $file_path = ! is_readable($file_path) ? EE_ADMIN_PAGES
1358 1358
                                                              . basename($this->_get_dir())
@@ -1360,7 +1360,7 @@  discard block
 block discarded – undo
1360 1360
                                                              . $cfg['filename']
1361 1361
                                                              . '.help_tab.php' : $file_path;
1362 1362
                     // if file is STILL not readable then let's do a EE_Error so its more graceful than a fatal error.
1363
-                    if (! isset($cfg['callback']) && ! is_readable($file_path)) {
1363
+                    if ( ! isset($cfg['callback']) && ! is_readable($file_path)) {
1364 1364
                         EE_Error::add_error(
1365 1365
                             sprintf(
1366 1366
                                 esc_html__(
@@ -1408,7 +1408,7 @@  discard block
 block discarded – undo
1408 1408
                     return;
1409 1409
                 }
1410 1410
                 // setup config array for help tab method
1411
-                $id  = $this->page_slug . '-' . $this->_req_action . '-' . $tab_id;
1411
+                $id  = $this->page_slug.'-'.$this->_req_action.'-'.$tab_id;
1412 1412
                 $_ht = [
1413 1413
                     'id'       => $id,
1414 1414
                     'title'    => $cfg['title'],
@@ -1434,8 +1434,8 @@  discard block
 block discarded – undo
1434 1434
             $qtips = (array) $this->_route_config['qtips'];
1435 1435
             // load qtip loader
1436 1436
             $path = [
1437
-                $this->_get_dir() . '/qtips/',
1438
-                EE_ADMIN_PAGES . basename($this->_get_dir()) . '/qtips/',
1437
+                $this->_get_dir().'/qtips/',
1438
+                EE_ADMIN_PAGES.basename($this->_get_dir()).'/qtips/',
1439 1439
             ];
1440 1440
             EEH_Qtip_Loader::instance()->register($qtips, $path);
1441 1441
         }
@@ -1457,7 +1457,7 @@  discard block
 block discarded – undo
1457 1457
         $i = 0;
1458 1458
         $only_tab = count($this->_page_config) < 2;
1459 1459
         foreach ($this->_page_config as $slug => $config) {
1460
-            if (! is_array($config) || empty($config['nav'])) {
1460
+            if ( ! is_array($config) || empty($config['nav'])) {
1461 1461
                 continue;
1462 1462
             }
1463 1463
             // no nav tab for this config
@@ -1466,27 +1466,27 @@  discard block
 block discarded – undo
1466 1466
                 // nav tab is only to appear when route requested.
1467 1467
                 continue;
1468 1468
             }
1469
-            if (! $this->check_user_access($slug, true)) {
1469
+            if ( ! $this->check_user_access($slug, true)) {
1470 1470
                 // no nav tab because current user does not have access.
1471 1471
                 continue;
1472 1472
             }
1473
-            $css_class = isset($config['css_class']) ? $config['css_class'] . ' ' : '';
1473
+            $css_class = isset($config['css_class']) ? $config['css_class'].' ' : '';
1474 1474
             $css_class .= $only_tab ? ' ee-only-tab' : '';
1475 1475
 
1476
-            $this->_nav_tabs[ $slug ] = [
1476
+            $this->_nav_tabs[$slug] = [
1477 1477
                 'url'       => $config['nav']['url'] ?? EE_Admin_Page::add_query_args_and_nonce(
1478 1478
                         ['action' => $slug],
1479 1479
                         $this->_admin_base_url
1480 1480
                     ),
1481 1481
                 'link_text' => $this->navTabLabel($config['nav'], $slug),
1482
-                'css_class' => $this->_req_action === $slug ? $css_class . ' nav-tab-active' : $css_class,
1482
+                'css_class' => $this->_req_action === $slug ? $css_class.' nav-tab-active' : $css_class,
1483 1483
                 'order'     => $config['nav']['order'] ?? $i,
1484 1484
             ];
1485 1485
             $i++;
1486 1486
         }
1487 1487
         // if $this->_nav_tabs is empty then lets set the default
1488 1488
         if (empty($this->_nav_tabs)) {
1489
-            $this->_nav_tabs[ $this->_default_nav_tab_name ] = [
1489
+            $this->_nav_tabs[$this->_default_nav_tab_name] = [
1490 1490
                 'url'       => $this->_admin_base_url,
1491 1491
                 'link_text' => ucwords(str_replace('_', ' ', $this->_default_nav_tab_name)),
1492 1492
                 'css_class' => 'nav-tab-active',
@@ -1502,11 +1502,11 @@  discard block
 block discarded – undo
1502 1502
     {
1503 1503
         $label = $nav_tab['label'] ?? ucwords(str_replace('_', ' ', $slug));
1504 1504
         $icon = $nav_tab['icon'] ?? null;
1505
-        $icon = $icon ? '<span class="dashicons ' . $icon . '"></span>' : '';
1505
+        $icon = $icon ? '<span class="dashicons '.$icon.'"></span>' : '';
1506 1506
         return '
1507 1507
             <span class="ee-admin-screen-tab__label">
1508
-                ' . $icon . '
1509
-                <span class="ee-nav-label__text">' . $label . '</span>
1508
+                ' . $icon.'
1509
+                <span class="ee-nav-label__text">' . $label.'</span>
1510 1510
             </span>';
1511 1511
     }
1512 1512
 
@@ -1523,10 +1523,10 @@  discard block
 block discarded – undo
1523 1523
             foreach ($this->_route_config['labels'] as $label => $text) {
1524 1524
                 if (is_array($text)) {
1525 1525
                     foreach ($text as $sublabel => $subtext) {
1526
-                        $this->_labels[ $label ][ $sublabel ] = $subtext;
1526
+                        $this->_labels[$label][$sublabel] = $subtext;
1527 1527
                     }
1528 1528
                 } else {
1529
-                    $this->_labels[ $label ] = $text;
1529
+                    $this->_labels[$label] = $text;
1530 1530
                 }
1531 1531
             }
1532 1532
         }
@@ -1548,10 +1548,10 @@  discard block
 block discarded – undo
1548 1548
     {
1549 1549
         do_action('AHEE_log', __FILE__, __FUNCTION__, '');
1550 1550
         $route_to_check = empty($route_to_check) ? $this->_req_action : $route_to_check;
1551
-        $capability = ! empty($route_to_check) && isset($this->_page_routes[ $route_to_check ])
1552
-                                      && is_array($this->_page_routes[ $route_to_check ])
1553
-                        && ! empty($this->_page_routes[ $route_to_check ]['capability'])
1554
-            ? $this->_page_routes[ $route_to_check ]['capability']
1551
+        $capability = ! empty($route_to_check) && isset($this->_page_routes[$route_to_check])
1552
+                                      && is_array($this->_page_routes[$route_to_check])
1553
+                        && ! empty($this->_page_routes[$route_to_check]['capability'])
1554
+            ? $this->_page_routes[$route_to_check]['capability']
1555 1555
             : null;
1556 1556
 
1557 1557
         if (empty($capability) && empty($route_to_check)) {
@@ -1605,14 +1605,14 @@  discard block
 block discarded – undo
1605 1605
         string $priority = 'default',
1606 1606
         ?array $callback_args = null
1607 1607
     ) {
1608
-        if (! is_callable($callback)) {
1608
+        if ( ! is_callable($callback)) {
1609 1609
             return;
1610 1610
         }
1611 1611
 
1612 1612
         add_meta_box($box_id, $title, $callback, $screen, $context, $priority, $callback_args);
1613 1613
         add_filter(
1614 1614
             "postbox_classes_{$this->_wp_page_slug}_{$box_id}",
1615
-            function ($classes) {
1615
+            function($classes) {
1616 1616
                 array_push($classes, 'ee-admin-container');
1617 1617
                 return $classes;
1618 1618
             }
@@ -1706,7 +1706,7 @@  discard block
 block discarded – undo
1706 1706
         ';
1707 1707
 
1708 1708
         // current set timezone for timezone js
1709
-        echo '<span id="current_timezone" class="hidden">' . esc_html(EEH_DTT_Helper::get_timezone()) . '</span>';
1709
+        echo '<span id="current_timezone" class="hidden">'.esc_html(EEH_DTT_Helper::get_timezone()).'</span>';
1710 1710
     }
1711 1711
 
1712 1712
 
@@ -1740,7 +1740,7 @@  discard block
 block discarded – undo
1740 1740
         // loop through the array and setup content
1741 1741
         foreach ($help_array as $trigger => $help) {
1742 1742
             // make sure the array is setup properly
1743
-            if (! isset($help['title'], $help['content'])) {
1743
+            if ( ! isset($help['title'], $help['content'])) {
1744 1744
                 throw new EE_Error(
1745 1745
                     esc_html__(
1746 1746
                         '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',
@@ -1754,8 +1754,8 @@  discard block
 block discarded – undo
1754 1754
                 'help_popup_title'   => $help['title'],
1755 1755
                 'help_popup_content' => $help['content'],
1756 1756
             ];
1757
-            $content       .= EEH_Template::display_template(
1758
-                EE_ADMIN_TEMPLATE . 'admin_help_popup.template.php',
1757
+            $content .= EEH_Template::display_template(
1758
+                EE_ADMIN_TEMPLATE.'admin_help_popup.template.php',
1759 1759
                 $template_args,
1760 1760
                 true
1761 1761
             );
@@ -1777,15 +1777,15 @@  discard block
 block discarded – undo
1777 1777
     private function _get_help_content()
1778 1778
     {
1779 1779
         // what is the method we're looking for?
1780
-        $method_name = '_help_popup_content_' . $this->_req_action;
1780
+        $method_name = '_help_popup_content_'.$this->_req_action;
1781 1781
         // if method doesn't exist let's get out.
1782
-        if (! method_exists($this, $method_name)) {
1782
+        if ( ! method_exists($this, $method_name)) {
1783 1783
             return [];
1784 1784
         }
1785 1785
         // k we're good to go let's retrieve the help array
1786 1786
         $help_array = $this->{$method_name}();
1787 1787
         // make sure we've got an array!
1788
-        if (! is_array($help_array)) {
1788
+        if ( ! is_array($help_array)) {
1789 1789
             throw new EE_Error(
1790 1790
                 esc_html__(
1791 1791
                     'Something went wrong with help popup content generation. Expecting an array and well, this ain\'t no array bub.',
@@ -1817,8 +1817,8 @@  discard block
 block discarded – undo
1817 1817
         // 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
1818 1818
         $help_array   = $this->_get_help_content();
1819 1819
         $help_content = '';
1820
-        if (empty($help_array) || ! isset($help_array[ $trigger_id ])) {
1821
-            $help_array[ $trigger_id ] = [
1820
+        if (empty($help_array) || ! isset($help_array[$trigger_id])) {
1821
+            $help_array[$trigger_id] = [
1822 1822
                 'title'   => esc_html__('Missing Content', 'event_espresso'),
1823 1823
                 'content' => esc_html__(
1824 1824
                     '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.)',
@@ -1913,7 +1913,7 @@  discard block
 block discarded – undo
1913 1913
 
1914 1914
         add_filter(
1915 1915
             'admin_body_class',
1916
-            function ($classes) {
1916
+            function($classes) {
1917 1917
                 if (strpos($classes, 'espresso-admin') === false) {
1918 1918
                     $classes .= ' espresso-admin';
1919 1919
                 }
@@ -2004,12 +2004,12 @@  discard block
 block discarded – undo
2004 2004
     protected function _set_list_table()
2005 2005
     {
2006 2006
         // first is this a list_table view?
2007
-        if (! isset($this->_route_config['list_table'])) {
2007
+        if ( ! isset($this->_route_config['list_table'])) {
2008 2008
             return;
2009 2009
         } //not a list_table view so get out.
2010 2010
         // list table functions are per view specific (because some admin pages might have more than one list table!)
2011
-        $list_table_view = '_set_list_table_views_' . $this->_req_action;
2012
-        if (! method_exists($this, $list_table_view) || $this->{$list_table_view}() === false) {
2011
+        $list_table_view = '_set_list_table_views_'.$this->_req_action;
2012
+        if ( ! method_exists($this, $list_table_view) || $this->{$list_table_view}() === false) {
2013 2013
             // user error msg
2014 2014
             $error_msg = esc_html__(
2015 2015
                 'An error occurred. The requested list table views could not be found.',
@@ -2029,10 +2029,10 @@  discard block
 block discarded – undo
2029 2029
         }
2030 2030
         // let's provide the ability to filter the views per PAGE AND ROUTE, per PAGE, and globally
2031 2031
         $this->_views = apply_filters(
2032
-            'FHEE_list_table_views_' . $this->page_slug . '_' . $this->_req_action,
2032
+            'FHEE_list_table_views_'.$this->page_slug.'_'.$this->_req_action,
2033 2033
             $this->_views
2034 2034
         );
2035
-        $this->_views = apply_filters('FHEE_list_table_views_' . $this->page_slug, $this->_views);
2035
+        $this->_views = apply_filters('FHEE_list_table_views_'.$this->page_slug, $this->_views);
2036 2036
         $this->_views = apply_filters('FHEE_list_table_views', $this->_views);
2037 2037
         $this->_set_list_table_view();
2038 2038
         $this->_set_list_table_object();
@@ -2067,7 +2067,7 @@  discard block
 block discarded – undo
2067 2067
     protected function _set_list_table_object()
2068 2068
     {
2069 2069
         if (isset($this->_route_config['list_table'])) {
2070
-            if (! class_exists($this->_route_config['list_table'])) {
2070
+            if ( ! class_exists($this->_route_config['list_table'])) {
2071 2071
                 throw new EE_Error(
2072 2072
                     sprintf(
2073 2073
                         esc_html__(
@@ -2105,17 +2105,17 @@  discard block
 block discarded – undo
2105 2105
         foreach ($this->_views as $key => $view) {
2106 2106
             $query_args = [];
2107 2107
             // check for current view
2108
-            $this->_views[ $key ]['class']               = $this->_view === $view['slug'] ? 'current' : '';
2108
+            $this->_views[$key]['class']               = $this->_view === $view['slug'] ? 'current' : '';
2109 2109
             $query_args['action']                        = $this->_req_action;
2110
-            $query_args[ $this->_req_action . '_nonce' ] = wp_create_nonce($query_args['action'] . '_nonce');
2110
+            $query_args[$this->_req_action.'_nonce'] = wp_create_nonce($query_args['action'].'_nonce');
2111 2111
             $query_args['status']                        = $view['slug'];
2112 2112
             // merge any other arguments sent in.
2113
-            if (isset($extra_query_args[ $view['slug'] ])) {
2114
-                foreach ($extra_query_args[ $view['slug'] ] as $extra_query_arg) {
2113
+            if (isset($extra_query_args[$view['slug']])) {
2114
+                foreach ($extra_query_args[$view['slug']] as $extra_query_arg) {
2115 2115
                     $query_args[] = $extra_query_arg;
2116 2116
                 }
2117 2117
             }
2118
-            $this->_views[ $key ]['url'] = EE_Admin_Page::add_query_args_and_nonce($query_args, $this->_admin_base_url);
2118
+            $this->_views[$key]['url'] = EE_Admin_Page::add_query_args_and_nonce($query_args, $this->_admin_base_url);
2119 2119
         }
2120 2120
         return $this->_views;
2121 2121
     }
@@ -2146,14 +2146,14 @@  discard block
 block discarded – undo
2146 2146
 					<select id="entries-per-page-slct" name="entries-per-page-slct">';
2147 2147
         foreach ($values as $value) {
2148 2148
             if ($value < $max_entries) {
2149
-                $selected                  = $value === $per_page ? ' selected="' . $per_page . '"' : '';
2149
+                $selected = $value === $per_page ? ' selected="'.$per_page.'"' : '';
2150 2150
                 $entries_per_page_dropdown .= '
2151
-						<option value="' . $value . '"' . $selected . '>' . $value . '&nbsp;&nbsp;</option>';
2151
+						<option value="' . $value.'"'.$selected.'>'.$value.'&nbsp;&nbsp;</option>';
2152 2152
             }
2153 2153
         }
2154
-        $selected                  = $max_entries === $per_page ? ' selected="' . $per_page . '"' : '';
2154
+        $selected = $max_entries === $per_page ? ' selected="'.$per_page.'"' : '';
2155 2155
         $entries_per_page_dropdown .= '
2156
-						<option value="' . $max_entries . '"' . $selected . '>All&nbsp;&nbsp;</option>';
2156
+						<option value="' . $max_entries.'"'.$selected.'>All&nbsp;&nbsp;</option>';
2157 2157
         $entries_per_page_dropdown .= '
2158 2158
 					</select>
2159 2159
 					entries
@@ -2177,7 +2177,7 @@  discard block
 block discarded – undo
2177 2177
             empty($this->_search_btn_label) ? $this->page_label
2178 2178
                 : $this->_search_btn_label
2179 2179
         );
2180
-        $this->_template_args['search']['callback']  = 'search_' . $this->page_slug;
2180
+        $this->_template_args['search']['callback'] = 'search_'.$this->page_slug;
2181 2181
     }
2182 2182
 
2183 2183
 
@@ -2265,7 +2265,7 @@  discard block
 block discarded – undo
2265 2265
             $total_columns                                       = ! empty($screen_columns)
2266 2266
                 ? $screen_columns
2267 2267
                 : $this->_route_config['columns'][1];
2268
-            $this->_template_args['current_screen_widget_class'] = 'columns-' . $total_columns;
2268
+            $this->_template_args['current_screen_widget_class'] = 'columns-'.$total_columns;
2269 2269
             $this->_template_args['current_page']                = $this->_wp_page_slug;
2270 2270
             $this->_template_args['screen']                      = $this->_current_screen;
2271 2271
             $this->_column_template_path                         = EE_ADMIN_TEMPLATE
@@ -2311,7 +2311,7 @@  discard block
 block discarded – undo
2311 2311
      */
2312 2312
     protected function _espresso_ratings_request()
2313 2313
     {
2314
-        if (! apply_filters('FHEE_show_ratings_request_meta_box', true)) {
2314
+        if ( ! apply_filters('FHEE_show_ratings_request_meta_box', true)) {
2315 2315
             return;
2316 2316
         }
2317 2317
         $ratings_box_title = apply_filters(
@@ -2338,28 +2338,28 @@  discard block
 block discarded – undo
2338 2338
      */
2339 2339
     public function espresso_ratings_request()
2340 2340
     {
2341
-        EEH_Template::display_template(EE_ADMIN_TEMPLATE . 'espresso_ratings_request_content.template.php');
2341
+        EEH_Template::display_template(EE_ADMIN_TEMPLATE.'espresso_ratings_request_content.template.php');
2342 2342
     }
2343 2343
 
2344 2344
 
2345 2345
     public static function cached_rss_display($rss_id, $url)
2346 2346
     {
2347
-        $loading   = '<p class="widget-loading hide-if-no-js">'
2347
+        $loading = '<p class="widget-loading hide-if-no-js">'
2348 2348
                      . esc_html__('Loading&#8230;', 'event_espresso')
2349 2349
                      . '</p><p class="hide-if-js">'
2350 2350
                      . esc_html__('This widget requires JavaScript.', 'event_espresso')
2351 2351
                      . '</p>';
2352
-        $pre       = '<div class="espresso-rss-display">' . "\n\t";
2353
-        $pre       .= '<span id="' . esc_attr($rss_id) . '_url" class="hidden">' . esc_url_raw($url) . '</span>';
2354
-        $post      = '</div>' . "\n";
2355
-        $cache_key = 'ee_rss_' . md5($rss_id);
2352
+        $pre       = '<div class="espresso-rss-display">'."\n\t";
2353
+        $pre .= '<span id="'.esc_attr($rss_id).'_url" class="hidden">'.esc_url_raw($url).'</span>';
2354
+        $post      = '</div>'."\n";
2355
+        $cache_key = 'ee_rss_'.md5($rss_id);
2356 2356
         $output    = get_transient($cache_key);
2357 2357
         if ($output !== false) {
2358
-            echo wp_kses($pre . $output . $post, AllowedTags::getWithFormTags());
2358
+            echo wp_kses($pre.$output.$post, AllowedTags::getWithFormTags());
2359 2359
             return true;
2360 2360
         }
2361
-        if (! (defined('DOING_AJAX') && DOING_AJAX)) {
2362
-            echo wp_kses($pre . $loading . $post, AllowedTags::getWithFormTags());
2361
+        if ( ! (defined('DOING_AJAX') && DOING_AJAX)) {
2362
+            echo wp_kses($pre.$loading.$post, AllowedTags::getWithFormTags());
2363 2363
             return false;
2364 2364
         }
2365 2365
         ob_start();
@@ -2426,19 +2426,19 @@  discard block
 block discarded – undo
2426 2426
     public function espresso_sponsors_post_box()
2427 2427
     {
2428 2428
         EEH_Template::display_template(
2429
-            EE_ADMIN_TEMPLATE . 'admin_general_metabox_contents_espresso_sponsors.template.php'
2429
+            EE_ADMIN_TEMPLATE.'admin_general_metabox_contents_espresso_sponsors.template.php'
2430 2430
         );
2431 2431
     }
2432 2432
 
2433 2433
 
2434 2434
     private function _publish_post_box()
2435 2435
     {
2436
-        $meta_box_ref = 'espresso_' . $this->page_slug . '_editor_overview';
2436
+        $meta_box_ref = 'espresso_'.$this->page_slug.'_editor_overview';
2437 2437
         // if there is a array('label' => array('publishbox' => 'some title') ) present in the _page_config array
2438 2438
         // then we'll use that for the metabox label.
2439 2439
         // Otherwise we'll just use publish (publishbox itself could be an array of labels indexed by routes)
2440
-        if (! empty($this->_labels['publishbox'])) {
2441
-            $box_label = is_array($this->_labels['publishbox']) ? $this->_labels['publishbox'][ $this->_req_action ]
2440
+        if ( ! empty($this->_labels['publishbox'])) {
2441
+            $box_label = is_array($this->_labels['publishbox']) ? $this->_labels['publishbox'][$this->_req_action]
2442 2442
                 : $this->_labels['publishbox'];
2443 2443
         } else {
2444 2444
             $box_label = esc_html__('Publish', 'event_espresso');
@@ -2467,7 +2467,7 @@  discard block
 block discarded – undo
2467 2467
             ? $this->_template_args['publish_box_extra_content']
2468 2468
             : '';
2469 2469
         echo EEH_Template::display_template(
2470
-            EE_ADMIN_TEMPLATE . 'admin_details_publish_metabox.template.php',
2470
+            EE_ADMIN_TEMPLATE.'admin_details_publish_metabox.template.php',
2471 2471
             $this->_template_args,
2472 2472
             true
2473 2473
         );
@@ -2555,18 +2555,18 @@  discard block
 block discarded – undo
2555 2555
             );
2556 2556
         }
2557 2557
         $this->_template_args['publish_delete_link'] = $delete_link;
2558
-        if (! empty($name) && ! empty($id)) {
2559
-            $hidden_field_arr[ $name ] = [
2558
+        if ( ! empty($name) && ! empty($id)) {
2559
+            $hidden_field_arr[$name] = [
2560 2560
                 'type'  => 'hidden',
2561 2561
                 'value' => $id,
2562 2562
             ];
2563
-            $hf                        = $this->_generate_admin_form_fields($hidden_field_arr, 'array');
2563
+            $hf = $this->_generate_admin_form_fields($hidden_field_arr, 'array');
2564 2564
         } else {
2565 2565
             $hf = '';
2566 2566
         }
2567 2567
         // add hidden field
2568 2568
         $this->_template_args['publish_hidden_fields'] = is_array($hf) && ! empty($name)
2569
-            ? $hf[ $name ]['field']
2569
+            ? $hf[$name]['field']
2570 2570
             : $hf;
2571 2571
     }
2572 2572
 
@@ -2668,7 +2668,7 @@  discard block
 block discarded – undo
2668 2668
         }
2669 2669
         // 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)
2670 2670
         $call_back_func = $create_func
2671
-            ? static function ($post, $metabox) {
2671
+            ? static function($post, $metabox) {
2672 2672
                 do_action('AHEE_log', __FILE__, __FUNCTION__, '');
2673 2673
                 echo EEH_Template::display_template(
2674 2674
                     $metabox['args']['template_path'],
@@ -2678,7 +2678,7 @@  discard block
 block discarded – undo
2678 2678
             }
2679 2679
             : $callback;
2680 2680
         $this->addMetaBox(
2681
-            str_replace('_', '-', $action) . '-mbox',
2681
+            str_replace('_', '-', $action).'-mbox',
2682 2682
             $title,
2683 2683
             $call_back_func,
2684 2684
             $this->_wp_page_slug,
@@ -2795,13 +2795,13 @@  discard block
 block discarded – undo
2795 2795
             'event-espresso_page_espresso_',
2796 2796
             '',
2797 2797
             $this->_wp_page_slug
2798
-        ) . ' ' . $this->_req_action . '-route';
2798
+        ).' '.$this->_req_action.'-route';
2799 2799
 
2800 2800
         $template_path = $sidebar
2801 2801
             ? EE_ADMIN_TEMPLATE . 'admin_details_wrapper.template.php'
2802
-            : EE_ADMIN_TEMPLATE . 'admin_details_wrapper_no_sidebar.template.php';
2802
+            : EE_ADMIN_TEMPLATE.'admin_details_wrapper_no_sidebar.template.php';
2803 2803
         if ($this->request->isAjax()) {
2804
-            $template_path = EE_ADMIN_TEMPLATE . 'admin_details_wrapper_no_sidebar_ajax.template.php';
2804
+            $template_path = EE_ADMIN_TEMPLATE.'admin_details_wrapper_no_sidebar_ajax.template.php';
2805 2805
         }
2806 2806
         $template_path = ! empty($this->_column_template_path) ? $this->_column_template_path : $template_path;
2807 2807
 
@@ -2835,11 +2835,11 @@  discard block
 block discarded – undo
2835 2835
     public function display_admin_caf_preview_page($utm_campaign_source = '', $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
-        $this->_template_args['list_table_hidden_fields']        = $hidden_form_fields;
2953
+        $this->_template_args['list_table_hidden_fields'] = $hidden_form_fields;
2954 2954
         // display message about search results?
2955 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
         );
@@ -3162,16 +3162,16 @@  discard block
 block discarded – undo
3162 3162
             $this->_template_args['before_admin_page_content'] ?? ''
3163 3163
         );
3164 3164
 
3165
-        $this->_template_args['after_admin_page_content']  = apply_filters(
3165
+        $this->_template_args['after_admin_page_content'] = apply_filters(
3166 3166
             "FHEE_after_admin_page_content{$this->_current_page}{$this->_current_view}",
3167 3167
             $this->_template_args['after_admin_page_content'] ?? ''
3168 3168
         );
3169
-        $this->_template_args['after_admin_page_content']  .= $this->_set_help_popup_content();
3169
+        $this->_template_args['after_admin_page_content'] .= $this->_set_help_popup_content();
3170 3170
 
3171 3171
         if ($this->request->isAjax()) {
3172 3172
             $this->_template_args['admin_page_content'] = EEH_Template::display_template(
3173 3173
                 // $template_path,
3174
-                EE_ADMIN_TEMPLATE . 'admin_wrapper_ajax.template.php',
3174
+                EE_ADMIN_TEMPLATE.'admin_wrapper_ajax.template.php',
3175 3175
                 $this->_template_args,
3176 3176
                 true
3177 3177
             );
@@ -3180,7 +3180,7 @@  discard block
 block discarded – undo
3180 3180
         // load settings page wrapper template
3181 3181
         $template_path = $about
3182 3182
             ? EE_ADMIN_TEMPLATE . 'about_admin_wrapper.template.php'
3183
-            : EE_ADMIN_TEMPLATE . 'admin_wrapper.template.php';
3183
+            : EE_ADMIN_TEMPLATE.'admin_wrapper.template.php';
3184 3184
 
3185 3185
         EEH_Template::display_template($template_path, $this->_template_args);
3186 3186
     }
@@ -3264,12 +3264,12 @@  discard block
 block discarded – undo
3264 3264
         $default_names = ['save', 'save_and_close'];
3265 3265
         $buttons = '';
3266 3266
         foreach ($button_text as $key => $button) {
3267
-            $ref     = $default_names[ $key ];
3268
-            $name    = ! empty($actions) ? $actions[ $key ] : $ref;
3269
-            $buttons .= '<input type="submit" class="button button--primary ' . $ref . '" '
3270
-                        . 'value="' . $button . '" name="' . $name . '" '
3271
-                        . 'id="' . $this->_current_view . '_' . $ref . '" />';
3272
-            if (! $both) {
3267
+            $ref     = $default_names[$key];
3268
+            $name    = ! empty($actions) ? $actions[$key] : $ref;
3269
+            $buttons .= '<input type="submit" class="button button--primary '.$ref.'" '
3270
+                        . 'value="'.$button.'" name="'.$name.'" '
3271
+                        . 'id="'.$this->_current_view.'_'.$ref.'" />';
3272
+            if ( ! $both) {
3273 3273
                 break;
3274 3274
             }
3275 3275
         }
@@ -3309,13 +3309,13 @@  discard block
 block discarded – undo
3309 3309
                 'An error occurred. No action was set for this page\'s form.',
3310 3310
                 'event_espresso'
3311 3311
             );
3312
-            $dev_msg  = $user_msg . "\n"
3312
+            $dev_msg = $user_msg."\n"
3313 3313
                         . sprintf(
3314 3314
                             esc_html__('The $route argument is required for the %s->%s method.', 'event_espresso'),
3315 3315
                             __FUNCTION__,
3316 3316
                             __CLASS__
3317 3317
                         );
3318
-            EE_Error::add_error($user_msg . '||' . $dev_msg, __FILE__, __FUNCTION__, __LINE__);
3318
+            EE_Error::add_error($user_msg.'||'.$dev_msg, __FILE__, __FUNCTION__, __LINE__);
3319 3319
         }
3320 3320
         // open form
3321 3321
         $action = $this->_admin_base_url;
@@ -3323,9 +3323,9 @@  discard block
 block discarded – undo
3323 3323
             <form name='form' method='post' action='{$action}' id='{$route}_event_form' class='ee-admin-page-form' >
3324 3324
             ";
3325 3325
         // add nonce
3326
-        $nonce                                             =
3327
-            wp_nonce_field($route . '_nonce', $route . '_nonce', false, false);
3328
-        $this->_template_args['before_admin_page_content'] .= "\n\t" . $nonce;
3326
+        $nonce =
3327
+            wp_nonce_field($route.'_nonce', $route.'_nonce', false, false);
3328
+        $this->_template_args['before_admin_page_content'] .= "\n\t".$nonce;
3329 3329
         // add REQUIRED form action
3330 3330
         $hidden_fields = [
3331 3331
             'action' => ['type' => 'hidden', 'value' => $route],
@@ -3338,7 +3338,7 @@  discard block
 block discarded – undo
3338 3338
         $form_fields = $this->_generate_admin_form_fields($hidden_fields, 'array');
3339 3339
         // add fields to form
3340 3340
         foreach ((array) $form_fields as $form_field) {
3341
-            $this->_template_args['before_admin_page_content'] .= "\n\t" . $form_field['field'];
3341
+            $this->_template_args['before_admin_page_content'] .= "\n\t".$form_field['field'];
3342 3342
         }
3343 3343
         // close form
3344 3344
         $this->_template_args['after_admin_page_content'] = '</form>';
@@ -3421,12 +3421,12 @@  discard block
 block discarded – undo
3421 3421
         bool $override_overwrite = false
3422 3422
     ) {
3423 3423
         do_action('AHEE_log', __FILE__, __FUNCTION__, '');
3424
-        $notices      = EE_Error::get_notices(false);
3424
+        $notices = EE_Error::get_notices(false);
3425 3425
         // overwrite default success messages //BUT ONLY if overwrite not overridden
3426
-        if (! $override_overwrite || ! empty($notices['errors'])) {
3426
+        if ( ! $override_overwrite || ! empty($notices['errors'])) {
3427 3427
             EE_Error::overwrite_success();
3428 3428
         }
3429
-        if (! $override_overwrite && ! empty($what) && ! empty($action_desc) && empty($notices['errors'])) {
3429
+        if ( ! $override_overwrite && ! empty($what) && ! empty($action_desc) && empty($notices['errors'])) {
3430 3430
             // how many records affected ? more than one record ? or just one ?
3431 3431
             EE_Error::add_success(
3432 3432
                 sprintf(
@@ -3447,7 +3447,7 @@  discard block
 block discarded – undo
3447 3447
             );
3448 3448
         }
3449 3449
         // check that $query_args isn't something crazy
3450
-        if (! is_array($query_args)) {
3450
+        if ( ! is_array($query_args)) {
3451 3451
             $query_args = [];
3452 3452
         }
3453 3453
         /**
@@ -3479,7 +3479,7 @@  discard block
 block discarded – undo
3479 3479
             $redirect_url = admin_url('admin.php');
3480 3480
         }
3481 3481
         // merge any default query_args set in _default_route_query_args property
3482
-        if (! empty($this->_default_route_query_args) && ! $this->_is_UI_request) {
3482
+        if ( ! empty($this->_default_route_query_args) && ! $this->_is_UI_request) {
3483 3483
             $args_to_merge = [];
3484 3484
             foreach ($this->_default_route_query_args as $query_param => $query_value) {
3485 3485
                 // is there a wp_referer array in our _default_route_query_args property?
@@ -3491,15 +3491,15 @@  discard block
 block discarded – undo
3491 3491
                         }
3492 3492
                         // finally we will override any arguments in the referer with
3493 3493
                         // what might be set on the _default_route_query_args array.
3494
-                        if (isset($this->_default_route_query_args[ $reference ])) {
3495
-                            $args_to_merge[ $reference ] = urlencode($this->_default_route_query_args[ $reference ]);
3494
+                        if (isset($this->_default_route_query_args[$reference])) {
3495
+                            $args_to_merge[$reference] = urlencode($this->_default_route_query_args[$reference]);
3496 3496
                         } else {
3497
-                            $args_to_merge[ $reference ] = urlencode($value);
3497
+                            $args_to_merge[$reference] = urlencode($value);
3498 3498
                         }
3499 3499
                     }
3500 3500
                     continue;
3501 3501
                 }
3502
-                $args_to_merge[ $query_param ] = $query_value;
3502
+                $args_to_merge[$query_param] = $query_value;
3503 3503
             }
3504 3504
             // now let's merge these arguments but override with what was specifically sent in to the
3505 3505
             // redirect.
@@ -3511,19 +3511,19 @@  discard block
 block discarded – undo
3511 3511
         if (isset($query_args['action'])) {
3512 3512
             // manually generate wp_nonce and merge that with the query vars
3513 3513
             // becuz the wp_nonce_url function wrecks havoc on some vars
3514
-            $query_args['_wpnonce'] = wp_create_nonce($query_args['action'] . '_nonce');
3514
+            $query_args['_wpnonce'] = wp_create_nonce($query_args['action'].'_nonce');
3515 3515
         }
3516 3516
         // we're adding some hooks and filters in here for processing any things just before redirects
3517 3517
         // (example: an admin page has done an insert or update and we want to run something after that).
3518
-        do_action('AHEE_redirect_' . $this->class_name . $this->_req_action, $query_args);
3518
+        do_action('AHEE_redirect_'.$this->class_name.$this->_req_action, $query_args);
3519 3519
         $redirect_url = apply_filters(
3520
-            'FHEE_redirect_' . $this->class_name . $this->_req_action,
3520
+            'FHEE_redirect_'.$this->class_name.$this->_req_action,
3521 3521
             EE_Admin_Page::add_query_args_and_nonce($query_args, $redirect_url),
3522 3522
             $query_args
3523 3523
         );
3524 3524
         // check if we're doing ajax.  If we are then lets just return the results and js can handle how it wants.
3525 3525
         if ($this->request->isAjax()) {
3526
-            $default_data                    = [
3526
+            $default_data = [
3527 3527
                 'close'        => true,
3528 3528
                 'redirect_url' => $redirect_url,
3529 3529
                 'where'        => 'main',
@@ -3573,7 +3573,7 @@  discard block
 block discarded – undo
3573 3573
         }
3574 3574
         $this->_template_args['notices'] = EE_Error::get_notices();
3575 3575
         // IF this isn't ajax we need to create a transient for the notices using the route (however, overridden if $sticky_notices == true)
3576
-        if (! $this->request->isAjax() || $sticky_notices) {
3576
+        if ( ! $this->request->isAjax() || $sticky_notices) {
3577 3577
             $route = isset($query_args['action']) ? $query_args['action'] : 'default';
3578 3578
             $this->_add_transient(
3579 3579
                 $route,
@@ -3613,7 +3613,7 @@  discard block
 block discarded – undo
3613 3613
         $exclude_nonce = false
3614 3614
     ) {
3615 3615
         // first let's validate the action (if $base_url is FALSE otherwise validation will happen further along)
3616
-        if (empty($base_url) && ! isset($this->_page_routes[ $action ])) {
3616
+        if (empty($base_url) && ! isset($this->_page_routes[$action])) {
3617 3617
             throw new EE_Error(
3618 3618
                 sprintf(
3619 3619
                     esc_html__(
@@ -3624,7 +3624,7 @@  discard block
 block discarded – undo
3624 3624
                 )
3625 3625
             );
3626 3626
         }
3627
-        if (! isset($this->_labels['buttons'][ $type ])) {
3627
+        if ( ! isset($this->_labels['buttons'][$type])) {
3628 3628
             throw new EE_Error(
3629 3629
                 sprintf(
3630 3630
                     esc_html__(
@@ -3637,7 +3637,7 @@  discard block
 block discarded – undo
3637 3637
         }
3638 3638
         // finally check user access for this button.
3639 3639
         $has_access = $this->check_user_access($action, true);
3640
-        if (! $has_access) {
3640
+        if ( ! $has_access) {
3641 3641
             return '';
3642 3642
         }
3643 3643
         $_base_url  = ! $base_url ? $this->_admin_base_url : $base_url;
@@ -3645,11 +3645,11 @@  discard block
 block discarded – undo
3645 3645
             'action' => $action,
3646 3646
         ];
3647 3647
         // merge extra_request args but make sure our original action takes precedence and doesn't get overwritten.
3648
-        if (! empty($extra_request)) {
3648
+        if ( ! empty($extra_request)) {
3649 3649
             $query_args = array_merge($extra_request, $query_args);
3650 3650
         }
3651 3651
         $url = EE_Admin_Page::add_query_args_and_nonce($query_args, $_base_url, false, $exclude_nonce);
3652
-        return EEH_Template::get_button_or_link($url, $this->_labels['buttons'][ $type ], $class);
3652
+        return EEH_Template::get_button_or_link($url, $this->_labels['buttons'][$type], $class);
3653 3653
     }
3654 3654
 
3655 3655
 
@@ -3675,7 +3675,7 @@  discard block
 block discarded – undo
3675 3675
                 'FHEE__EE_Admin_Page___per_page_screen_options__default',
3676 3676
                 20
3677 3677
             ),
3678
-            'option'  => $this->_current_page . '_' . $this->_current_view . '_per_page',
3678
+            'option'  => $this->_current_page.'_'.$this->_current_view.'_per_page',
3679 3679
         ];
3680 3680
         // ONLY add the screen option if the user has access to it.
3681 3681
         if ($this->check_user_access($this->_current_view, true)) {
@@ -3696,18 +3696,18 @@  discard block
 block discarded – undo
3696 3696
     {
3697 3697
         if ($this->request->requestParamIsSet('wp_screen_options')) {
3698 3698
             check_admin_referer('screen-options-nonce', 'screenoptionnonce');
3699
-            if (! $user = wp_get_current_user()) {
3699
+            if ( ! $user = wp_get_current_user()) {
3700 3700
                 return;
3701 3701
             }
3702 3702
             $option = $this->request->getRequestParam('wp_screen_options[option]', '', 'key');
3703
-            if (! $option) {
3703
+            if ( ! $option) {
3704 3704
                 return;
3705 3705
             }
3706
-            $value  = $this->request->getRequestParam('wp_screen_options[value]', 0, 'int');
3706
+            $value = $this->request->getRequestParam('wp_screen_options[value]', 0, 'int');
3707 3707
             $map_option = $option;
3708 3708
             $option     = str_replace('-', '_', $option);
3709 3709
             switch ($map_option) {
3710
-                case $this->_current_page . '_' . $this->_current_view . '_per_page':
3710
+                case $this->_current_page.'_'.$this->_current_view.'_per_page':
3711 3711
                     $max_value = apply_filters(
3712 3712
                         'FHEE__EE_Admin_Page___set_per_page_screen_options__max_value',
3713 3713
                         999,
@@ -3764,13 +3764,13 @@  discard block
 block discarded – undo
3764 3764
     protected function _add_transient($route, $data, $notices = false, $skip_route_verify = false)
3765 3765
     {
3766 3766
         $user_id = get_current_user_id();
3767
-        if (! $skip_route_verify) {
3767
+        if ( ! $skip_route_verify) {
3768 3768
             $this->_verify_route($route);
3769 3769
         }
3770 3770
         // now let's set the string for what kind of transient we're setting
3771 3771
         $transient = $notices
3772
-            ? 'ee_rte_n_tx_' . $route . '_' . $user_id
3773
-            : 'rte_tx_' . $route . '_' . $user_id;
3772
+            ? 'ee_rte_n_tx_'.$route.'_'.$user_id
3773
+            : 'rte_tx_'.$route.'_'.$user_id;
3774 3774
         $data      = $notices ? ['notices' => $data] : $data;
3775 3775
         // is there already a transient for this route?  If there is then let's ADD to that transient
3776 3776
         $existing = is_multisite() && is_network_admin()
@@ -3799,8 +3799,8 @@  discard block
 block discarded – undo
3799 3799
         $user_id   = get_current_user_id();
3800 3800
         $route     = ! $route ? $this->_req_action : $route;
3801 3801
         $transient = $notices
3802
-            ? 'ee_rte_n_tx_' . $route . '_' . $user_id
3803
-            : 'rte_tx_' . $route . '_' . $user_id;
3802
+            ? 'ee_rte_n_tx_'.$route.'_'.$user_id
3803
+            : 'rte_tx_'.$route.'_'.$user_id;
3804 3804
         $data      = is_multisite() && is_network_admin()
3805 3805
             ? get_site_transient($transient)
3806 3806
             : get_transient($transient);
@@ -4036,7 +4036,7 @@  discard block
 block discarded – undo
4036 4036
      */
4037 4037
     protected function _next_link($url, $class = 'dashicons dashicons-arrow-right')
4038 4038
     {
4039
-        return '<a class="' . $class . '" href="' . $url . '"></a>';
4039
+        return '<a class="'.$class.'" href="'.$url.'"></a>';
4040 4040
     }
4041 4041
 
4042 4042
 
@@ -4049,7 +4049,7 @@  discard block
 block discarded – undo
4049 4049
      */
4050 4050
     protected function _previous_link($url, $class = 'dashicons dashicons-arrow-left')
4051 4051
     {
4052
-        return '<a class="' . $class . '" href="' . $url . '"></a>';
4052
+        return '<a class="'.$class.'" href="'.$url.'"></a>';
4053 4053
     }
4054 4054
 
4055 4055
 
@@ -4197,7 +4197,7 @@  discard block
 block discarded – undo
4197 4197
         ?callable $callback = null
4198 4198
     ): bool {
4199 4199
         $entity_ID = absint($entity_ID);
4200
-        if (! $entity_ID) {
4200
+        if ( ! $entity_ID) {
4201 4201
             $this->trashRestoreDeleteError($action, $entity_model);
4202 4202
         }
4203 4203
         $result = 0;
@@ -4243,7 +4243,7 @@  discard block
 block discarded – undo
4243 4243
                 )
4244 4244
             );
4245 4245
         }
4246
-        if (! $entity_model->has_field($delete_column)) {
4246
+        if ( ! $entity_model->has_field($delete_column)) {
4247 4247
             throw new DomainException(
4248 4248
                 sprintf(
4249 4249
                     esc_html__(
Please login to merge, or discard this patch.
core/services/commands/CommandHandler.php 2 patches
Indentation   +22 added lines, -22 removed lines patch added patch discarded remove patch
@@ -14,26 +14,26 @@
 block discarded – undo
14 14
  */
15 15
 abstract class CommandHandler implements CommandHandlerInterface
16 16
 {
17
-    /**
18
-     * Verifies the Command class matches the Handler class
19
-     * by simply removing "Handler" from the Command class and then comparing.
20
-     * IF the Command Handler has been changed via CommandHandlerManager::addCommandHandler,
21
-     * or via the filter in CommandHandlerManager::getCommandHandler(),
22
-     * then this method MUST be overridden in the new Command Handler class.
23
-     * PLZ NOTE: that it also needs to return itself ($this)
24
-     * because the CommandBus utilizes method chaining.
25
-     *
26
-     * @param CommandInterface $command
27
-     * @return CommandHandlerInterface
28
-     * @throws InvalidEntityException
29
-     * @since 4.9.80.p
30
-     */
31
-    public function verify(CommandInterface $command): CommandHandlerInterface
32
-    {
33
-        $expected = str_replace('CommandHandler', 'Command', get_class($this));
34
-        if (! $command instanceof $expected) {
35
-            throw new InvalidEntityException($command, $expected);
36
-        }
37
-        return $this;
38
-    }
17
+	/**
18
+	 * Verifies the Command class matches the Handler class
19
+	 * by simply removing "Handler" from the Command class and then comparing.
20
+	 * IF the Command Handler has been changed via CommandHandlerManager::addCommandHandler,
21
+	 * or via the filter in CommandHandlerManager::getCommandHandler(),
22
+	 * then this method MUST be overridden in the new Command Handler class.
23
+	 * PLZ NOTE: that it also needs to return itself ($this)
24
+	 * because the CommandBus utilizes method chaining.
25
+	 *
26
+	 * @param CommandInterface $command
27
+	 * @return CommandHandlerInterface
28
+	 * @throws InvalidEntityException
29
+	 * @since 4.9.80.p
30
+	 */
31
+	public function verify(CommandInterface $command): CommandHandlerInterface
32
+	{
33
+		$expected = str_replace('CommandHandler', 'Command', get_class($this));
34
+		if (! $command instanceof $expected) {
35
+			throw new InvalidEntityException($command, $expected);
36
+		}
37
+		return $this;
38
+	}
39 39
 }
Please login to merge, or discard this patch.
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -31,7 +31,7 @@
 block discarded – undo
31 31
     public function verify(CommandInterface $command): CommandHandlerInterface
32 32
     {
33 33
         $expected = str_replace('CommandHandler', 'Command', get_class($this));
34
-        if (! $command instanceof $expected) {
34
+        if ( ! $command instanceof $expected) {
35 35
             throw new InvalidEntityException($command, $expected);
36 36
         }
37 37
         return $this;
Please login to merge, or discard this patch.
core/services/commands/CommandHandlerInterface.php 1 patch
Indentation   +19 added lines, -19 removed lines patch added patch discarded remove patch
@@ -9,24 +9,24 @@
 block discarded – undo
9 9
  */
10 10
 interface CommandHandlerInterface
11 11
 {
12
-    /**
13
-     * verifies that the supplied command is the correct class for the handler.
14
-     *
15
-     * !!! IMPORTANT !!!
16
-     * Must return $this (ie: the handler itself)
17
-     * as the CommandBus utilizes method chaining
18
-     *
19
-     * @param CommandInterface $command
20
-     * @return CommandHandlerInterface
21
-     * @since 4.9.80.p
22
-     */
23
-    public function verify(CommandInterface $command): CommandHandlerInterface;
12
+	/**
13
+	 * verifies that the supplied command is the correct class for the handler.
14
+	 *
15
+	 * !!! IMPORTANT !!!
16
+	 * Must return $this (ie: the handler itself)
17
+	 * as the CommandBus utilizes method chaining
18
+	 *
19
+	 * @param CommandInterface $command
20
+	 * @return CommandHandlerInterface
21
+	 * @since 4.9.80.p
22
+	 */
23
+	public function verify(CommandInterface $command): CommandHandlerInterface;
24 24
 
25
-    /**
26
-     * Performs the command handler's logic.
27
-     *
28
-     * @param CommandInterface $command
29
-     * @return mixed
30
-     */
31
-    public function handle(CommandInterface $command);
25
+	/**
26
+	 * Performs the command handler's logic.
27
+	 *
28
+	 * @param CommandInterface $command
29
+	 * @return mixed
30
+	 */
31
+	public function handle(CommandInterface $command);
32 32
 }
Please login to merge, or discard this patch.
core/db_models/EEM_Base.model.php 1 patch
Indentation   +6532 added lines, -6532 removed lines patch added patch discarded remove patch
@@ -39,6538 +39,6538 @@
 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 = array();
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
-     * @var string |null
146
-     */
147
-    protected $model_chain_to_password = null;
148
-
149
-    /**
150
-     * This is a flag typically set by updates so that we don't load the where strategy on updates because updates
151
-     * don't need it (particularly CPT models)
152
-     *
153
-     * @var bool
154
-     */
155
-    protected $_ignore_where_strategy = false;
156
-
157
-    /**
158
-     * String used in caps relating to this model. Eg, if the caps relating to this
159
-     * model are 'ee_edit_events', 'ee_read_events', etc, it would be 'events'.
160
-     *
161
-     * @var string. If null it hasn't been initialized yet. If false then we
162
-     * have indicated capabilities don't apply to this
163
-     */
164
-    protected $_caps_slug = null;
165
-
166
-    /**
167
-     * 2d array where top-level keys are one of EEM_Base::valid_cap_contexts(),
168
-     * and next-level keys are capability names, and each's value is a
169
-     * EE_Default_Where_Condition. If the requester requests to apply caps to the query,
170
-     * they specify which context to use (ie, frontend, backend, edit or delete)
171
-     * and then each capability in the corresponding sub-array that they're missing
172
-     * adds the where conditions onto the query.
173
-     *
174
-     * @var array
175
-     */
176
-    protected $_cap_restrictions = array(
177
-        self::caps_read       => array(),
178
-        self::caps_read_admin => array(),
179
-        self::caps_edit       => array(),
180
-        self::caps_delete     => array(),
181
-    );
182
-
183
-    /**
184
-     * Array defining which cap restriction generators to use to create default
185
-     * cap restrictions to put in EEM_Base::_cap_restrictions.
186
-     * Array-keys are one of EEM_Base::valid_cap_contexts(), and values are a child of
187
-     * EE_Restriction_Generator_Base. If you don't want any cap restrictions generated
188
-     * automatically set this to false (not just null).
189
-     *
190
-     * @var EE_Restriction_Generator_Base[]
191
-     */
192
-    protected $_cap_restriction_generators = array();
193
-
194
-    /**
195
-     * constants used to categorize capability restrictions on EEM_Base::_caps_restrictions
196
-     */
197
-    const caps_read       = 'read';
198
-
199
-    const caps_read_admin = 'read_admin';
200
-
201
-    const caps_edit       = 'edit';
202
-
203
-    const caps_delete     = 'delete';
204
-
205
-    /**
206
-     * Keys are all the cap contexts (ie constants EEM_Base::_caps_*) and values are their 'action'
207
-     * as how they'd be used in capability names. Eg EEM_Base::caps_read ('read_frontend')
208
-     * maps to 'read' because when looking for relevant permissions we're going to use
209
-     * 'read' in teh capabilities names like 'ee_read_events' etc.
210
-     *
211
-     * @var array
212
-     */
213
-    protected $_cap_contexts_to_cap_action_map = array(
214
-        self::caps_read       => 'read',
215
-        self::caps_read_admin => 'read',
216
-        self::caps_edit       => 'edit',
217
-        self::caps_delete     => 'delete',
218
-    );
219
-
220
-    /**
221
-     * Timezone
222
-     * This gets set via the constructor so that we know what timezone incoming strings|timestamps are in when there
223
-     * are EE_Datetime_Fields in use.  This can also be used before a get to set what timezone you want strings coming
224
-     * out of the created objects.  NOT all EEM_Base child classes use this property but any that use a
225
-     * EE_Datetime_Field data type will have access to it.
226
-     *
227
-     * @var string
228
-     */
229
-    protected $_timezone;
230
-
231
-
232
-    /**
233
-     * This holds the id of the blog currently making the query.  Has no bearing on single site but is used for
234
-     * multisite.
235
-     *
236
-     * @var int
237
-     */
238
-    protected static $_model_query_blog_id;
239
-
240
-    /**
241
-     * A copy of _fields, except the array keys are the model names pointed to by
242
-     * the field
243
-     *
244
-     * @var EE_Model_Field_Base[]
245
-     */
246
-    private $_cache_foreign_key_to_fields = array();
247
-
248
-    /**
249
-     * Cached list of all the fields on the model, indexed by their name
250
-     *
251
-     * @var EE_Model_Field_Base[]
252
-     */
253
-    private $_cached_fields = null;
254
-
255
-    /**
256
-     * Cached list of all the fields on the model, except those that are
257
-     * marked as only pertinent to the database
258
-     *
259
-     * @var EE_Model_Field_Base[]
260
-     */
261
-    private $_cached_fields_non_db_only = null;
262
-
263
-    /**
264
-     * A cached reference to the primary key for quick lookup
265
-     *
266
-     * @var EE_Model_Field_Base
267
-     */
268
-    private $_primary_key_field = null;
269
-
270
-    /**
271
-     * Flag indicating whether this model has a primary key or not
272
-     *
273
-     * @var boolean
274
-     */
275
-    protected $_has_primary_key_field = null;
276
-
277
-    /**
278
-     * array in the format:  [ FK alias => full PK ]
279
-     * where keys are local column name aliases for foreign keys
280
-     * and values are the fully qualified column name for the primary key they represent
281
-     *  ex:
282
-     *      [ 'Event.EVT_wp_user' => 'WP_User.ID' ]
283
-     *
284
-     * @var array $foreign_key_aliases
285
-     */
286
-    protected $foreign_key_aliases = [];
287
-
288
-    /**
289
-     * Whether or not this model is based off a table in WP core only (CPTs should set
290
-     * this to FALSE, but if we were to make an EE_WP_Post model, it should set this to true).
291
-     * This should be true for models that deal with data that should exist independent of EE.
292
-     * For example, if the model can read and insert data that isn't used by EE, this should be true.
293
-     * It would be false, however, if you could guarantee the model would only interact with EE data,
294
-     * even if it uses a WP core table (eg event and venue models set this to false for that reason:
295
-     * they can only read and insert events and venues custom post types, not arbitrary post types)
296
-     * @var boolean
297
-     */
298
-    protected $_wp_core_model = false;
299
-
300
-    /**
301
-     * @var bool stores whether this model has a password field or not.
302
-     * null until initialized by hasPasswordField()
303
-     */
304
-    protected $has_password_field;
305
-
306
-    /**
307
-     * @var EE_Password_Field|null Automatically set when calling getPasswordField()
308
-     */
309
-    protected $password_field;
310
-
311
-    /**
312
-     *    List of valid operators that can be used for querying.
313
-     * The keys are all operators we'll accept, the values are the real SQL
314
-     * operators used
315
-     *
316
-     * @var array
317
-     */
318
-    protected $_valid_operators = array(
319
-        '='           => '=',
320
-        '<='          => '<=',
321
-        '<'           => '<',
322
-        '>='          => '>=',
323
-        '>'           => '>',
324
-        '!='          => '!=',
325
-        'LIKE'        => 'LIKE',
326
-        'like'        => 'LIKE',
327
-        'NOT_LIKE'    => 'NOT LIKE',
328
-        'not_like'    => 'NOT LIKE',
329
-        'NOT LIKE'    => 'NOT LIKE',
330
-        'not like'    => 'NOT LIKE',
331
-        'IN'          => 'IN',
332
-        'in'          => 'IN',
333
-        'NOT_IN'      => 'NOT IN',
334
-        'not_in'      => 'NOT IN',
335
-        'NOT IN'      => 'NOT IN',
336
-        'not in'      => 'NOT IN',
337
-        'between'     => 'BETWEEN',
338
-        'BETWEEN'     => 'BETWEEN',
339
-        'IS_NOT_NULL' => 'IS NOT NULL',
340
-        'is_not_null' => 'IS NOT NULL',
341
-        'IS NOT NULL' => 'IS NOT NULL',
342
-        'is not null' => 'IS NOT NULL',
343
-        'IS_NULL'     => 'IS NULL',
344
-        'is_null'     => 'IS NULL',
345
-        'IS NULL'     => 'IS NULL',
346
-        'is null'     => 'IS NULL',
347
-        'REGEXP'      => 'REGEXP',
348
-        'regexp'      => 'REGEXP',
349
-        'NOT_REGEXP'  => 'NOT REGEXP',
350
-        'not_regexp'  => 'NOT REGEXP',
351
-        'NOT REGEXP'  => 'NOT REGEXP',
352
-        'not regexp'  => 'NOT REGEXP',
353
-    );
354
-
355
-    /**
356
-     * operators that work like 'IN', accepting a comma-separated list of values inside brackets. Eg '(1,2,3)'
357
-     *
358
-     * @var array
359
-     */
360
-    protected $_in_style_operators = array('IN', 'NOT IN');
361
-
362
-    /**
363
-     * operators that work like 'BETWEEN'.  Typically used for datetime calculations, i.e. "BETWEEN '12-1-2011' AND
364
-     * '12-31-2012'"
365
-     *
366
-     * @var array
367
-     */
368
-    protected $_between_style_operators = array('BETWEEN');
369
-
370
-    /**
371
-     * Operators that work like SQL's like: input should be assumed to be a string, already prepared for a LIKE query.
372
-     * @var array
373
-     */
374
-    protected $_like_style_operators = array('LIKE', 'NOT LIKE');
375
-    /**
376
-     * operators that are used for handling NUll and !NULL queries.  Typically used for when checking if a row exists
377
-     * on a join table.
378
-     *
379
-     * @var array
380
-     */
381
-    protected $_null_style_operators = array('IS NOT NULL', 'IS NULL');
382
-
383
-    /**
384
-     * Allowed values for $query_params['order'] for ordering in queries
385
-     *
386
-     * @var array
387
-     */
388
-    protected $_allowed_order_values = array('asc', 'desc', 'ASC', 'DESC');
389
-
390
-    /**
391
-     * When these are keys in a WHERE or HAVING clause, they are handled much differently
392
-     * than regular field names. It is assumed that their values are an array of WHERE conditions
393
-     *
394
-     * @var array
395
-     */
396
-    private $_logic_query_param_keys = array('not', 'and', 'or', 'NOT', 'AND', 'OR');
397
-
398
-    /**
399
-     * Allowed keys in $query_params arrays passed into queries. Note that 0 is meant to always be a
400
-     * 'where', but 'where' clauses are so common that we thought we'd omit it
401
-     *
402
-     * @var array
403
-     */
404
-    private $_allowed_query_params = array(
405
-        0,
406
-        'limit',
407
-        'order_by',
408
-        'group_by',
409
-        'having',
410
-        'force_join',
411
-        'order',
412
-        'on_join_limit',
413
-        'default_where_conditions',
414
-        'caps',
415
-        'extra_selects',
416
-        'exclude_protected',
417
-    );
418
-
419
-    /**
420
-     * All the data types that can be used in $wpdb->prepare statements.
421
-     *
422
-     * @var array
423
-     */
424
-    private $_valid_wpdb_data_types = array('%d', '%s', '%f');
425
-
426
-    /**
427
-     * @var EE_Registry $EE
428
-     */
429
-    protected $EE = null;
430
-
431
-
432
-    /**
433
-     * Property which, when set, will have this model echo out the next X queries to the page for debugging.
434
-     *
435
-     * @var int
436
-     */
437
-    protected $_show_next_x_db_queries = 0;
438
-
439
-    /**
440
-     * When using _get_all_wpdb_results, you can specify a custom selection. If you do so,
441
-     * it gets saved on this property as an instance of CustomSelects so those selections can be used in
442
-     * WHERE, GROUP_BY, etc.
443
-     *
444
-     * @var CustomSelects
445
-     */
446
-    protected $_custom_selections = array();
447
-
448
-    /**
449
-     * key => value Entity Map using  array( EEM_Base::$_model_query_blog_id => array( ID => model object ) )
450
-     * caches every model object we've fetched from the DB on this request
451
-     *
452
-     * @var array
453
-     */
454
-    protected $_entity_map;
455
-
456
-    /**
457
-     * @var LoaderInterface
458
-     */
459
-    protected static $loader;
460
-
461
-    /**
462
-     * @var Mirror
463
-     */
464
-    private static $mirror;
465
-
466
-
467
-
468
-    /**
469
-     * constant used to show EEM_Base has not yet verified the db on this http request
470
-     */
471
-    const db_verified_none = 0;
472
-
473
-    /**
474
-     * constant used to show EEM_Base has verified the EE core db on this http request,
475
-     * but not the addons' dbs
476
-     */
477
-    const db_verified_core = 1;
478
-
479
-    /**
480
-     * constant used to show EEM_Base has verified the addons' dbs (and implicitly
481
-     * the EE core db too)
482
-     */
483
-    const db_verified_addons = 2;
484
-
485
-    /**
486
-     * indicates whether an EEM_Base child has already re-verified the DB
487
-     * is ok (we don't want to do it repetitively). Should be set to one the constants
488
-     * looking like EEM_Base::db_verified_*
489
-     *
490
-     * @var int - 0 = none, 1 = core, 2 = addons
491
-     */
492
-    protected static $_db_verification_level = EEM_Base::db_verified_none;
493
-
494
-    /**
495
-     * @const constant for 'default_where_conditions' to apply default where conditions to ALL queried models
496
-     *        (eg, if retrieving registrations ordered by their datetimes, this will only return non-trashed
497
-     *        registrations for non-trashed tickets for non-trashed datetimes)
498
-     */
499
-    const default_where_conditions_all = 'all';
500
-
501
-    /**
502
-     * @const constant for 'default_where_conditions' to apply default where conditions to THIS model only, but
503
-     *        no other models which are joined to (eg, if retrieving registrations ordered by their datetimes, this will
504
-     *        return non-trashed registrations, regardless of the related datetimes and tickets' statuses).
505
-     *        It is preferred to use EEM_Base::default_where_conditions_minimum_others because, when joining to
506
-     *        models which share tables with other models, this can return data for the wrong model.
507
-     */
508
-    const default_where_conditions_this_only = 'this_model_only';
509
-
510
-    /**
511
-     * @const constant for 'default_where_conditions' to apply default where conditions to other models queried,
512
-     *        but not the current model (eg, if retrieving registrations ordered by their datetimes, this will
513
-     *        return all registrations related to non-trashed tickets and non-trashed datetimes)
514
-     */
515
-    const default_where_conditions_others_only = 'other_models_only';
516
-
517
-    /**
518
-     * @const constant for 'default_where_conditions' to apply minimum where conditions to all models queried.
519
-     *        For most models this the same as EEM_Base::default_where_conditions_none, except for models which share
520
-     *        their table with other models, like the Event and Venue models. For example, when querying for events
521
-     *        ordered by their venues' name, this will be sure to only return real events with associated real venues
522
-     *        (regardless of whether those events and venues are trashed)
523
-     *        In contrast, using EEM_Base::default_where_conditions_none would could return WP posts other than EE
524
-     *        events.
525
-     */
526
-    const default_where_conditions_minimum_all = 'minimum';
527
-
528
-    /**
529
-     * @const constant for 'default_where_conditions' to apply apply where conditions to other models, and full default
530
-     *        where conditions for the queried model (eg, when querying events ordered by venues' names, this will
531
-     *        return non-trashed events for any venues, regardless of whether those associated venues are trashed or
532
-     *        not)
533
-     */
534
-    const default_where_conditions_minimum_others = 'full_this_minimum_others';
535
-
536
-    /**
537
-     * @const constant for 'default_where_conditions' to NOT apply any where conditions. This should very rarely be
538
-     *        used, because when querying from a model which shares its table with another model (eg Events and Venues)
539
-     *        it's possible it will return table entries for other models. You should use
540
-     *        EEM_Base::default_where_conditions_minimum_all instead.
541
-     */
542
-    const default_where_conditions_none = 'none';
543
-
544
-
545
-
546
-    /**
547
-     * About all child constructors:
548
-     * they should define the _tables, _fields and _model_relations arrays.
549
-     * Should ALWAYS be called after child constructor.
550
-     * In order to make the child constructors to be as simple as possible, this parent constructor
551
-     * finalizes constructing all the object's attributes.
552
-     * Generally, rather than requiring a child to code
553
-     * $this->_tables = array(
554
-     *        'Event_Post_Table' => new EE_Table('Event_Post_Table','wp_posts')
555
-     *        ...);
556
-     *  (thus repeating itself in the array key and in the constructor of the new EE_Table,)
557
-     * each EE_Table has a function to set the table's alias after the constructor, using
558
-     * the array key ('Event_Post_Table'), instead of repeating it. The model fields and model relations
559
-     * do something similar.
560
-     *
561
-     * @param null $timezone
562
-     * @throws EE_Error
563
-     */
564
-    protected function __construct($timezone = null)
565
-    {
566
-        // check that the model has not been loaded too soon
567
-        if (! did_action('AHEE__EE_System__load_espresso_addons')) {
568
-            throw new EE_Error(
569
-                sprintf(
570
-                    esc_html__(
571
-                        '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.',
572
-                        'event_espresso'
573
-                    ),
574
-                    get_class($this)
575
-                )
576
-            );
577
-        }
578
-        /**
579
-         * Set blogid for models to current blog. However we ONLY do this if $_model_query_blog_id is not already set.
580
-         */
581
-        if (empty(EEM_Base::$_model_query_blog_id)) {
582
-            EEM_Base::set_model_query_blog_id();
583
-        }
584
-        /**
585
-         * Filters the list of tables on a model. It is best to NOT use this directly and instead
586
-         * just use EE_Register_Model_Extension
587
-         *
588
-         * @var EE_Table_Base[] $_tables
589
-         */
590
-        $this->_tables = (array) apply_filters('FHEE__' . get_class($this) . '__construct__tables', $this->_tables);
591
-        foreach ($this->_tables as $table_alias => $table_obj) {
592
-            /** @var $table_obj EE_Table_Base */
593
-            $table_obj->_construct_finalize_with_alias($table_alias);
594
-            if ($table_obj instanceof EE_Secondary_Table) {
595
-                /** @var $table_obj EE_Secondary_Table */
596
-                $table_obj->_construct_finalize_set_table_to_join_with($this->_get_main_table());
597
-            }
598
-        }
599
-        /**
600
-         * Filters the list of fields on a model. It is best to NOT use this directly and instead just use
601
-         * EE_Register_Model_Extension
602
-         *
603
-         * @param EE_Model_Field_Base[] $_fields
604
-         */
605
-        $this->_fields = (array) apply_filters('FHEE__' . get_class($this) . '__construct__fields', $this->_fields);
606
-        $this->_invalidate_field_caches();
607
-        foreach ($this->_fields as $table_alias => $fields_for_table) {
608
-            if (! array_key_exists($table_alias, $this->_tables)) {
609
-                throw new EE_Error(sprintf(esc_html__(
610
-                    "Table alias %s does not exist in EEM_Base child's _tables array. Only tables defined are %s",
611
-                    'event_espresso'
612
-                ), $table_alias, implode(",", $this->_fields)));
613
-            }
614
-            foreach ($fields_for_table as $field_name => $field_obj) {
615
-                /** @var $field_obj EE_Model_Field_Base | EE_Primary_Key_Field_Base */
616
-                // primary key field base has a slightly different _construct_finalize
617
-                /** @var $field_obj EE_Model_Field_Base */
618
-                $field_obj->_construct_finalize($table_alias, $field_name, $this->get_this_model_name());
619
-            }
620
-        }
621
-        // everything is related to Extra_Meta
622
-        if (get_class($this) !== 'EEM_Extra_Meta') {
623
-            // make extra meta related to everything, but don't block deleting things just
624
-            // because they have related extra meta info. For now just orphan those extra meta
625
-            // in the future we should automatically delete them
626
-            $this->_model_relations['Extra_Meta'] = new EE_Has_Many_Any_Relation(false);
627
-        }
628
-        // and change logs
629
-        if (get_class($this) !== 'EEM_Change_Log') {
630
-            $this->_model_relations['Change_Log'] = new EE_Has_Many_Any_Relation(false);
631
-        }
632
-        /**
633
-         * Filters the list of relations on a model. It is best to NOT use this directly and instead just use
634
-         * EE_Register_Model_Extension
635
-         *
636
-         * @param EE_Model_Relation_Base[] $_model_relations
637
-         */
638
-        $this->_model_relations = (array) apply_filters(
639
-            'FHEE__' . get_class($this) . '__construct__model_relations',
640
-            $this->_model_relations
641
-        );
642
-        foreach ($this->_model_relations as $model_name => $relation_obj) {
643
-            /** @var $relation_obj EE_Model_Relation_Base */
644
-            $relation_obj->_construct_finalize_set_models($this->get_this_model_name(), $model_name);
645
-        }
646
-        foreach ($this->_indexes as $index_name => $index_obj) {
647
-            $index_obj->_construct_finalize($index_name, $this->get_this_model_name());
648
-        }
649
-        $this->set_timezone($timezone);
650
-        // finalize default where condition strategy, or set default
651
-        if (! $this->_default_where_conditions_strategy) {
652
-            // nothing was set during child constructor, so set default
653
-            $this->_default_where_conditions_strategy = new EE_Default_Where_Conditions();
654
-        }
655
-        $this->_default_where_conditions_strategy->_finalize_construct($this);
656
-        if (! $this->_minimum_where_conditions_strategy) {
657
-            // nothing was set during child constructor, so set default
658
-            $this->_minimum_where_conditions_strategy = new EE_Default_Where_Conditions();
659
-        }
660
-        $this->_minimum_where_conditions_strategy->_finalize_construct($this);
661
-        // if the cap slug hasn't been set, and we haven't set it to false on purpose
662
-        // to indicate to NOT set it, set it to the logical default
663
-        if ($this->_caps_slug === null) {
664
-            $this->_caps_slug = EEH_Inflector::pluralize_and_lower($this->get_this_model_name());
665
-        }
666
-        // initialize the standard cap restriction generators if none were specified by the child constructor
667
-        if (is_array($this->_cap_restriction_generators)) {
668
-            foreach ($this->cap_contexts_to_cap_action_map() as $cap_context => $action) {
669
-                if (! isset($this->_cap_restriction_generators[ $cap_context ])) {
670
-                    $this->_cap_restriction_generators[ $cap_context ] = apply_filters(
671
-                        'FHEE__EEM_Base___construct__standard_cap_restriction_generator',
672
-                        new EE_Restriction_Generator_Protected(),
673
-                        $cap_context,
674
-                        $this
675
-                    );
676
-                }
677
-            }
678
-        }
679
-        // if there are cap restriction generators, use them to make the default cap restrictions
680
-        if (is_array($this->_cap_restriction_generators)) {
681
-            foreach ($this->_cap_restriction_generators as $context => $generator_object) {
682
-                if (! $generator_object) {
683
-                    continue;
684
-                }
685
-                if (! $generator_object instanceof EE_Restriction_Generator_Base) {
686
-                    throw new EE_Error(
687
-                        sprintf(
688
-                            esc_html__(
689
-                                '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.',
690
-                                'event_espresso'
691
-                            ),
692
-                            $context,
693
-                            $this->get_this_model_name()
694
-                        )
695
-                    );
696
-                }
697
-                $action = $this->cap_action_for_context($context);
698
-                if (! $generator_object->construction_finalized()) {
699
-                    $generator_object->_construct_finalize($this, $action);
700
-                }
701
-            }
702
-        }
703
-        do_action('AHEE__' . get_class($this) . '__construct__end');
704
-    }
705
-
706
-
707
-    /**
708
-     * @return LoaderInterface
709
-     * @throws InvalidArgumentException
710
-     * @throws InvalidDataTypeException
711
-     * @throws InvalidInterfaceException
712
-     */
713
-    protected static function getLoader(): LoaderInterface
714
-    {
715
-        if (! EEM_Base::$loader instanceof LoaderInterface) {
716
-            EEM_Base::$loader = LoaderFactory::getLoader();
717
-        }
718
-        return EEM_Base::$loader;
719
-    }
720
-
721
-
722
-    /**
723
-     * @return Mirror
724
-     * @since   $VID:$
725
-     */
726
-    private static function getMirror(): Mirror
727
-    {
728
-        if (! EEM_Base::$mirror instanceof Mirror) {
729
-            EEM_Base::$mirror = EEM_Base::getLoader()->getShared(Mirror::class);
730
-        }
731
-        return EEM_Base::$mirror;
732
-    }
733
-
734
-
735
-    /**
736
-     * @param string $model_class_Name
737
-     * @param string $timezone
738
-     * @return array
739
-     * @throws ReflectionException
740
-     * @since   $VID:$
741
-     */
742
-    private static function getModelArguments(string $model_class_Name, string $timezone): array
743
-    {
744
-        $arguments = [$timezone];
745
-        $params    = EEM_Base::getMirror()->getParameters($model_class_Name);
746
-        if (count($params) > 1) {
747
-            if ($params[1]->getName() === 'model_field_factory') {
748
-                $arguments = [
749
-                    $timezone,
750
-                    EEM_Base::getLoader()->getShared(ModelFieldFactory::class)
751
-                ];
752
-            } elseif ($model_class_Name === 'EEM_Form_Section') {
753
-                $arguments = [
754
-                    EEM_Base::getLoader()->getShared('EventEspresso\core\services\form\meta\FormStatus'),
755
-                    $timezone
756
-                ];
757
-            } elseif ($model_class_Name === 'EEM_Form_Element') {
758
-                $arguments = [
759
-                    EEM_Base::getLoader()->getShared('EventEspresso\core\services\form\meta\FormStatus'),
760
-                    EEM_Base::getLoader()->getShared('EventEspresso\core\services\form\meta\InputTypes'),
761
-                    $timezone,
762
-                ];
763
-            }
764
-        }
765
-        return $arguments;
766
-    }
767
-
768
-
769
-    /**
770
-     * This function is a singleton method used to instantiate the Espresso_model object
771
-     *
772
-     * @param string|null $timezone   string representing the timezone we want to set for returned Date Time Strings
773
-     *                                (and any incoming timezone data that gets saved).
774
-     *                                Note this just sends the timezone info to the date time model field objects.
775
-     *                                Default is NULL
776
-     *                                (and will be assumed using the set timezone in the 'timezone_string' wp option)
777
-     * @return static (as in the concrete child class)
778
-     * @throws EE_Error
779
-     * @throws ReflectionException
780
-     */
781
-    public static function instance($timezone = null)
782
-    {
783
-        // check if instance of Espresso_model already exists
784
-        if (! static::$_instance instanceof static) {
785
-            $arguments = EEM_Base::getModelArguments(static::class, (string) $timezone);
786
-            $model = new static(...$arguments);
787
-            EEM_Base::getLoader()->share(static::class, $model, $arguments);
788
-            static::$_instance = $model;
789
-        }
790
-        // we might have a timezone set, let set_timezone decide what to do with it
791
-        if ($timezone) {
792
-            static::$_instance->set_timezone($timezone);
793
-        }
794
-        // Espresso_model object
795
-        return static::$_instance;
796
-    }
797
-
798
-
799
-
800
-    /**
801
-     * resets the model and returns it
802
-     *
803
-     * @param string|null $timezone
804
-     * @return EEM_Base|null (if the model was already instantiated, returns it, with
805
-     * all its properties reset; if it wasn't instantiated, returns null)
806
-     * @throws EE_Error
807
-     * @throws ReflectionException
808
-     * @throws InvalidArgumentException
809
-     * @throws InvalidDataTypeException
810
-     * @throws InvalidInterfaceException
811
-     */
812
-    public static function reset($timezone = null)
813
-    {
814
-        if (! static::$_instance instanceof EEM_Base) {
815
-            return null;
816
-        }
817
-        // Let's NOT swap out the current instance for a new one
818
-        // because if someone has a reference to it, we can't remove their reference.
819
-        // It's best to keep using the same reference but change the original object instead,
820
-        // so reset all its properties to their original values as defined in the class.
821
-        $static_properties = EEM_Base::getMirror()->getStaticProperties(static::class);
822
-        foreach (EEM_Base::getMirror()->getDefaultProperties(static::class) as $property => $value) {
823
-            // don't set instance to null like it was originally,
824
-            // but it's static anyways, and we're ignoring static properties (for now at least)
825
-            if (! isset($static_properties[ $property ])) {
826
-                static::$_instance->{$property} = $value;
827
-            }
828
-        }
829
-        // and then directly call its constructor again, like we would if we were creating a new one
830
-        $arguments = EEM_Base::getModelArguments(static::class, (string) $timezone);
831
-        static::$_instance->__construct(...$arguments);
832
-        return self::instance();
833
-    }
834
-
835
-
836
-    /**
837
-     * Used to set the $_model_query_blog_id static property.
838
-     *
839
-     * @param int $blog_id  If provided then will set the blog_id for the models to this id.  If not provided then the
840
-     *                      value for get_current_blog_id() will be used.
841
-     */
842
-    public static function set_model_query_blog_id($blog_id = 0)
843
-    {
844
-        EEM_Base::$_model_query_blog_id = $blog_id > 0
845
-            ? (int) $blog_id
846
-            : get_current_blog_id();
847
-    }
848
-
849
-
850
-    /**
851
-     * Returns whatever is set as the internal $model_query_blog_id.
852
-     *
853
-     * @return int
854
-     */
855
-    public static function get_model_query_blog_id()
856
-    {
857
-        return EEM_Base::$_model_query_blog_id;
858
-    }
859
-
860
-
861
-
862
-    /**
863
-     * retrieve the status details from esp_status table as an array IF this model has the status table as a relation.
864
-     *
865
-     * @param  boolean $translated return localized strings or JUST the array.
866
-     * @return array
867
-     * @throws EE_Error
868
-     * @throws InvalidArgumentException
869
-     * @throws InvalidDataTypeException
870
-     * @throws InvalidInterfaceException
871
-     */
872
-    public function status_array($translated = false)
873
-    {
874
-        if (! array_key_exists('Status', $this->_model_relations)) {
875
-            return array();
876
-        }
877
-        $model_name = $this->get_this_model_name();
878
-        $status_type = str_replace(' ', '_', strtolower(str_replace('_', ' ', $model_name)));
879
-        $stati = EEM_Status::instance()->get_all(array(array('STS_type' => $status_type)));
880
-        $status_array = array();
881
-        foreach ($stati as $status) {
882
-            $status_array[ $status->ID() ] = $status->get('STS_code');
883
-        }
884
-        return $translated
885
-            ? EEM_Status::instance()->localized_status($status_array, false, 'sentence')
886
-            : $status_array;
887
-    }
888
-
889
-
890
-
891
-    /**
892
-     * Gets all the EE_Base_Class objects which match the $query_params, by querying the DB.
893
-     *
894
-     * @param array $query_params  @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
895
-     *                             or if you have the development copy of EE you can view this at the path:
896
-     *                             /docs/G--Model-System/model-query-params.md
897
-     * @return EE_Base_Class[]  *note that there is NO option to pass the output type. If you want results different
898
-     *                                        from EE_Base_Class[], use get_all_wpdb_results(). Array keys are object IDs (if there is a primary key on the model.
899
-     *                                        if not, numerically indexed) Some full examples: get 10 transactions
900
-     *                                        which have Scottish attendees: EEM_Transaction::instance()->get_all(
901
-     *                                        array( array(
902
-     *                                        'OR'=>array(
903
-     *                                        'Registration.Attendee.ATT_fname'=>array('like','Mc%'),
904
-     *                                        'Registration.Attendee.ATT_fname*other'=>array('like','Mac%')
905
-     *                                        )
906
-     *                                        ),
907
-     *                                        'limit'=>10,
908
-     *                                        'group_by'=>'TXN_ID'
909
-     *                                        ));
910
-     *                                        get all the answers to the question titled "shirt size" for event with id
911
-     *                                        12, ordered by their answer EEM_Answer::instance()->get_all(array( array(
912
-     *                                        'Question.QST_display_text'=>'shirt size',
913
-     *                                        'Registration.Event.EVT_ID'=>12
914
-     *                                        ),
915
-     *                                        'order_by'=>array('ANS_value'=>'ASC')
916
-     *                                        ));
917
-     * @throws EE_Error
918
-     */
919
-    public function get_all($query_params = array())
920
-    {
921
-        if (
922
-            isset($query_params['limit'])
923
-            && ! isset($query_params['group_by'])
924
-        ) {
925
-            $query_params['group_by'] = array_keys($this->get_combined_primary_key_fields());
926
-        }
927
-        return $this->_create_objects($this->_get_all_wpdb_results($query_params));
928
-    }
929
-
930
-
931
-
932
-    /**
933
-     * Modifies the query parameters so we only get back model objects
934
-     * that "belong" to the current user
935
-     *
936
-     * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
937
-     * @return array @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
938
-     */
939
-    public function alter_query_params_to_only_include_mine($query_params = array())
940
-    {
941
-        $wp_user_field_name = $this->wp_user_field_name();
942
-        if ($wp_user_field_name) {
943
-            $query_params[0][ $wp_user_field_name ] = get_current_user_id();
944
-        }
945
-        return $query_params;
946
-    }
947
-
948
-
949
-
950
-    /**
951
-     * Returns the name of the field's name that points to the WP_User table
952
-     *  on this model (or follows the _model_chain_to_wp_user and uses that model's
953
-     * foreign key to the WP_User table)
954
-     *
955
-     * @return string|boolean string on success, boolean false when there is no
956
-     * foreign key to the WP_User table
957
-     */
958
-    public function wp_user_field_name()
959
-    {
960
-        try {
961
-            if (! empty($this->_model_chain_to_wp_user)) {
962
-                $models_to_follow_to_wp_users = explode('.', $this->_model_chain_to_wp_user);
963
-                $last_model_name = end($models_to_follow_to_wp_users);
964
-                $model_with_fk_to_wp_users = EE_Registry::instance()->load_model($last_model_name);
965
-                $model_chain_to_wp_user = $this->_model_chain_to_wp_user . '.';
966
-            } else {
967
-                $model_with_fk_to_wp_users = $this;
968
-                $model_chain_to_wp_user = '';
969
-            }
970
-            $wp_user_field = $model_with_fk_to_wp_users->get_foreign_key_to('WP_User');
971
-            return $model_chain_to_wp_user . $wp_user_field->get_name();
972
-        } catch (EE_Error $e) {
973
-            return false;
974
-        }
975
-    }
976
-
977
-
978
-
979
-    /**
980
-     * Returns the _model_chain_to_wp_user string, which indicates which related model
981
-     * (or transiently-related model) has a foreign key to the wp_users table;
982
-     * useful for finding if model objects of this type are 'owned' by the current user.
983
-     * This is an empty string when the foreign key is on this model and when it isn't,
984
-     * but is only non-empty when this model's ownership is indicated by a RELATED model
985
-     * (or transiently-related model)
986
-     *
987
-     * @return string
988
-     */
989
-    public function model_chain_to_wp_user()
990
-    {
991
-        return $this->_model_chain_to_wp_user;
992
-    }
993
-
994
-
995
-
996
-    /**
997
-     * Whether this model is 'owned' by a specific wordpress user (even indirectly,
998
-     * like how registrations don't have a foreign key to wp_users, but the
999
-     * events they are for are), or is unrelated to wp users.
1000
-     * generally available
1001
-     *
1002
-     * @return boolean
1003
-     */
1004
-    public function is_owned()
1005
-    {
1006
-        if ($this->model_chain_to_wp_user()) {
1007
-            return true;
1008
-        }
1009
-        try {
1010
-            $this->get_foreign_key_to('WP_User');
1011
-            return true;
1012
-        } catch (EE_Error $e) {
1013
-            return false;
1014
-        }
1015
-    }
1016
-
1017
-
1018
-    /**
1019
-     * Used internally to get WPDB results, because other functions, besides get_all, may want to do some queries, but
1020
-     * may want to preserve the WPDB results (eg, update, which first queries to make sure we have all the tables on
1021
-     * the model)
1022
-     *
1023
-     * @param array  $query_params      @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1024
-     * @param string $output            ARRAY_A, OBJECT_K, etc. Just like
1025
-     * @param mixed  $columns_to_select , What columns to select. By default, we select all columns specified by the
1026
-     *                                  fields on the model, and the models we joined to in the query. However, you can
1027
-     *                                  override this and set the select to "*", or a specific column name, like
1028
-     *                                  "ATT_ID", etc. If you would like to use these custom selections in WHERE,
1029
-     *                                  GROUP_BY, or HAVING clauses, you must instead provide an array. Array keys are
1030
-     *                                  the aliases used to refer to this selection, and values are to be
1031
-     *                                  numerically-indexed arrays, where 0 is the selection and 1 is the data type.
1032
-     *                                  Eg, array('count'=>array('COUNT(REG_ID)','%d'))
1033
-     * @return array | stdClass[] like results of $wpdb->get_results($sql,OBJECT), (ie, output type is OBJECT)
1034
-     * @throws EE_Error
1035
-     * @throws InvalidArgumentException
1036
-     */
1037
-    protected function _get_all_wpdb_results($query_params = array(), $output = ARRAY_A, $columns_to_select = null)
1038
-    {
1039
-        $this->_custom_selections = $this->getCustomSelection($query_params, $columns_to_select);
1040
-        $model_query_info = $this->_create_model_query_info_carrier($query_params);
1041
-        $select_expressions = $columns_to_select === null
1042
-            ? $this->_construct_default_select_sql($model_query_info)
1043
-            : '';
1044
-        if ($this->_custom_selections instanceof CustomSelects) {
1045
-            $custom_expressions = $this->_custom_selections->columnsToSelectExpression();
1046
-            $select_expressions .= $select_expressions
1047
-                ? ', ' . $custom_expressions
1048
-                : $custom_expressions;
1049
-        }
1050
-
1051
-        $SQL = "SELECT $select_expressions " . $this->_construct_2nd_half_of_select_query($model_query_info);
1052
-        return $this->_do_wpdb_query('get_results', array($SQL, $output));
1053
-    }
1054
-
1055
-
1056
-    /**
1057
-     * Get a CustomSelects object if the $query_params or $columns_to_select allows for it.
1058
-     * Note: $query_params['extra_selects'] will always override any $columns_to_select values. It is the preferred
1059
-     * method of including extra select information.
1060
-     *
1061
-     * @param array             $query_params
1062
-     * @param null|array|string $columns_to_select
1063
-     * @return null|CustomSelects
1064
-     * @throws InvalidArgumentException
1065
-     */
1066
-    protected function getCustomSelection(array $query_params, $columns_to_select = null)
1067
-    {
1068
-        if (! isset($query_params['extra_selects']) && $columns_to_select === null) {
1069
-            return null;
1070
-        }
1071
-        $selects = isset($query_params['extra_selects']) ? $query_params['extra_selects'] : $columns_to_select;
1072
-        $selects = is_string($selects) ? explode(',', $selects) : $selects;
1073
-        return new CustomSelects($selects);
1074
-    }
1075
-
1076
-
1077
-
1078
-    /**
1079
-     * Gets an array of rows from the database just like $wpdb->get_results would,
1080
-     * but you can use the model query params to more easily
1081
-     * take care of joins, field preparation etc.
1082
-     *
1083
-     * @param array  $query_params      @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1084
-     * @param string $output            ARRAY_A, OBJECT_K, etc. Just like
1085
-     * @param mixed  $columns_to_select , What columns to select. By default, we select all columns specified by the
1086
-     *                                  fields on the model, and the models we joined to in the query. However, you can
1087
-     *                                  override this and set the select to "*", or a specific column name, like
1088
-     *                                  "ATT_ID", etc. If you would like to use these custom selections in WHERE,
1089
-     *                                  GROUP_BY, or HAVING clauses, you must instead provide an array. Array keys are
1090
-     *                                  the aliases used to refer to this selection, and values are to be
1091
-     *                                  numerically-indexed arrays, where 0 is the selection and 1 is the data type.
1092
-     *                                  Eg, array('count'=>array('COUNT(REG_ID)','%d'))
1093
-     * @return array|stdClass[] like results of $wpdb->get_results($sql,OBJECT), (ie, output type is OBJECT)
1094
-     * @throws EE_Error
1095
-     */
1096
-    public function get_all_wpdb_results($query_params = array(), $output = ARRAY_A, $columns_to_select = null)
1097
-    {
1098
-        return $this->_get_all_wpdb_results($query_params, $output, $columns_to_select);
1099
-    }
1100
-
1101
-
1102
-
1103
-    /**
1104
-     * For creating a custom select statement
1105
-     *
1106
-     * @param mixed $columns_to_select either a string to be inserted directly as the select statement,
1107
-     *                                 or an array where keys are aliases, and values are arrays where 0=>the selection
1108
-     *                                 SQL, and 1=>is the datatype
1109
-     * @throws EE_Error
1110
-     * @return string
1111
-     */
1112
-    private function _construct_select_from_input($columns_to_select)
1113
-    {
1114
-        if (is_array($columns_to_select)) {
1115
-            $select_sql_array = array();
1116
-            foreach ($columns_to_select as $alias => $selection_and_datatype) {
1117
-                if (! is_array($selection_and_datatype) || ! isset($selection_and_datatype[1])) {
1118
-                    throw new EE_Error(
1119
-                        sprintf(
1120
-                            esc_html__(
1121
-                                "Custom selection %s (alias %s) needs to be an array like array('COUNT(REG_ID)','%%d')",
1122
-                                'event_espresso'
1123
-                            ),
1124
-                            $selection_and_datatype,
1125
-                            $alias
1126
-                        )
1127
-                    );
1128
-                }
1129
-                if (! in_array($selection_and_datatype[1], $this->_valid_wpdb_data_types, true)) {
1130
-                    throw new EE_Error(
1131
-                        sprintf(
1132
-                            esc_html__(
1133
-                                "Datatype %s (for selection '%s' and alias '%s') is not a valid wpdb datatype (eg %%s)",
1134
-                                'event_espresso'
1135
-                            ),
1136
-                            $selection_and_datatype[1],
1137
-                            $selection_and_datatype[0],
1138
-                            $alias,
1139
-                            implode(', ', $this->_valid_wpdb_data_types)
1140
-                        )
1141
-                    );
1142
-                }
1143
-                $select_sql_array[] = "{$selection_and_datatype[0]} AS $alias";
1144
-            }
1145
-            $columns_to_select_string = implode(', ', $select_sql_array);
1146
-        } else {
1147
-            $columns_to_select_string = $columns_to_select;
1148
-        }
1149
-        return $columns_to_select_string;
1150
-    }
1151
-
1152
-
1153
-
1154
-    /**
1155
-     * Convenient wrapper for getting the primary key field's name. Eg, on Registration, this would be 'REG_ID'
1156
-     *
1157
-     * @return string
1158
-     * @throws EE_Error
1159
-     */
1160
-    public function primary_key_name()
1161
-    {
1162
-        return $this->get_primary_key_field()->get_name();
1163
-    }
1164
-
1165
-
1166
-    /**
1167
-     * Gets a single item for this model from the DB, given only its ID (or null if none is found).
1168
-     * If there is no primary key on this model, $id is treated as primary key string
1169
-     *
1170
-     * @param mixed $id int or string, depending on the type of the model's primary key
1171
-     * @return EE_Base_Class|mixed|null
1172
-     * @throws EE_Error
1173
-     */
1174
-    public function get_one_by_ID($id)
1175
-    {
1176
-        if ($this->get_from_entity_map($id)) {
1177
-            return $this->get_from_entity_map($id);
1178
-        }
1179
-        $model_object = $this->get_one(
1180
-            $this->alter_query_params_to_restrict_by_ID(
1181
-                $id,
1182
-                array('default_where_conditions' => EEM_Base::default_where_conditions_minimum_all)
1183
-            )
1184
-        );
1185
-        $className = $this->_get_class_name();
1186
-        if ($model_object instanceof $className) {
1187
-            // make sure valid objects get added to the entity map
1188
-            // so that the next call to this method doesn't trigger another trip to the db
1189
-            $this->add_to_entity_map($model_object);
1190
-        }
1191
-        return $model_object;
1192
-    }
1193
-
1194
-
1195
-
1196
-    /**
1197
-     * Alters query parameters to only get items with this ID are returned.
1198
-     * Takes into account that the ID might be a string produced by EEM_Base::get_index_primary_key_string(),
1199
-     * or could just be a simple primary key ID
1200
-     *
1201
-     * @param int   $id
1202
-     * @param array $query_params
1203
-     * @return array of normal query params, @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1204
-     * @throws EE_Error
1205
-     */
1206
-    public function alter_query_params_to_restrict_by_ID($id, $query_params = array())
1207
-    {
1208
-        if (! isset($query_params[0])) {
1209
-            $query_params[0] = array();
1210
-        }
1211
-        $conditions_from_id = $this->parse_index_primary_key_string($id);
1212
-        if ($conditions_from_id === null) {
1213
-            $query_params[0][ $this->primary_key_name() ] = $id;
1214
-        } else {
1215
-            // no primary key, so the $id must be from the get_index_primary_key_string()
1216
-            $query_params[0] = array_replace_recursive($query_params[0], $this->parse_index_primary_key_string($id));
1217
-        }
1218
-        return $query_params;
1219
-    }
1220
-
1221
-
1222
-
1223
-    /**
1224
-     * Gets a single item for this model from the DB, given the $query_params. Only returns a single class, not an
1225
-     * array. If no item is found, null is returned.
1226
-     *
1227
-     * @param array $query_params like EEM_Base's $query_params variable.
1228
-     * @return EE_Base_Class|EE_Soft_Delete_Base_Class|NULL
1229
-     * @throws EE_Error
1230
-     */
1231
-    public function get_one($query_params = array())
1232
-    {
1233
-        if (! is_array($query_params)) {
1234
-            EE_Error::doing_it_wrong(
1235
-                'EEM_Base::get_one',
1236
-                sprintf(
1237
-                    esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1238
-                    gettype($query_params)
1239
-                ),
1240
-                '4.6.0'
1241
-            );
1242
-            $query_params = array();
1243
-        }
1244
-        $query_params['limit'] = 1;
1245
-        $items = $this->get_all($query_params);
1246
-        if (empty($items)) {
1247
-            return null;
1248
-        }
1249
-        return array_shift($items);
1250
-    }
1251
-
1252
-
1253
-
1254
-    /**
1255
-     * Returns the next x number of items in sequence from the given value as
1256
-     * found in the database matching the given query conditions.
1257
-     *
1258
-     * @param mixed $current_field_value    Value used for the reference point.
1259
-     * @param null  $field_to_order_by      What field is used for the
1260
-     *                                      reference point.
1261
-     * @param int   $limit                  How many to return.
1262
-     * @param array $query_params           Extra conditions on the query.
1263
-     * @param null  $columns_to_select      If left null, then an array of
1264
-     *                                      EE_Base_Class objects is returned,
1265
-     *                                      otherwise you can indicate just the
1266
-     *                                      columns you want returned.
1267
-     * @return EE_Base_Class[]|array
1268
-     * @throws EE_Error
1269
-     */
1270
-    public function next_x(
1271
-        $current_field_value,
1272
-        $field_to_order_by = null,
1273
-        $limit = 1,
1274
-        $query_params = array(),
1275
-        $columns_to_select = null
1276
-    ) {
1277
-        return $this->_get_consecutive(
1278
-            $current_field_value,
1279
-            '>',
1280
-            $field_to_order_by,
1281
-            $limit,
1282
-            $query_params,
1283
-            $columns_to_select
1284
-        );
1285
-    }
1286
-
1287
-
1288
-
1289
-    /**
1290
-     * Returns the previous x number of items in sequence from the given value
1291
-     * as found in the database matching the given query conditions.
1292
-     *
1293
-     * @param mixed $current_field_value    Value used for the reference point.
1294
-     * @param null  $field_to_order_by      What field is used for the
1295
-     *                                      reference point.
1296
-     * @param int   $limit                  How many to return.
1297
-     * @param array $query_params           Extra conditions on the query.
1298
-     * @param null  $columns_to_select      If left null, then an array of
1299
-     *                                      EE_Base_Class objects is returned,
1300
-     *                                      otherwise you can indicate just the
1301
-     *                                      columns you want returned.
1302
-     * @return EE_Base_Class[]|array
1303
-     * @throws EE_Error
1304
-     */
1305
-    public function previous_x(
1306
-        $current_field_value,
1307
-        $field_to_order_by = null,
1308
-        $limit = 1,
1309
-        $query_params = array(),
1310
-        $columns_to_select = null
1311
-    ) {
1312
-        return $this->_get_consecutive(
1313
-            $current_field_value,
1314
-            '<',
1315
-            $field_to_order_by,
1316
-            $limit,
1317
-            $query_params,
1318
-            $columns_to_select
1319
-        );
1320
-    }
1321
-
1322
-
1323
-
1324
-    /**
1325
-     * Returns the next item in sequence from the given value as found in the
1326
-     * database matching the given query conditions.
1327
-     *
1328
-     * @param mixed $current_field_value    Value used for the reference point.
1329
-     * @param null  $field_to_order_by      What field is used for the
1330
-     *                                      reference point.
1331
-     * @param array $query_params           Extra conditions on the query.
1332
-     * @param null  $columns_to_select      If left null, then an EE_Base_Class
1333
-     *                                      object is returned, otherwise you
1334
-     *                                      can indicate just the columns you
1335
-     *                                      want and a single array indexed by
1336
-     *                                      the columns will be returned.
1337
-     * @return EE_Base_Class|null|array()
1338
-     * @throws EE_Error
1339
-     */
1340
-    public function next(
1341
-        $current_field_value,
1342
-        $field_to_order_by = null,
1343
-        $query_params = array(),
1344
-        $columns_to_select = null
1345
-    ) {
1346
-        $results = $this->_get_consecutive(
1347
-            $current_field_value,
1348
-            '>',
1349
-            $field_to_order_by,
1350
-            1,
1351
-            $query_params,
1352
-            $columns_to_select
1353
-        );
1354
-        return empty($results) ? null : reset($results);
1355
-    }
1356
-
1357
-
1358
-
1359
-    /**
1360
-     * Returns the previous item in sequence from the given value as found in
1361
-     * the database matching the given query conditions.
1362
-     *
1363
-     * @param mixed $current_field_value    Value used for the reference point.
1364
-     * @param null  $field_to_order_by      What field is used for the
1365
-     *                                      reference point.
1366
-     * @param array $query_params           Extra conditions on the query.
1367
-     * @param null  $columns_to_select      If left null, then an EE_Base_Class
1368
-     *                                      object is returned, otherwise you
1369
-     *                                      can indicate just the columns you
1370
-     *                                      want and a single array indexed by
1371
-     *                                      the columns will be returned.
1372
-     * @return EE_Base_Class|null|array()
1373
-     * @throws EE_Error
1374
-     */
1375
-    public function previous(
1376
-        $current_field_value,
1377
-        $field_to_order_by = null,
1378
-        $query_params = array(),
1379
-        $columns_to_select = null
1380
-    ) {
1381
-        $results = $this->_get_consecutive(
1382
-            $current_field_value,
1383
-            '<',
1384
-            $field_to_order_by,
1385
-            1,
1386
-            $query_params,
1387
-            $columns_to_select
1388
-        );
1389
-        return empty($results) ? null : reset($results);
1390
-    }
1391
-
1392
-
1393
-
1394
-    /**
1395
-     * Returns the a consecutive number of items in sequence from the given
1396
-     * value as found in the database matching the given query conditions.
1397
-     *
1398
-     * @param mixed  $current_field_value   Value used for the reference point.
1399
-     * @param string $operand               What operand is used for the sequence.
1400
-     * @param string $field_to_order_by     What field is used for the reference point.
1401
-     * @param int    $limit                 How many to return.
1402
-     * @param array  $query_params          Extra conditions on the query.
1403
-     * @param null   $columns_to_select     If left null, then an array of EE_Base_Class objects is returned,
1404
-     *                                      otherwise you can indicate just the columns you want returned.
1405
-     * @return EE_Base_Class[]|array
1406
-     * @throws EE_Error
1407
-     */
1408
-    protected function _get_consecutive(
1409
-        $current_field_value,
1410
-        $operand = '>',
1411
-        $field_to_order_by = null,
1412
-        $limit = 1,
1413
-        $query_params = array(),
1414
-        $columns_to_select = null
1415
-    ) {
1416
-        // if $field_to_order_by is empty then let's assume we're ordering by the primary key.
1417
-        if (empty($field_to_order_by)) {
1418
-            if ($this->has_primary_key_field()) {
1419
-                $field_to_order_by = $this->get_primary_key_field()->get_name();
1420
-            } else {
1421
-                if (WP_DEBUG) {
1422
-                    throw new EE_Error(esc_html__(
1423
-                        '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).',
1424
-                        'event_espresso'
1425
-                    ));
1426
-                }
1427
-                EE_Error::add_error(esc_html__('There was an error with the query.', 'event_espresso'));
1428
-                return array();
1429
-            }
1430
-        }
1431
-        if (! is_array($query_params)) {
1432
-            EE_Error::doing_it_wrong(
1433
-                'EEM_Base::_get_consecutive',
1434
-                sprintf(
1435
-                    esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1436
-                    gettype($query_params)
1437
-                ),
1438
-                '4.6.0'
1439
-            );
1440
-            $query_params = array();
1441
-        }
1442
-        // let's add the where query param for consecutive look up.
1443
-        $query_params[0][ $field_to_order_by ] = array($operand, $current_field_value);
1444
-        $query_params['limit'] = $limit;
1445
-        // set direction
1446
-        $incoming_orderby = isset($query_params['order_by']) ? (array) $query_params['order_by'] : array();
1447
-        $query_params['order_by'] = $operand === '>'
1448
-            ? array($field_to_order_by => 'ASC') + $incoming_orderby
1449
-            : array($field_to_order_by => 'DESC') + $incoming_orderby;
1450
-        // if $columns_to_select is empty then that means we're returning EE_Base_Class objects
1451
-        if (empty($columns_to_select)) {
1452
-            return $this->get_all($query_params);
1453
-        }
1454
-        // getting just the fields
1455
-        return $this->_get_all_wpdb_results($query_params, ARRAY_A, $columns_to_select);
1456
-    }
1457
-
1458
-
1459
-
1460
-    /**
1461
-     * This sets the _timezone property after model object has been instantiated.
1462
-     *
1463
-     * @param null | string $timezone valid PHP DateTimeZone timezone string
1464
-     */
1465
-    public function set_timezone($timezone)
1466
-    {
1467
-        if ($timezone !== null) {
1468
-            $this->_timezone = $timezone;
1469
-        }
1470
-        // note we need to loop through relations and set the timezone on those objects as well.
1471
-        foreach ($this->_model_relations as $relation) {
1472
-            $relation->set_timezone($timezone);
1473
-        }
1474
-        // and finally we do the same for any datetime fields
1475
-        foreach ($this->_fields as $field) {
1476
-            if ($field instanceof EE_Datetime_Field) {
1477
-                $field->set_timezone($timezone);
1478
-            }
1479
-        }
1480
-    }
1481
-
1482
-
1483
-
1484
-    /**
1485
-     * This just returns whatever is set for the current timezone.
1486
-     *
1487
-     * @access public
1488
-     * @return string
1489
-     */
1490
-    public function get_timezone()
1491
-    {
1492
-        // first validate if timezone is set.  If not, then let's set it be whatever is set on the model fields.
1493
-        if (empty($this->_timezone)) {
1494
-            foreach ($this->_fields as $field) {
1495
-                if ($field instanceof EE_Datetime_Field) {
1496
-                    $this->set_timezone($field->get_timezone());
1497
-                    break;
1498
-                }
1499
-            }
1500
-        }
1501
-        // if timezone STILL empty then return the default timezone for the site.
1502
-        if (empty($this->_timezone)) {
1503
-            $this->set_timezone(EEH_DTT_Helper::get_timezone());
1504
-        }
1505
-        return $this->_timezone;
1506
-    }
1507
-
1508
-
1509
-
1510
-    /**
1511
-     * This returns the date formats set for the given field name and also ensures that
1512
-     * $this->_timezone property is set correctly.
1513
-     *
1514
-     * @since 4.6.x
1515
-     * @param string $field_name The name of the field the formats are being retrieved for.
1516
-     * @param bool   $pretty     Whether to return the pretty formats (true) or not (false).
1517
-     * @throws EE_Error   If the given field_name is not of the EE_Datetime_Field type.
1518
-     * @return array formats in an array with the date format first, and the time format last.
1519
-     */
1520
-    public function get_formats_for($field_name, $pretty = false)
1521
-    {
1522
-        $field_settings = $this->field_settings_for($field_name);
1523
-        // if not a valid EE_Datetime_Field then throw error
1524
-        if (! $field_settings instanceof EE_Datetime_Field) {
1525
-            throw new EE_Error(sprintf(esc_html__(
1526
-                '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.',
1527
-                'event_espresso'
1528
-            ), $field_name));
1529
-        }
1530
-        // while we are here, let's make sure the timezone internally in EEM_Base matches what is stored on
1531
-        // the field.
1532
-        $this->_timezone = $field_settings->get_timezone();
1533
-        return array($field_settings->get_date_format($pretty), $field_settings->get_time_format($pretty));
1534
-    }
1535
-
1536
-
1537
-
1538
-    /**
1539
-     * This returns the current time in a format setup for a query on this model.
1540
-     * Usage of this method makes it easier to setup queries against EE_Datetime_Field columns because
1541
-     * it will return:
1542
-     *  - a formatted string in the timezone and format currently set on the EE_Datetime_Field for the given field for
1543
-     *  NOW
1544
-     *  - or a unix timestamp (equivalent to time())
1545
-     * Note: When requesting a formatted string, if the date or time format doesn't include seconds, for example,
1546
-     * the time returned, because it uses that format, will also NOT include seconds. For this reason, if you want
1547
-     * the time returned to be the current time down to the exact second, set $timestamp to true.
1548
-     * @since 4.6.x
1549
-     * @param string $field_name       The field the current time is needed for.
1550
-     * @param bool   $timestamp        True means to return a unix timestamp. Otherwise a
1551
-     *                                 formatted string matching the set format for the field in the set timezone will
1552
-     *                                 be returned.
1553
-     * @param string $what             Whether to return the string in just the time format, the date format, or both.
1554
-     * @throws EE_Error    If the given field_name is not of the EE_Datetime_Field type.
1555
-     * @return int|string  If the given field_name is not of the EE_Datetime_Field type, then an EE_Error
1556
-     *                                 exception is triggered.
1557
-     */
1558
-    public function current_time_for_query($field_name, $timestamp = false, $what = 'both')
1559
-    {
1560
-        $formats = $this->get_formats_for($field_name);
1561
-        $DateTime = new DateTime("now", new DateTimeZone($this->_timezone));
1562
-        if ($timestamp) {
1563
-            return $DateTime->format('U');
1564
-        }
1565
-        // not returning timestamp, so return formatted string in timezone.
1566
-        switch ($what) {
1567
-            case 'time':
1568
-                return $DateTime->format($formats[1]);
1569
-                break;
1570
-            case 'date':
1571
-                return $DateTime->format($formats[0]);
1572
-                break;
1573
-            default:
1574
-                return $DateTime->format(implode(' ', $formats));
1575
-                break;
1576
-        }
1577
-    }
1578
-
1579
-
1580
-
1581
-    /**
1582
-     * This receives a time string for a given field and ensures that it is setup to match what the internal settings
1583
-     * for the model are.  Returns a DateTime object.
1584
-     * Note: a gotcha for when you send in unix timestamp.  Remember a unix timestamp is already timezone agnostic,
1585
-     * (functionally the equivalent of UTC+0).  So when you send it in, whatever timezone string you include is
1586
-     * ignored.
1587
-     *
1588
-     * @param string $field_name      The field being setup.
1589
-     * @param string $timestring      The date time string being used.
1590
-     * @param string $incoming_format The format for the time string.
1591
-     * @param string $timezone        By default, it is assumed the incoming time string is in timezone for
1592
-     *                                the blog.  If this is not the case, then it can be specified here.  If incoming
1593
-     *                                format is
1594
-     *                                'U', this is ignored.
1595
-     * @return DateTime
1596
-     * @throws EE_Error
1597
-     */
1598
-    public function convert_datetime_for_query($field_name, $timestring, $incoming_format, $timezone = '')
1599
-    {
1600
-        // just using this to ensure the timezone is set correctly internally
1601
-        $this->get_formats_for($field_name);
1602
-        // load EEH_DTT_Helper
1603
-        $set_timezone = empty($timezone) ? EEH_DTT_Helper::get_timezone() : $timezone;
1604
-        $incomingDateTime = date_create_from_format($incoming_format, $timestring, new DateTimeZone($set_timezone));
1605
-        EEH_DTT_Helper::setTimezone($incomingDateTime, new DateTimeZone($this->_timezone));
1606
-        return \EventEspresso\core\domain\entities\DbSafeDateTime::createFromDateTime($incomingDateTime);
1607
-    }
1608
-
1609
-
1610
-
1611
-    /**
1612
-     * Gets all the tables comprising this model. Array keys are the table aliases, and values are EE_Table objects
1613
-     *
1614
-     * @return EE_Table_Base[]
1615
-     */
1616
-    public function get_tables()
1617
-    {
1618
-        return $this->_tables;
1619
-    }
1620
-
1621
-
1622
-
1623
-    /**
1624
-     * Updates all the database entries (in each table for this model) according to $fields_n_values and optionally
1625
-     * also updates all the model objects, where the criteria expressed in $query_params are met..
1626
-     * Also note: if this model has multiple tables, this update verifies all the secondary tables have an entry for
1627
-     * each row (in the primary table) we're trying to update; if not, it inserts an entry in the secondary table. Eg:
1628
-     * if our model has 2 tables: wp_posts (primary), and wp_esp_event (secondary). Let's say we are trying to update a
1629
-     * model object with EVT_ID = 1
1630
-     * (which means where wp_posts has ID = 1, because wp_posts.ID is the primary key's column), which exists, but
1631
-     * there is no entry in wp_esp_event for this entry in wp_posts. So, this update script will insert a row into
1632
-     * wp_esp_event, using any available parameters from $fields_n_values (eg, if "EVT_limit" => 40 is in
1633
-     * $fields_n_values, the new entry in wp_esp_event will set EVT_limit = 40, and use default for other columns which
1634
-     * are not specified)
1635
-     *
1636
-     * @param array   $fields_n_values         keys are model fields (exactly like keys in EEM_Base::_fields, NOT db
1637
-     *                                         columns!), values are strings, ints, floats, and maybe arrays if they
1638
-     *                                         are to be serialized. Basically, the values are what you'd expect to be
1639
-     *                                         values on the model, NOT necessarily what's in the DB. For example, if
1640
-     *                                         we wanted to update only the TXN_details on any Transactions where its
1641
-     *                                         ID=34, we'd use this method as follows:
1642
-     *                                         EEM_Transaction::instance()->update(
1643
-     *                                         array('TXN_details'=>array('detail1'=>'monkey','detail2'=>'banana'),
1644
-     *                                         array(array('TXN_ID'=>34)));
1645
-     * @param array   $query_params            @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1646
-     *                                         Eg, consider updating Question's QST_admin_label field is of type
1647
-     *                                         Simple_HTML. If you use this function to update that field to $new_value
1648
-     *                                         = (note replace 8's with appropriate opening and closing tags in the
1649
-     *                                         following example)"8script8alert('I hack all');8/script88b8boom
1650
-     *                                         baby8/b8", then if you set $values_already_prepared_by_model_object to
1651
-     *                                         TRUE, it is assumed that you've already called
1652
-     *                                         EE_Simple_HTML_Field->prepare_for_set($new_value), which removes the
1653
-     *                                         malicious javascript. However, if
1654
-     *                                         $values_already_prepared_by_model_object is left as FALSE, then
1655
-     *                                         EE_Simple_HTML_Field->prepare_for_set($new_value) will be called on it,
1656
-     *                                         and every other field, before insertion. We provide this parameter
1657
-     *                                         because model objects perform their prepare_for_set function on all
1658
-     *                                         their values, and so don't need to be called again (and in many cases,
1659
-     *                                         shouldn't be called again. Eg: if we escape HTML characters in the
1660
-     *                                         prepare_for_set method...)
1661
-     * @param boolean $keep_model_objs_in_sync if TRUE, makes sure we ALSO update model objects
1662
-     *                                         in this model's entity map according to $fields_n_values that match
1663
-     *                                         $query_params. This obviously has some overhead, so you can disable it
1664
-     *                                         by setting this to FALSE, but be aware that model objects being used
1665
-     *                                         could get out-of-sync with the database
1666
-     * @return int how many rows got updated or FALSE if something went wrong with the query (wp returns FALSE or num
1667
-     *                                         rows affected which *could* include 0 which DOES NOT mean the query was
1668
-     *                                         bad)
1669
-     * @throws EE_Error
1670
-     */
1671
-    public function update($fields_n_values, $query_params, $keep_model_objs_in_sync = true)
1672
-    {
1673
-        if (! is_array($query_params)) {
1674
-            EE_Error::doing_it_wrong(
1675
-                'EEM_Base::update',
1676
-                sprintf(
1677
-                    esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1678
-                    gettype($query_params)
1679
-                ),
1680
-                '4.6.0'
1681
-            );
1682
-            $query_params = array();
1683
-        }
1684
-        /**
1685
-         * Action called before a model update call has been made.
1686
-         *
1687
-         * @param EEM_Base $model
1688
-         * @param array    $fields_n_values the updated fields and their new values
1689
-         * @param array    $query_params    @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1690
-         */
1691
-        do_action('AHEE__EEM_Base__update__begin', $this, $fields_n_values, $query_params);
1692
-        /**
1693
-         * Filters the fields about to be updated given the query parameters. You can provide the
1694
-         * $query_params to $this->get_all() to find exactly which records will be updated
1695
-         *
1696
-         * @param array    $fields_n_values fields and their new values
1697
-         * @param EEM_Base $model           the model being queried
1698
-         * @param array    $query_params    @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1699
-         */
1700
-        $fields_n_values = (array) apply_filters(
1701
-            'FHEE__EEM_Base__update__fields_n_values',
1702
-            $fields_n_values,
1703
-            $this,
1704
-            $query_params
1705
-        );
1706
-        // need to verify that, for any entry we want to update, there are entries in each secondary table.
1707
-        // to do that, for each table, verify that it's PK isn't null.
1708
-        $tables = $this->get_tables();
1709
-        // 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
1710
-        // NOTE: we should make this code more efficient by NOT querying twice
1711
-        // before the real update, but that needs to first go through ALPHA testing
1712
-        // as it's dangerous. says Mike August 8 2014
1713
-        // we want to make sure the default_where strategy is ignored
1714
-        $this->_ignore_where_strategy = true;
1715
-        $wpdb_select_results = $this->_get_all_wpdb_results($query_params);
1716
-        foreach ($wpdb_select_results as $wpdb_result) {
1717
-            // type cast stdClass as array
1718
-            $wpdb_result = (array) $wpdb_result;
1719
-            // get the model object's PK, as we'll want this if we need to insert a row into secondary tables
1720
-            if ($this->has_primary_key_field()) {
1721
-                $main_table_pk_value = $wpdb_result[ $this->get_primary_key_field()->get_qualified_column() ];
1722
-            } else {
1723
-                // 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)
1724
-                $main_table_pk_value = null;
1725
-            }
1726
-            // 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
1727
-            // 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
1728
-            if (count($tables) > 1) {
1729
-                // foreach matching row in the DB, ensure that each table's PK isn't null. If so, there must not be an entry
1730
-                // in that table, and so we'll want to insert one
1731
-                foreach ($tables as $table_obj) {
1732
-                    $this_table_pk_column = $table_obj->get_fully_qualified_pk_column();
1733
-                    // if there is no private key for this table on the results, it means there's no entry
1734
-                    // in this table, right? so insert a row in the current table, using any fields available
1735
-                    if (
1736
-                        ! (array_key_exists($this_table_pk_column, $wpdb_result)
1737
-                           && $wpdb_result[ $this_table_pk_column ])
1738
-                    ) {
1739
-                        $success = $this->_insert_into_specific_table(
1740
-                            $table_obj,
1741
-                            $fields_n_values,
1742
-                            $main_table_pk_value
1743
-                        );
1744
-                        // if we died here, report the error
1745
-                        if (! $success) {
1746
-                            return false;
1747
-                        }
1748
-                    }
1749
-                }
1750
-            }
1751
-            //              //and now check that if we have cached any models by that ID on the model, that
1752
-            //              //they also get updated properly
1753
-            //              $model_object = $this->get_from_entity_map( $main_table_pk_value );
1754
-            //              if( $model_object ){
1755
-            //                  foreach( $fields_n_values as $field => $value ){
1756
-            //                      $model_object->set($field, $value);
1757
-            // let's make sure default_where strategy is followed now
1758
-            $this->_ignore_where_strategy = false;
1759
-        }
1760
-        // if we want to keep model objects in sync, AND
1761
-        // if this wasn't called from a model object (to update itself)
1762
-        // then we want to make sure we keep all the existing
1763
-        // model objects in sync with the db
1764
-        if ($keep_model_objs_in_sync && ! $this->_values_already_prepared_by_model_object) {
1765
-            if ($this->has_primary_key_field()) {
1766
-                $model_objs_affected_ids = $this->get_col($query_params);
1767
-            } else {
1768
-                // we need to select a bunch of columns and then combine them into the the "index primary key string"s
1769
-                $models_affected_key_columns = $this->_get_all_wpdb_results($query_params, ARRAY_A);
1770
-                $model_objs_affected_ids = array();
1771
-                foreach ($models_affected_key_columns as $row) {
1772
-                    $combined_index_key = $this->get_index_primary_key_string($row);
1773
-                    $model_objs_affected_ids[ $combined_index_key ] = $combined_index_key;
1774
-                }
1775
-            }
1776
-            if (! $model_objs_affected_ids) {
1777
-                // wait wait wait- if nothing was affected let's stop here
1778
-                return 0;
1779
-            }
1780
-            foreach ($model_objs_affected_ids as $id) {
1781
-                $model_obj_in_entity_map = $this->get_from_entity_map($id);
1782
-                if ($model_obj_in_entity_map) {
1783
-                    foreach ($fields_n_values as $field => $new_value) {
1784
-                        $model_obj_in_entity_map->set($field, $new_value);
1785
-                    }
1786
-                }
1787
-            }
1788
-            // if there is a primary key on this model, we can now do a slight optimization
1789
-            if ($this->has_primary_key_field()) {
1790
-                // we already know what we want to update. So let's make the query simpler so it's a little more efficient
1791
-                $query_params = array(
1792
-                    array($this->primary_key_name() => array('IN', $model_objs_affected_ids)),
1793
-                    'limit'                    => count($model_objs_affected_ids),
1794
-                    'default_where_conditions' => EEM_Base::default_where_conditions_none,
1795
-                );
1796
-            }
1797
-        }
1798
-        $model_query_info = $this->_create_model_query_info_carrier($query_params);
1799
-        $SQL = "UPDATE "
1800
-               . $model_query_info->get_full_join_sql()
1801
-               . " SET "
1802
-               . $this->_construct_update_sql($fields_n_values)
1803
-               . $model_query_info->get_where_sql();// note: doesn't use _construct_2nd_half_of_select_query() because doesn't accept LIMIT, ORDER BY, etc.
1804
-        $rows_affected = $this->_do_wpdb_query('query', array($SQL));
1805
-        /**
1806
-         * Action called after a model update call has been made.
1807
-         *
1808
-         * @param EEM_Base $model
1809
-         * @param array    $fields_n_values the updated fields and their new values
1810
-         * @param array    $query_params    @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1811
-         * @param int      $rows_affected
1812
-         */
1813
-        do_action('AHEE__EEM_Base__update__end', $this, $fields_n_values, $query_params, $rows_affected);
1814
-        return $rows_affected;// how many supposedly got updated
1815
-    }
1816
-
1817
-
1818
-
1819
-    /**
1820
-     * Analogous to $wpdb->get_col, returns a 1-dimensional array where teh values
1821
-     * are teh values of the field specified (or by default the primary key field)
1822
-     * that matched the query params. Note that you should pass the name of the
1823
-     * model FIELD, not the database table's column name.
1824
-     *
1825
-     * @param array  $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1826
-     * @param string $field_to_select
1827
-     * @return array just like $wpdb->get_col()
1828
-     * @throws EE_Error
1829
-     */
1830
-    public function get_col($query_params = array(), $field_to_select = null)
1831
-    {
1832
-        if ($field_to_select) {
1833
-            $field = $this->field_settings_for($field_to_select);
1834
-        } elseif ($this->has_primary_key_field()) {
1835
-            $field = $this->get_primary_key_field();
1836
-        } else {
1837
-            // no primary key, just grab the first column
1838
-            $field_settings = $this->field_settings();
1839
-            $field = reset($field_settings);
1840
-        }
1841
-        $model_query_info = $this->_create_model_query_info_carrier($query_params);
1842
-        $select_expressions = $field->get_qualified_column();
1843
-        $SQL = "SELECT $select_expressions " . $this->_construct_2nd_half_of_select_query($model_query_info);
1844
-        return $this->_do_wpdb_query('get_col', array($SQL));
1845
-    }
1846
-
1847
-
1848
-
1849
-    /**
1850
-     * Returns a single column value for a single row from the database
1851
-     *
1852
-     * @param array  $query_params    @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1853
-     * @param string $field_to_select @see EEM_Base::get_col()
1854
-     * @return string
1855
-     * @throws EE_Error
1856
-     */
1857
-    public function get_var($query_params = array(), $field_to_select = null)
1858
-    {
1859
-        $query_params['limit'] = 1;
1860
-        $col = $this->get_col($query_params, $field_to_select);
1861
-        if (! empty($col)) {
1862
-            return reset($col);
1863
-        }
1864
-        return null;
1865
-    }
1866
-
1867
-
1868
-
1869
-    /**
1870
-     * Makes the SQL for after "UPDATE table_X inner join table_Y..." and before "...WHERE". Eg "Question.name='party
1871
-     * time?', Question.desc='what do you think?',..." Values are filtered through wpdb->prepare to avoid against SQL
1872
-     * injection, but currently no further filtering is done
1873
-     *
1874
-     * @global      $wpdb
1875
-     * @param array $fields_n_values array keys are field names on this model, and values are what those fields should
1876
-     *                               be updated to in the DB
1877
-     * @return string of SQL
1878
-     * @throws EE_Error
1879
-     */
1880
-    public function _construct_update_sql($fields_n_values)
1881
-    {
1882
-        /** @type WPDB $wpdb */
1883
-        global $wpdb;
1884
-        $cols_n_values = array();
1885
-        foreach ($fields_n_values as $field_name => $value) {
1886
-            $field_obj = $this->field_settings_for($field_name);
1887
-            // if the value is NULL, we want to assign the value to that.
1888
-            // wpdb->prepare doesn't really handle that properly
1889
-            $prepared_value = $this->_prepare_value_or_use_default($field_obj, $fields_n_values);
1890
-            $value_sql = $prepared_value === null ? 'NULL'
1891
-                : $wpdb->prepare($field_obj->get_wpdb_data_type(), $prepared_value);
1892
-            $cols_n_values[] = $field_obj->get_qualified_column() . "=" . $value_sql;
1893
-        }
1894
-        return implode(",", $cols_n_values);
1895
-    }
1896
-
1897
-
1898
-
1899
-    /**
1900
-     * Deletes a single row from the DB given the model object's primary key value. (eg, EE_Attendee->ID()'s value).
1901
-     * Performs a HARD delete, meaning the database row should always be removed,
1902
-     * not just have a flag field on it switched
1903
-     * Wrapper for EEM_Base::delete_permanently()
1904
-     *
1905
-     * @param mixed $id
1906
-     * @param boolean $allow_blocking
1907
-     * @return int the number of rows deleted
1908
-     * @throws EE_Error
1909
-     */
1910
-    public function delete_permanently_by_ID($id, $allow_blocking = true)
1911
-    {
1912
-        return $this->delete_permanently(
1913
-            array(
1914
-                array($this->get_primary_key_field()->get_name() => $id),
1915
-                'limit' => 1,
1916
-            ),
1917
-            $allow_blocking
1918
-        );
1919
-    }
1920
-
1921
-
1922
-
1923
-    /**
1924
-     * Deletes a single row from the DB given the model object's primary key value. (eg, EE_Attendee->ID()'s value).
1925
-     * Wrapper for EEM_Base::delete()
1926
-     *
1927
-     * @param mixed $id
1928
-     * @param boolean $allow_blocking
1929
-     * @return int the number of rows deleted
1930
-     * @throws EE_Error
1931
-     */
1932
-    public function delete_by_ID($id, $allow_blocking = true)
1933
-    {
1934
-        return $this->delete(
1935
-            array(
1936
-                array($this->get_primary_key_field()->get_name() => $id),
1937
-                'limit' => 1,
1938
-            ),
1939
-            $allow_blocking
1940
-        );
1941
-    }
1942
-
1943
-
1944
-
1945
-    /**
1946
-     * Identical to delete_permanently, but does a "soft" delete if possible,
1947
-     * meaning if the model has a field that indicates its been "trashed" or
1948
-     * "soft deleted", we will just set that instead of actually deleting the rows.
1949
-     *
1950
-     * @see EEM_Base::delete_permanently
1951
-     * @param array   $query_params
1952
-     * @param boolean $allow_blocking
1953
-     * @return int how many rows got deleted
1954
-     * @throws EE_Error
1955
-     */
1956
-    public function delete($query_params, $allow_blocking = true)
1957
-    {
1958
-        return $this->delete_permanently($query_params, $allow_blocking);
1959
-    }
1960
-
1961
-
1962
-
1963
-    /**
1964
-     * Deletes the model objects that meet the query params. Note: this method is overridden
1965
-     * in EEM_Soft_Delete_Base so that soft-deleted model objects are instead only flagged
1966
-     * as archived, not actually deleted
1967
-     *
1968
-     * @param array   $query_params   @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1969
-     * @param boolean $allow_blocking if TRUE, matched objects will only be deleted if there is no related model info
1970
-     *                                that blocks it (ie, there' sno other data that depends on this data); if false,
1971
-     *                                deletes regardless of other objects which may depend on it. Its generally
1972
-     *                                advisable to always leave this as TRUE, otherwise you could easily corrupt your
1973
-     *                                DB
1974
-     * @return int how many rows got deleted
1975
-     * @throws EE_Error
1976
-     */
1977
-    public function delete_permanently($query_params, $allow_blocking = true)
1978
-    {
1979
-        /**
1980
-         * Action called just before performing a real deletion query. You can use the
1981
-         * model and its $query_params to find exactly which items will be deleted
1982
-         *
1983
-         * @param EEM_Base $model
1984
-         * @param array    $query_params   @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1985
-         * @param boolean  $allow_blocking whether or not to allow related model objects
1986
-         *                                 to block (prevent) this deletion
1987
-         */
1988
-        do_action('AHEE__EEM_Base__delete__begin', $this, $query_params, $allow_blocking);
1989
-        // some MySQL databases may be running safe mode, which may restrict
1990
-        // deletion if there is no KEY column used in the WHERE statement of a deletion.
1991
-        // to get around this, we first do a SELECT, get all the IDs, and then run another query
1992
-        // to delete them
1993
-        $items_for_deletion = $this->_get_all_wpdb_results($query_params);
1994
-        $columns_and_ids_for_deleting = $this->_get_ids_for_delete($items_for_deletion, $allow_blocking);
1995
-        $deletion_where_query_part = $this->_build_query_part_for_deleting_from_columns_and_values(
1996
-            $columns_and_ids_for_deleting
1997
-        );
1998
-        /**
1999
-         * Allows client code to act on the items being deleted before the query is actually executed.
2000
-         *
2001
-         * @param EEM_Base $this  The model instance being acted on.
2002
-         * @param array    $query_params  The incoming array of query parameters influencing what gets deleted.
2003
-         * @param bool     $allow_blocking @see param description in method phpdoc block.
2004
-         * @param array $columns_and_ids_for_deleting       An array indicating what entities will get removed as
2005
-         *                                                  derived from the incoming query parameters.
2006
-         *                                                  @see details on the structure of this array in the phpdocs
2007
-         *                                                  for the `_get_ids_for_delete_method`
2008
-         *
2009
-         */
2010
-        do_action(
2011
-            'AHEE__EEM_Base__delete__before_query',
2012
-            $this,
2013
-            $query_params,
2014
-            $allow_blocking,
2015
-            $columns_and_ids_for_deleting
2016
-        );
2017
-        if ($deletion_where_query_part) {
2018
-            $model_query_info = $this->_create_model_query_info_carrier($query_params);
2019
-            $table_aliases = array_keys($this->_tables);
2020
-            $SQL = "DELETE "
2021
-                   . implode(", ", $table_aliases)
2022
-                   . " FROM "
2023
-                   . $model_query_info->get_full_join_sql()
2024
-                   . " WHERE "
2025
-                   . $deletion_where_query_part;
2026
-            $rows_deleted = $this->_do_wpdb_query('query', array($SQL));
2027
-        } else {
2028
-            $rows_deleted = 0;
2029
-        }
2030
-
2031
-        // Next, make sure those items are removed from the entity map; if they could be put into it at all; and if
2032
-        // there was no error with the delete query.
2033
-        if (
2034
-            $this->has_primary_key_field()
2035
-            && $rows_deleted !== false
2036
-            && isset($columns_and_ids_for_deleting[ $this->get_primary_key_field()->get_qualified_column() ])
2037
-        ) {
2038
-            $ids_for_removal = $columns_and_ids_for_deleting[ $this->get_primary_key_field()->get_qualified_column() ];
2039
-            foreach ($ids_for_removal as $id) {
2040
-                if (isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])) {
2041
-                    unset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ]);
2042
-                }
2043
-            }
2044
-
2045
-            // delete any extra meta attached to the deleted entities but ONLY if this model is not an instance of
2046
-            // `EEM_Extra_Meta`.  In other words we want to prevent recursion on EEM_Extra_Meta::delete_permanently calls
2047
-            // unnecessarily.  It's very unlikely that users will have assigned Extra Meta to Extra Meta
2048
-            // (although it is possible).
2049
-            // Note this can be skipped by using the provided filter and returning false.
2050
-            if (
2051
-                apply_filters(
2052
-                    'FHEE__EEM_Base__delete_permanently__dont_delete_extra_meta_for_extra_meta',
2053
-                    ! $this instanceof EEM_Extra_Meta,
2054
-                    $this
2055
-                )
2056
-            ) {
2057
-                EEM_Extra_Meta::instance()->delete_permanently(array(
2058
-                    0 => array(
2059
-                        'EXM_type' => $this->get_this_model_name(),
2060
-                        'OBJ_ID'   => array(
2061
-                            'IN',
2062
-                            $ids_for_removal
2063
-                        )
2064
-                    )
2065
-                ));
2066
-            }
2067
-        }
2068
-
2069
-        /**
2070
-         * Action called just after performing a real deletion query. Although at this point the
2071
-         * items should have been deleted
2072
-         *
2073
-         * @param EEM_Base $model
2074
-         * @param array    $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2075
-         * @param int      $rows_deleted
2076
-         */
2077
-        do_action('AHEE__EEM_Base__delete__end', $this, $query_params, $rows_deleted, $columns_and_ids_for_deleting);
2078
-        return $rows_deleted;// how many supposedly got deleted
2079
-    }
2080
-
2081
-
2082
-
2083
-    /**
2084
-     * Checks all the relations that throw error messages when there are blocking related objects
2085
-     * for related model objects. If there are any related model objects on those relations,
2086
-     * adds an EE_Error, and return true
2087
-     *
2088
-     * @param EE_Base_Class|int $this_model_obj_or_id
2089
-     * @param EE_Base_Class     $ignore_this_model_obj a model object like 'EE_Event', or 'EE_Term_Taxonomy', which
2090
-     *                                                 should be ignored when determining whether there are related
2091
-     *                                                 model objects which block this model object's deletion. Useful
2092
-     *                                                 if you know A is related to B and are considering deleting A,
2093
-     *                                                 but want to see if A has any other objects blocking its deletion
2094
-     *                                                 before removing the relation between A and B
2095
-     * @return boolean
2096
-     * @throws EE_Error
2097
-     */
2098
-    public function delete_is_blocked_by_related_models($this_model_obj_or_id, $ignore_this_model_obj = null)
2099
-    {
2100
-        // first, if $ignore_this_model_obj was supplied, get its model
2101
-        if ($ignore_this_model_obj && $ignore_this_model_obj instanceof EE_Base_Class) {
2102
-            $ignored_model = $ignore_this_model_obj->get_model();
2103
-        } else {
2104
-            $ignored_model = null;
2105
-        }
2106
-        // now check all the relations of $this_model_obj_or_id and see if there
2107
-        // are any related model objects blocking it?
2108
-        $is_blocked = false;
2109
-        foreach ($this->_model_relations as $relation_name => $relation_obj) {
2110
-            if ($relation_obj->block_delete_if_related_models_exist()) {
2111
-                // if $ignore_this_model_obj was supplied, then for the query
2112
-                // on that model needs to be told to ignore $ignore_this_model_obj
2113
-                if ($ignored_model && $relation_name === $ignored_model->get_this_model_name()) {
2114
-                    $related_model_objects = $relation_obj->get_all_related($this_model_obj_or_id, array(
2115
-                        array(
2116
-                            $ignored_model->get_primary_key_field()->get_name() => array(
2117
-                                '!=',
2118
-                                $ignore_this_model_obj->ID(),
2119
-                            ),
2120
-                        ),
2121
-                    ));
2122
-                } else {
2123
-                    $related_model_objects = $relation_obj->get_all_related($this_model_obj_or_id);
2124
-                }
2125
-                if ($related_model_objects) {
2126
-                    EE_Error::add_error($relation_obj->get_deletion_error_message(), __FILE__, __FUNCTION__, __LINE__);
2127
-                    $is_blocked = true;
2128
-                }
2129
-            }
2130
-        }
2131
-        return $is_blocked;
2132
-    }
2133
-
2134
-
2135
-    /**
2136
-     * Builds the columns and values for items to delete from the incoming $row_results_for_deleting array.
2137
-     * @param array $row_results_for_deleting
2138
-     * @param bool  $allow_blocking
2139
-     * @return array   The shape of this array depends on whether the model `has_primary_key_field` or not.  If the
2140
-     *                 model DOES have a primary_key_field, then the array will be a simple single dimension array where
2141
-     *                 the key is the fully qualified primary key column and the value is an array of ids that will be
2142
-     *                 deleted. Example:
2143
-     *                      array('Event.EVT_ID' => array( 1,2,3))
2144
-     *                 If the model DOES NOT have a primary_key_field, then the array will be a two dimensional array
2145
-     *                 where each element is a group of columns and values that get deleted. Example:
2146
-     *                      array(
2147
-     *                          0 => array(
2148
-     *                              'Term_Relationship.object_id' => 1
2149
-     *                              'Term_Relationship.term_taxonomy_id' => 5
2150
-     *                          ),
2151
-     *                          1 => array(
2152
-     *                              'Term_Relationship.object_id' => 1
2153
-     *                              'Term_Relationship.term_taxonomy_id' => 6
2154
-     *                          )
2155
-     *                      )
2156
-     * @throws EE_Error
2157
-     */
2158
-    protected function _get_ids_for_delete(array $row_results_for_deleting, $allow_blocking = true)
2159
-    {
2160
-        $ids_to_delete_indexed_by_column = array();
2161
-        if ($this->has_primary_key_field()) {
2162
-            $primary_table = $this->_get_main_table();
2163
-            $primary_table_pk_field = $this->get_field_by_column($primary_table->get_fully_qualified_pk_column());
2164
-            $other_tables = $this->_get_other_tables();
2165
-            $ids_to_delete_indexed_by_column = $query = array();
2166
-            foreach ($row_results_for_deleting as $item_to_delete) {
2167
-                // before we mark this item for deletion,
2168
-                // make sure there's no related entities blocking its deletion (if we're checking)
2169
-                if (
2170
-                    $allow_blocking
2171
-                    && $this->delete_is_blocked_by_related_models(
2172
-                        $item_to_delete[ $primary_table->get_fully_qualified_pk_column() ]
2173
-                    )
2174
-                ) {
2175
-                    continue;
2176
-                }
2177
-                // primary table deletes
2178
-                if (isset($item_to_delete[ $primary_table->get_fully_qualified_pk_column() ])) {
2179
-                    $ids_to_delete_indexed_by_column[ $primary_table->get_fully_qualified_pk_column() ][] =
2180
-                        $item_to_delete[ $primary_table->get_fully_qualified_pk_column() ];
2181
-                }
2182
-            }
2183
-        } elseif (count($this->get_combined_primary_key_fields()) > 1) {
2184
-            $fields = $this->get_combined_primary_key_fields();
2185
-            foreach ($row_results_for_deleting as $item_to_delete) {
2186
-                $ids_to_delete_indexed_by_column_for_row = array();
2187
-                foreach ($fields as $cpk_field) {
2188
-                    if ($cpk_field instanceof EE_Model_Field_Base) {
2189
-                        $ids_to_delete_indexed_by_column_for_row[ $cpk_field->get_qualified_column() ] =
2190
-                            $item_to_delete[ $cpk_field->get_qualified_column() ];
2191
-                    }
2192
-                }
2193
-                $ids_to_delete_indexed_by_column[] = $ids_to_delete_indexed_by_column_for_row;
2194
-            }
2195
-        } else {
2196
-            // so there's no primary key and no combined key...
2197
-            // sorry, can't help you
2198
-            throw new EE_Error(
2199
-                sprintf(
2200
-                    esc_html__(
2201
-                        "Cannot delete objects of type %s because there is no primary key NOR combined key",
2202
-                        "event_espresso"
2203
-                    ),
2204
-                    get_class($this)
2205
-                )
2206
-            );
2207
-        }
2208
-        return $ids_to_delete_indexed_by_column;
2209
-    }
2210
-
2211
-
2212
-    /**
2213
-     * This receives an array of columns and values set to be deleted (as prepared by _get_ids_for_delete) and prepares
2214
-     * the corresponding query_part for the query performing the delete.
2215
-     *
2216
-     * @param array $ids_to_delete_indexed_by_column @see _get_ids_for_delete for how this array might be shaped.
2217
-     * @return string
2218
-     * @throws EE_Error
2219
-     */
2220
-    protected function _build_query_part_for_deleting_from_columns_and_values(array $ids_to_delete_indexed_by_column)
2221
-    {
2222
-        $query_part = '';
2223
-        if (empty($ids_to_delete_indexed_by_column)) {
2224
-            return $query_part;
2225
-        } elseif ($this->has_primary_key_field()) {
2226
-            $query = array();
2227
-            foreach ($ids_to_delete_indexed_by_column as $column => $ids) {
2228
-                $query[] = $column . ' IN' . $this->_construct_in_value($ids, $this->_primary_key_field);
2229
-            }
2230
-            $query_part = ! empty($query) ? implode(' AND ', $query) : $query_part;
2231
-        } elseif (count($this->get_combined_primary_key_fields()) > 1) {
2232
-            $ways_to_identify_a_row = array();
2233
-            foreach ($ids_to_delete_indexed_by_column as $ids_to_delete_indexed_by_column_for_each_row) {
2234
-                $values_for_each_combined_primary_key_for_a_row = array();
2235
-                foreach ($ids_to_delete_indexed_by_column_for_each_row as $column => $id) {
2236
-                    $values_for_each_combined_primary_key_for_a_row[] = $column . '=' . $id;
2237
-                }
2238
-                $ways_to_identify_a_row[] = '('
2239
-                                            . implode(' AND ', $values_for_each_combined_primary_key_for_a_row)
2240
-                                            . ')';
2241
-            }
2242
-            $query_part = implode(' OR ', $ways_to_identify_a_row);
2243
-        }
2244
-        return $query_part;
2245
-    }
2246
-
2247
-
2248
-
2249
-    /**
2250
-     * Gets the model field by the fully qualified name
2251
-     * @param string $qualified_column_name eg 'Event_CPT.post_name' or $field_obj->get_qualified_column()
2252
-     * @return EE_Model_Field_Base
2253
-     */
2254
-    public function get_field_by_column($qualified_column_name)
2255
-    {
2256
-        foreach ($this->field_settings(true) as $field_name => $field_obj) {
2257
-            if ($field_obj->get_qualified_column() === $qualified_column_name) {
2258
-                return $field_obj;
2259
-            }
2260
-        }
2261
-        throw new EE_Error(
2262
-            sprintf(
2263
-                esc_html__('Could not find a field on the model "%1$s" for qualified column "%2$s"', 'event_espresso'),
2264
-                $this->get_this_model_name(),
2265
-                $qualified_column_name
2266
-            )
2267
-        );
2268
-    }
2269
-
2270
-
2271
-
2272
-    /**
2273
-     * Count all the rows that match criteria the model query params.
2274
-     * If $field_to_count isn't provided, the model's primary key is used. Otherwise, we count by field_to_count's
2275
-     * column
2276
-     *
2277
-     * @param array  $query_params   @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2278
-     * @param string $field_to_count field on model to count by (not column name)
2279
-     * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2280
-     *                               that by the setting $distinct to TRUE;
2281
-     * @return int
2282
-     * @throws EE_Error
2283
-     */
2284
-    public function count($query_params = array(), $field_to_count = null, $distinct = false)
2285
-    {
2286
-        $model_query_info = $this->_create_model_query_info_carrier($query_params);
2287
-        if ($field_to_count) {
2288
-            $field_obj = $this->field_settings_for($field_to_count);
2289
-            $column_to_count = $field_obj->get_qualified_column();
2290
-        } elseif ($this->has_primary_key_field()) {
2291
-            $pk_field_obj = $this->get_primary_key_field();
2292
-            $column_to_count = $pk_field_obj->get_qualified_column();
2293
-        } else {
2294
-            // there's no primary key
2295
-            // if we're counting distinct items, and there's no primary key,
2296
-            // we need to list out the columns for distinction;
2297
-            // otherwise we can just use star
2298
-            if ($distinct) {
2299
-                $columns_to_use = array();
2300
-                foreach ($this->get_combined_primary_key_fields() as $field_obj) {
2301
-                    $columns_to_use[] = $field_obj->get_qualified_column();
2302
-                }
2303
-                $column_to_count = implode(',', $columns_to_use);
2304
-            } else {
2305
-                $column_to_count = '*';
2306
-            }
2307
-        }
2308
-        $column_to_count = $distinct ? "DISTINCT " . $column_to_count : $column_to_count;
2309
-        $SQL = "SELECT COUNT(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2310
-        return (int) $this->_do_wpdb_query('get_var', array($SQL));
2311
-    }
2312
-
2313
-
2314
-
2315
-    /**
2316
-     * Sums up the value of the $field_to_sum (defaults to the primary key, which isn't terribly useful)
2317
-     *
2318
-     * @param array  $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2319
-     * @param string $field_to_sum name of field (array key in $_fields array)
2320
-     * @return float
2321
-     * @throws EE_Error
2322
-     */
2323
-    public function sum($query_params, $field_to_sum = null)
2324
-    {
2325
-        $model_query_info = $this->_create_model_query_info_carrier($query_params);
2326
-        if ($field_to_sum) {
2327
-            $field_obj = $this->field_settings_for($field_to_sum);
2328
-        } else {
2329
-            $field_obj = $this->get_primary_key_field();
2330
-        }
2331
-        $column_to_count = $field_obj->get_qualified_column();
2332
-        $SQL = "SELECT SUM(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2333
-        $return_value = $this->_do_wpdb_query('get_var', array($SQL));
2334
-        $data_type = $field_obj->get_wpdb_data_type();
2335
-        if ($data_type === '%d' || $data_type === '%s') {
2336
-            return (float) $return_value;
2337
-        }
2338
-        // must be %f
2339
-        return (float) $return_value;
2340
-    }
2341
-
2342
-
2343
-
2344
-    /**
2345
-     * Just calls the specified method on $wpdb with the given arguments
2346
-     * Consolidates a little extra error handling code
2347
-     *
2348
-     * @param string $wpdb_method
2349
-     * @param array  $arguments_to_provide
2350
-     * @throws EE_Error
2351
-     * @global wpdb  $wpdb
2352
-     * @return mixed
2353
-     */
2354
-    protected function _do_wpdb_query($wpdb_method, $arguments_to_provide)
2355
-    {
2356
-        // if we're in maintenance mode level 2, DON'T run any queries
2357
-        // because level 2 indicates the database needs updating and
2358
-        // is probably out of sync with the code
2359
-        if (! EE_Maintenance_Mode::instance()->models_can_query()) {
2360
-            throw new EE_Error(sprintf(esc_html__(
2361
-                "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.",
2362
-                "event_espresso"
2363
-            )));
2364
-        }
2365
-        /** @type WPDB $wpdb */
2366
-        global $wpdb;
2367
-        if (! method_exists($wpdb, $wpdb_method)) {
2368
-            throw new EE_Error(sprintf(esc_html__(
2369
-                'There is no method named "%s" on Wordpress\' $wpdb object',
2370
-                'event_espresso'
2371
-            ), $wpdb_method));
2372
-        }
2373
-        if (WP_DEBUG) {
2374
-            $old_show_errors_value = $wpdb->show_errors;
2375
-            $wpdb->show_errors(false);
2376
-        }
2377
-        $result = $this->_process_wpdb_query($wpdb_method, $arguments_to_provide);
2378
-        $this->show_db_query_if_previously_requested($wpdb->last_query);
2379
-        if (WP_DEBUG) {
2380
-            $wpdb->show_errors($old_show_errors_value);
2381
-            if (! empty($wpdb->last_error)) {
2382
-                throw new EE_Error(sprintf(esc_html__('WPDB Error: "%s"', 'event_espresso'), $wpdb->last_error));
2383
-            }
2384
-            if ($result === false) {
2385
-                throw new EE_Error(sprintf(esc_html__(
2386
-                    'WPDB Error occurred, but no error message was logged by wpdb! The wpdb method called was "%1$s" and the arguments were "%2$s"',
2387
-                    'event_espresso'
2388
-                ), $wpdb_method, var_export($arguments_to_provide, true)));
2389
-            }
2390
-        } elseif ($result === false) {
2391
-            EE_Error::add_error(
2392
-                sprintf(
2393
-                    esc_html__(
2394
-                        '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"',
2395
-                        'event_espresso'
2396
-                    ),
2397
-                    $wpdb_method,
2398
-                    var_export($arguments_to_provide, true),
2399
-                    $wpdb->last_error
2400
-                ),
2401
-                __FILE__,
2402
-                __FUNCTION__,
2403
-                __LINE__
2404
-            );
2405
-        }
2406
-        return $result;
2407
-    }
2408
-
2409
-
2410
-
2411
-    /**
2412
-     * Attempts to run the indicated WPDB method with the provided arguments,
2413
-     * and if there's an error tries to verify the DB is correct. Uses
2414
-     * the static property EEM_Base::$_db_verification_level to determine whether
2415
-     * we should try to fix the EE core db, the addons, or just give up
2416
-     *
2417
-     * @param string $wpdb_method
2418
-     * @param array  $arguments_to_provide
2419
-     * @return mixed
2420
-     */
2421
-    private function _process_wpdb_query($wpdb_method, $arguments_to_provide)
2422
-    {
2423
-        /** @type WPDB $wpdb */
2424
-        global $wpdb;
2425
-        $wpdb->last_error = null;
2426
-        $result = call_user_func_array(array($wpdb, $wpdb_method), $arguments_to_provide);
2427
-        // was there an error running the query? but we don't care on new activations
2428
-        // (we're going to setup the DB anyway on new activations)
2429
-        if (
2430
-            ($result === false || ! empty($wpdb->last_error))
2431
-            && EE_System::instance()->detect_req_type() !== EE_System::req_type_new_activation
2432
-        ) {
2433
-            switch (EEM_Base::$_db_verification_level) {
2434
-                case EEM_Base::db_verified_none:
2435
-                    // let's double-check core's DB
2436
-                    $error_message = $this->_verify_core_db($wpdb_method, $arguments_to_provide);
2437
-                    break;
2438
-                case EEM_Base::db_verified_core:
2439
-                    // STILL NO LOVE?? verify all the addons too. Maybe they need to be fixed
2440
-                    $error_message = $this->_verify_addons_db($wpdb_method, $arguments_to_provide);
2441
-                    break;
2442
-                case EEM_Base::db_verified_addons:
2443
-                    // ummmm... you in trouble
2444
-                    return $result;
2445
-                    break;
2446
-            }
2447
-            if (! empty($error_message)) {
2448
-                EE_Log::instance()->log(__FILE__, __FUNCTION__, $error_message, 'error');
2449
-                trigger_error($error_message);
2450
-            }
2451
-            return $this->_process_wpdb_query($wpdb_method, $arguments_to_provide);
2452
-        }
2453
-        return $result;
2454
-    }
2455
-
2456
-
2457
-
2458
-    /**
2459
-     * Verifies the EE core database is up-to-date and records that we've done it on
2460
-     * EEM_Base::$_db_verification_level
2461
-     *
2462
-     * @param string $wpdb_method
2463
-     * @param array  $arguments_to_provide
2464
-     * @return string
2465
-     */
2466
-    private function _verify_core_db($wpdb_method, $arguments_to_provide)
2467
-    {
2468
-        /** @type WPDB $wpdb */
2469
-        global $wpdb;
2470
-        // ok remember that we've already attempted fixing the core db, in case the problem persists
2471
-        EEM_Base::$_db_verification_level = EEM_Base::db_verified_core;
2472
-        $error_message = sprintf(
2473
-            esc_html__(
2474
-                'WPDB Error "%1$s" while running wpdb method "%2$s" with arguments %3$s. Automatically attempting to fix EE Core DB',
2475
-                'event_espresso'
2476
-            ),
2477
-            $wpdb->last_error,
2478
-            $wpdb_method,
2479
-            wp_json_encode($arguments_to_provide)
2480
-        );
2481
-        EE_System::instance()->initialize_db_if_no_migrations_required(false, true);
2482
-        return $error_message;
2483
-    }
2484
-
2485
-
2486
-
2487
-    /**
2488
-     * Verifies the EE addons' database is up-to-date and records that we've done it on
2489
-     * EEM_Base::$_db_verification_level
2490
-     *
2491
-     * @param $wpdb_method
2492
-     * @param $arguments_to_provide
2493
-     * @return string
2494
-     */
2495
-    private function _verify_addons_db($wpdb_method, $arguments_to_provide)
2496
-    {
2497
-        /** @type WPDB $wpdb */
2498
-        global $wpdb;
2499
-        // ok remember that we've already attempted fixing the addons dbs, in case the problem persists
2500
-        EEM_Base::$_db_verification_level = EEM_Base::db_verified_addons;
2501
-        $error_message = sprintf(
2502
-            esc_html__(
2503
-                'WPDB AGAIN: Error "%1$s" while running the same method and arguments as before. Automatically attempting to fix EE Addons DB',
2504
-                'event_espresso'
2505
-            ),
2506
-            $wpdb->last_error,
2507
-            $wpdb_method,
2508
-            wp_json_encode($arguments_to_provide)
2509
-        );
2510
-        EE_System::instance()->initialize_addons();
2511
-        return $error_message;
2512
-    }
2513
-
2514
-
2515
-
2516
-    /**
2517
-     * In order to avoid repeating this code for the get_all, sum, and count functions, put the code parts
2518
-     * that are identical in here. Returns a string of SQL of everything in a SELECT query except the beginning
2519
-     * SELECT clause, eg " FROM wp_posts AS Event INNER JOIN ... WHERE ... ORDER BY ... LIMIT ... GROUP BY ... HAVING
2520
-     * ..."
2521
-     *
2522
-     * @param EE_Model_Query_Info_Carrier $model_query_info
2523
-     * @return string
2524
-     */
2525
-    private function _construct_2nd_half_of_select_query(EE_Model_Query_Info_Carrier $model_query_info)
2526
-    {
2527
-        return " FROM " . $model_query_info->get_full_join_sql() .
2528
-               $model_query_info->get_where_sql() .
2529
-               $model_query_info->get_group_by_sql() .
2530
-               $model_query_info->get_having_sql() .
2531
-               $model_query_info->get_order_by_sql() .
2532
-               $model_query_info->get_limit_sql();
2533
-    }
2534
-
2535
-
2536
-
2537
-    /**
2538
-     * Set to easily debug the next X queries ran from this model.
2539
-     *
2540
-     * @param int $count
2541
-     */
2542
-    public function show_next_x_db_queries($count = 1)
2543
-    {
2544
-        $this->_show_next_x_db_queries = $count;
2545
-    }
2546
-
2547
-
2548
-
2549
-    /**
2550
-     * @param $sql_query
2551
-     */
2552
-    public function show_db_query_if_previously_requested($sql_query)
2553
-    {
2554
-        if ($this->_show_next_x_db_queries > 0) {
2555
-            echo esc_html($sql_query);
2556
-            $this->_show_next_x_db_queries--;
2557
-        }
2558
-    }
2559
-
2560
-
2561
-
2562
-    /**
2563
-     * Adds a relationship of the correct type between $modelObject and $otherModelObject.
2564
-     * There are the 3 cases:
2565
-     * 'belongsTo' relationship: sets $id_or_obj's foreign_key to be $other_model_id_or_obj's primary_key. If
2566
-     * $otherModelObject has no ID, it is first saved.
2567
-     * 'hasMany' relationship: sets $other_model_id_or_obj's foreign_key to be $id_or_obj's primary_key. If $id_or_obj
2568
-     * has no ID, it is first saved.
2569
-     * 'hasAndBelongsToMany' relationships: checks that there isn't already an entry in the join table, and adds one.
2570
-     * If one of the model Objects has not yet been saved to the database, it is saved before adding the entry in the
2571
-     * join table
2572
-     *
2573
-     * @param        EE_Base_Class                     /int $thisModelObject
2574
-     * @param        EE_Base_Class                     /int $id_or_obj EE_base_Class or ID of other Model Object
2575
-     * @param string $relationName                     , key in EEM_Base::_relations
2576
-     *                                                 an attendee to a group, you also want to specify which role they
2577
-     *                                                 will have in that group. So you would use this parameter to
2578
-     *                                                 specify array('role-column-name'=>'role-id')
2579
-     * @param array  $extra_join_model_fields_n_values This allows you to enter further query params for the relation
2580
-     *                                                 to for relation to methods that allow you to further specify
2581
-     *                                                 extra columns to join by (such as HABTM).  Keep in mind that the
2582
-     *                                                 only acceptable query_params is strict "col" => "value" pairs
2583
-     *                                                 because these will be inserted in any new rows created as well.
2584
-     * @return EE_Base_Class which was added as a relation. Object referred to by $other_model_id_or_obj
2585
-     * @throws EE_Error
2586
-     */
2587
-    public function add_relationship_to(
2588
-        $id_or_obj,
2589
-        $other_model_id_or_obj,
2590
-        $relationName,
2591
-        $extra_join_model_fields_n_values = array()
2592
-    ) {
2593
-        $relation_obj = $this->related_settings_for($relationName);
2594
-        return $relation_obj->add_relation_to($id_or_obj, $other_model_id_or_obj, $extra_join_model_fields_n_values);
2595
-    }
2596
-
2597
-
2598
-
2599
-    /**
2600
-     * Removes a relationship of the correct type between $modelObject and $otherModelObject.
2601
-     * There are the 3 cases:
2602
-     * 'belongsTo' relationship: sets $modelObject's foreign_key to null, if that field is nullable.Otherwise throws an
2603
-     * error
2604
-     * 'hasMany' relationship: sets $otherModelObject's foreign_key to null,if that field is nullable.Otherwise throws
2605
-     * an error
2606
-     * 'hasAndBelongsToMany' relationships:removes any existing entry in the join table between the two models.
2607
-     *
2608
-     * @param        EE_Base_Class /int $id_or_obj
2609
-     * @param        EE_Base_Class /int $other_model_id_or_obj EE_Base_Class or ID of other Model Object
2610
-     * @param string $relationName key in EEM_Base::_relations
2611
-     * @return boolean of success
2612
-     * @throws EE_Error
2613
-     * @param array  $where_query  This allows you to enter further query params for the relation to for relation to
2614
-     *                             methods that allow you to further specify extra columns to join by (such as HABTM).
2615
-     *                             Keep in mind that the only acceptable query_params is strict "col" => "value" pairs
2616
-     *                             because these will be inserted in any new rows created as well.
2617
-     */
2618
-    public function remove_relationship_to($id_or_obj, $other_model_id_or_obj, $relationName, $where_query = array())
2619
-    {
2620
-        $relation_obj = $this->related_settings_for($relationName);
2621
-        return $relation_obj->remove_relation_to($id_or_obj, $other_model_id_or_obj, $where_query);
2622
-    }
2623
-
2624
-
2625
-
2626
-    /**
2627
-     * @param mixed           $id_or_obj
2628
-     * @param string          $relationName
2629
-     * @param array           $where_query_params
2630
-     * @param EE_Base_Class[] objects to which relations were removed
2631
-     * @return \EE_Base_Class[]
2632
-     * @throws EE_Error
2633
-     */
2634
-    public function remove_relations($id_or_obj, $relationName, $where_query_params = array())
2635
-    {
2636
-        $relation_obj = $this->related_settings_for($relationName);
2637
-        return $relation_obj->remove_relations($id_or_obj, $where_query_params);
2638
-    }
2639
-
2640
-
2641
-
2642
-    /**
2643
-     * Gets all the related items of the specified $model_name, using $query_params.
2644
-     * Note: by default, we remove the "default query params"
2645
-     * because we want to get even deleted items etc.
2646
-     *
2647
-     * @param mixed  $id_or_obj    EE_Base_Class child or its ID
2648
-     * @param string $model_name   like 'Event', 'Registration', etc. always singular
2649
-     * @param array  $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2650
-     * @return EE_Base_Class[]
2651
-     * @throws EE_Error
2652
-     */
2653
-    public function get_all_related($id_or_obj, $model_name, $query_params = null)
2654
-    {
2655
-        $model_obj = $this->ensure_is_obj($id_or_obj);
2656
-        $relation_settings = $this->related_settings_for($model_name);
2657
-        return $relation_settings->get_all_related($model_obj, $query_params);
2658
-    }
2659
-
2660
-
2661
-
2662
-    /**
2663
-     * Deletes all the model objects across the relation indicated by $model_name
2664
-     * which are related to $id_or_obj which meet the criteria set in $query_params.
2665
-     * However, if the model objects can't be deleted because of blocking related model objects, then
2666
-     * they aren't deleted. (Unless the thing that would have been deleted can be soft-deleted, that still happens).
2667
-     *
2668
-     * @param EE_Base_Class|int|string $id_or_obj
2669
-     * @param string                   $model_name
2670
-     * @param array                    $query_params
2671
-     * @return int how many deleted
2672
-     * @throws EE_Error
2673
-     */
2674
-    public function delete_related($id_or_obj, $model_name, $query_params = array())
2675
-    {
2676
-        $model_obj = $this->ensure_is_obj($id_or_obj);
2677
-        $relation_settings = $this->related_settings_for($model_name);
2678
-        return $relation_settings->delete_all_related($model_obj, $query_params);
2679
-    }
2680
-
2681
-
2682
-
2683
-    /**
2684
-     * Hard deletes all the model objects across the relation indicated by $model_name
2685
-     * which are related to $id_or_obj which meet the criteria set in $query_params. If
2686
-     * the model objects can't be hard deleted because of blocking related model objects,
2687
-     * just does a soft-delete on them instead.
2688
-     *
2689
-     * @param EE_Base_Class|int|string $id_or_obj
2690
-     * @param string                   $model_name
2691
-     * @param array                    $query_params
2692
-     * @return int how many deleted
2693
-     * @throws EE_Error
2694
-     */
2695
-    public function delete_related_permanently($id_or_obj, $model_name, $query_params = array())
2696
-    {
2697
-        $model_obj = $this->ensure_is_obj($id_or_obj);
2698
-        $relation_settings = $this->related_settings_for($model_name);
2699
-        return $relation_settings->delete_related_permanently($model_obj, $query_params);
2700
-    }
2701
-
2702
-
2703
-
2704
-    /**
2705
-     * Instead of getting the related model objects, simply counts them. Ignores default_where_conditions by default,
2706
-     * unless otherwise specified in the $query_params
2707
-     *
2708
-     * @param        int             /EE_Base_Class $id_or_obj
2709
-     * @param string $model_name     like 'Event', or 'Registration'
2710
-     * @param array  $query_params   @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2711
-     * @param string $field_to_count name of field to count by. By default, uses primary key
2712
-     * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2713
-     *                               that by the setting $distinct to TRUE;
2714
-     * @return int
2715
-     * @throws EE_Error
2716
-     */
2717
-    public function count_related(
2718
-        $id_or_obj,
2719
-        $model_name,
2720
-        $query_params = array(),
2721
-        $field_to_count = null,
2722
-        $distinct = false
2723
-    ) {
2724
-        $related_model = $this->get_related_model_obj($model_name);
2725
-        // we're just going to use the query params on the related model's normal get_all query,
2726
-        // except add a condition to say to match the current mod
2727
-        if (! isset($query_params['default_where_conditions'])) {
2728
-            $query_params['default_where_conditions'] = EEM_Base::default_where_conditions_none;
2729
-        }
2730
-        $this_model_name = $this->get_this_model_name();
2731
-        $this_pk_field_name = $this->get_primary_key_field()->get_name();
2732
-        $query_params[0][ $this_model_name . "." . $this_pk_field_name ] = $id_or_obj;
2733
-        return $related_model->count($query_params, $field_to_count, $distinct);
2734
-    }
2735
-
2736
-
2737
-
2738
-    /**
2739
-     * Instead of getting the related model objects, simply sums up the values of the specified field.
2740
-     * Note: ignores default_where_conditions by default, unless otherwise specified in the $query_params
2741
-     *
2742
-     * @param        int           /EE_Base_Class $id_or_obj
2743
-     * @param string $model_name   like 'Event', or 'Registration'
2744
-     * @param array  $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2745
-     * @param string $field_to_sum name of field to count by. By default, uses primary key
2746
-     * @return float
2747
-     * @throws EE_Error
2748
-     */
2749
-    public function sum_related($id_or_obj, $model_name, $query_params, $field_to_sum = null)
2750
-    {
2751
-        $related_model = $this->get_related_model_obj($model_name);
2752
-        if (! is_array($query_params)) {
2753
-            EE_Error::doing_it_wrong(
2754
-                'EEM_Base::sum_related',
2755
-                sprintf(
2756
-                    esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
2757
-                    gettype($query_params)
2758
-                ),
2759
-                '4.6.0'
2760
-            );
2761
-            $query_params = array();
2762
-        }
2763
-        // we're just going to use the query params on the related model's normal get_all query,
2764
-        // except add a condition to say to match the current mod
2765
-        if (! isset($query_params['default_where_conditions'])) {
2766
-            $query_params['default_where_conditions'] = EEM_Base::default_where_conditions_none;
2767
-        }
2768
-        $this_model_name = $this->get_this_model_name();
2769
-        $this_pk_field_name = $this->get_primary_key_field()->get_name();
2770
-        $query_params[0][ $this_model_name . "." . $this_pk_field_name ] = $id_or_obj;
2771
-        return $related_model->sum($query_params, $field_to_sum);
2772
-    }
2773
-
2774
-
2775
-
2776
-    /**
2777
-     * Uses $this->_relatedModels info to find the first related model object of relation $relationName to the given
2778
-     * $modelObject
2779
-     *
2780
-     * @param int | EE_Base_Class $id_or_obj        EE_Base_Class child or its ID
2781
-     * @param string              $other_model_name , key in $this->_relatedModels, eg 'Registration', or 'Events'
2782
-     * @param array               $query_params     @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2783
-     * @return EE_Base_Class
2784
-     * @throws EE_Error
2785
-     */
2786
-    public function get_first_related(EE_Base_Class $id_or_obj, $other_model_name, $query_params)
2787
-    {
2788
-        $query_params['limit'] = 1;
2789
-        $results = $this->get_all_related($id_or_obj, $other_model_name, $query_params);
2790
-        if ($results) {
2791
-            return array_shift($results);
2792
-        }
2793
-        return null;
2794
-    }
2795
-
2796
-
2797
-
2798
-    /**
2799
-     * Gets the model's name as it's expected in queries. For example, if this is EEM_Event model, that would be Event
2800
-     *
2801
-     * @return string
2802
-     */
2803
-    public function get_this_model_name()
2804
-    {
2805
-        return str_replace("EEM_", "", get_class($this));
2806
-    }
2807
-
2808
-
2809
-
2810
-    /**
2811
-     * Gets the model field on this model which is of type EE_Any_Foreign_Model_Name_Field
2812
-     *
2813
-     * @return EE_Any_Foreign_Model_Name_Field
2814
-     * @throws EE_Error
2815
-     */
2816
-    public function get_field_containing_related_model_name()
2817
-    {
2818
-        foreach ($this->field_settings(true) as $field) {
2819
-            if ($field instanceof EE_Any_Foreign_Model_Name_Field) {
2820
-                $field_with_model_name = $field;
2821
-            }
2822
-        }
2823
-        if (! isset($field_with_model_name) || ! $field_with_model_name) {
2824
-            throw new EE_Error(sprintf(
2825
-                esc_html__("There is no EE_Any_Foreign_Model_Name field on model %s", "event_espresso"),
2826
-                $this->get_this_model_name()
2827
-            ));
2828
-        }
2829
-        return $field_with_model_name;
2830
-    }
2831
-
2832
-
2833
-
2834
-    /**
2835
-     * Inserts a new entry into the database, for each table.
2836
-     * Note: does not add the item to the entity map because that is done by EE_Base_Class::save() right after this.
2837
-     * If client code uses EEM_Base::insert() directly, then although the item isn't in the entity map,
2838
-     * we also know there is no model object with the newly inserted item's ID at the moment (because
2839
-     * if there were, then they would already be in the DB and this would fail); and in the future if someone
2840
-     * creates a model object with this ID (or grabs it from the DB) then it will be added to the
2841
-     * entity map at that time anyways. SO, no need for EEM_Base::insert ot add to the entity map
2842
-     *
2843
-     * @param array $field_n_values keys are field names, values are their values (in the client code's domain if
2844
-     *                              $values_already_prepared_by_model_object is false, in the model object's domain if
2845
-     *                              $values_already_prepared_by_model_object is true. See comment about this at the top
2846
-     *                              of EEM_Base)
2847
-     * @return int|string new primary key on main table that got inserted
2848
-     * @throws EE_Error
2849
-     */
2850
-    public function insert($field_n_values)
2851
-    {
2852
-        /**
2853
-         * Filters the fields and their values before inserting an item using the models
2854
-         *
2855
-         * @param array    $fields_n_values keys are the fields and values are their new values
2856
-         * @param EEM_Base $model           the model used
2857
-         */
2858
-        $field_n_values = (array) apply_filters('FHEE__EEM_Base__insert__fields_n_values', $field_n_values, $this);
2859
-        if ($this->_satisfies_unique_indexes($field_n_values)) {
2860
-            $main_table = $this->_get_main_table();
2861
-            $new_id = $this->_insert_into_specific_table($main_table, $field_n_values, false);
2862
-            if ($new_id !== false) {
2863
-                foreach ($this->_get_other_tables() as $other_table) {
2864
-                    $this->_insert_into_specific_table($other_table, $field_n_values, $new_id);
2865
-                }
2866
-            }
2867
-            /**
2868
-             * Done just after attempting to insert a new model object
2869
-             *
2870
-             * @param EEM_Base   $model           used
2871
-             * @param array      $fields_n_values fields and their values
2872
-             * @param int|string the              ID of the newly-inserted model object
2873
-             */
2874
-            do_action('AHEE__EEM_Base__insert__end', $this, $field_n_values, $new_id);
2875
-            return $new_id;
2876
-        }
2877
-        return false;
2878
-    }
2879
-
2880
-
2881
-
2882
-    /**
2883
-     * Checks that the result would satisfy the unique indexes on this model
2884
-     *
2885
-     * @param array  $field_n_values
2886
-     * @param string $action
2887
-     * @return boolean
2888
-     * @throws EE_Error
2889
-     */
2890
-    protected function _satisfies_unique_indexes($field_n_values, $action = 'insert')
2891
-    {
2892
-        foreach ($this->unique_indexes() as $index_name => $index) {
2893
-            $uniqueness_where_params = array_intersect_key($field_n_values, $index->fields());
2894
-            if ($this->exists(array($uniqueness_where_params))) {
2895
-                EE_Error::add_error(
2896
-                    sprintf(
2897
-                        esc_html__(
2898
-                            "Could not %s %s. %s uniqueness index failed. Fields %s must form a unique set, but an entry already exists with values %s.",
2899
-                            "event_espresso"
2900
-                        ),
2901
-                        $action,
2902
-                        $this->_get_class_name(),
2903
-                        $index_name,
2904
-                        implode(",", $index->field_names()),
2905
-                        http_build_query($uniqueness_where_params)
2906
-                    ),
2907
-                    __FILE__,
2908
-                    __FUNCTION__,
2909
-                    __LINE__
2910
-                );
2911
-                return false;
2912
-            }
2913
-        }
2914
-        return true;
2915
-    }
2916
-
2917
-
2918
-
2919
-    /**
2920
-     * Checks the database for an item that conflicts (ie, if this item were
2921
-     * saved to the DB would break some uniqueness requirement, like a primary key
2922
-     * or an index primary key set) with the item specified. $id_obj_or_fields_array
2923
-     * can be either an EE_Base_Class or an array of fields n values
2924
-     *
2925
-     * @param EE_Base_Class|array $obj_or_fields_array
2926
-     * @param boolean             $include_primary_key whether to use the model object's primary key
2927
-     *                                                 when looking for conflicts
2928
-     *                                                 (ie, if false, we ignore the model object's primary key
2929
-     *                                                 when finding "conflicts". If true, it's also considered).
2930
-     *                                                 Only works for INT primary key,
2931
-     *                                                 STRING primary keys cannot be ignored
2932
-     * @throws EE_Error
2933
-     * @return EE_Base_Class|array
2934
-     */
2935
-    public function get_one_conflicting($obj_or_fields_array, $include_primary_key = true)
2936
-    {
2937
-        if ($obj_or_fields_array instanceof EE_Base_Class) {
2938
-            $fields_n_values = $obj_or_fields_array->model_field_array();
2939
-        } elseif (is_array($obj_or_fields_array)) {
2940
-            $fields_n_values = $obj_or_fields_array;
2941
-        } else {
2942
-            throw new EE_Error(
2943
-                sprintf(
2944
-                    esc_html__(
2945
-                        "%s get_all_conflicting should be called with a model object or an array of field names and values, you provided %d",
2946
-                        "event_espresso"
2947
-                    ),
2948
-                    get_class($this),
2949
-                    $obj_or_fields_array
2950
-                )
2951
-            );
2952
-        }
2953
-        $query_params = array();
2954
-        if (
2955
-            $this->has_primary_key_field()
2956
-            && ($include_primary_key
2957
-                || $this->get_primary_key_field()
2958
-                   instanceof
2959
-                   EE_Primary_Key_String_Field)
2960
-            && isset($fields_n_values[ $this->primary_key_name() ])
2961
-        ) {
2962
-            $query_params[0]['OR'][ $this->primary_key_name() ] = $fields_n_values[ $this->primary_key_name() ];
2963
-        }
2964
-        foreach ($this->unique_indexes() as $unique_index_name => $unique_index) {
2965
-            $uniqueness_where_params = array_intersect_key($fields_n_values, $unique_index->fields());
2966
-            $query_params[0]['OR'][ 'AND*' . $unique_index_name ] = $uniqueness_where_params;
2967
-        }
2968
-        // if there is nothing to base this search on, then we shouldn't find anything
2969
-        if (empty($query_params)) {
2970
-            return array();
2971
-        }
2972
-        return $this->get_one($query_params);
2973
-    }
2974
-
2975
-
2976
-
2977
-    /**
2978
-     * Like count, but is optimized and returns a boolean instead of an int
2979
-     *
2980
-     * @param array $query_params
2981
-     * @return boolean
2982
-     * @throws EE_Error
2983
-     */
2984
-    public function exists($query_params)
2985
-    {
2986
-        $query_params['limit'] = 1;
2987
-        return $this->count($query_params) > 0;
2988
-    }
2989
-
2990
-
2991
-
2992
-    /**
2993
-     * Wrapper for exists, except ignores default query parameters so we're only considering ID
2994
-     *
2995
-     * @param int|string $id
2996
-     * @return boolean
2997
-     * @throws EE_Error
2998
-     */
2999
-    public function exists_by_ID($id)
3000
-    {
3001
-        return $this->exists(
3002
-            array(
3003
-                'default_where_conditions' => EEM_Base::default_where_conditions_none,
3004
-                array(
3005
-                    $this->primary_key_name() => $id,
3006
-                ),
3007
-            )
3008
-        );
3009
-    }
3010
-
3011
-
3012
-
3013
-    /**
3014
-     * Inserts a new row in $table, using the $cols_n_values which apply to that table.
3015
-     * If a $new_id is supplied and if $table is an EE_Other_Table, we assume
3016
-     * we need to add a foreign key column to point to $new_id (which should be the primary key's value
3017
-     * on the main table)
3018
-     * This is protected rather than private because private is not accessible to any child methods and there MAY be
3019
-     * cases where we want to call it directly rather than via insert().
3020
-     *
3021
-     * @access   protected
3022
-     * @param EE_Table_Base $table
3023
-     * @param array         $fields_n_values each key should be in field's keys, and value should be an int, string or
3024
-     *                                       float
3025
-     * @param int           $new_id          for now we assume only int keys
3026
-     * @throws EE_Error
3027
-     * @global WPDB         $wpdb            only used to get the $wpdb->insert_id after performing an insert
3028
-     * @return int ID of new row inserted, or FALSE on failure
3029
-     */
3030
-    protected function _insert_into_specific_table(EE_Table_Base $table, $fields_n_values, $new_id = 0)
3031
-    {
3032
-        global $wpdb;
3033
-        $insertion_col_n_values = array();
3034
-        $format_for_insertion = array();
3035
-        $fields_on_table = $this->_get_fields_for_table($table->get_table_alias());
3036
-        foreach ($fields_on_table as $field_name => $field_obj) {
3037
-            // check if its an auto-incrementing column, in which case we should just leave it to do its autoincrement thing
3038
-            if ($field_obj->is_auto_increment()) {
3039
-                continue;
3040
-            }
3041
-            $prepared_value = $this->_prepare_value_or_use_default($field_obj, $fields_n_values);
3042
-            // if the value we want to assign it to is NULL, just don't mention it for the insertion
3043
-            if ($prepared_value !== null) {
3044
-                $insertion_col_n_values[ $field_obj->get_table_column() ] = $prepared_value;
3045
-                $format_for_insertion[] = $field_obj->get_wpdb_data_type();
3046
-            }
3047
-        }
3048
-        if ($table instanceof EE_Secondary_Table && $new_id) {
3049
-            // its not the main table, so we should have already saved the main table's PK which we just inserted
3050
-            // so add the fk to the main table as a column
3051
-            $insertion_col_n_values[ $table->get_fk_on_table() ] = $new_id;
3052
-            $format_for_insertion[] = '%d';// yes right now we're only allowing these foreign keys to be INTs
3053
-        }
3054
-        // insert the new entry
3055
-        $result = $this->_do_wpdb_query(
3056
-            'insert',
3057
-            array($table->get_table_name(), $insertion_col_n_values, $format_for_insertion)
3058
-        );
3059
-        if ($result === false) {
3060
-            return false;
3061
-        }
3062
-        // ok, now what do we return for the ID of the newly-inserted thing?
3063
-        if ($this->has_primary_key_field()) {
3064
-            if ($this->get_primary_key_field()->is_auto_increment()) {
3065
-                return $wpdb->insert_id;
3066
-            }
3067
-            // it's not an auto-increment primary key, so
3068
-            // it must have been supplied
3069
-            return $fields_n_values[ $this->get_primary_key_field()->get_name() ];
3070
-        }
3071
-        // we can't return a  primary key because there is none. instead return
3072
-        // a unique string indicating this model
3073
-        return $this->get_index_primary_key_string($fields_n_values);
3074
-    }
3075
-
3076
-
3077
-
3078
-    /**
3079
-     * Prepare the $field_obj 's value in $fields_n_values for use in the database.
3080
-     * If the field doesn't allow NULL, try to use its default. (If it doesn't allow NULL,
3081
-     * and there is no default, we pass it along. WPDB will take care of it)
3082
-     *
3083
-     * @param EE_Model_Field_Base $field_obj
3084
-     * @param array               $fields_n_values
3085
-     * @return mixed string|int|float depending on what the table column will be expecting
3086
-     * @throws EE_Error
3087
-     */
3088
-    protected function _prepare_value_or_use_default($field_obj, $fields_n_values)
3089
-    {
3090
-        // if this field doesn't allow nullable, don't allow it
3091
-        if (
3092
-            ! $field_obj->is_nullable()
3093
-            && (
3094
-                ! isset($fields_n_values[ $field_obj->get_name() ])
3095
-                || $fields_n_values[ $field_obj->get_name() ] === null
3096
-            )
3097
-        ) {
3098
-            $fields_n_values[ $field_obj->get_name() ] = $field_obj->get_default_value();
3099
-        }
3100
-        $unprepared_value = isset($fields_n_values[ $field_obj->get_name() ])
3101
-            ? $fields_n_values[ $field_obj->get_name() ]
3102
-            : null;
3103
-        return $this->_prepare_value_for_use_in_db($unprepared_value, $field_obj);
3104
-    }
3105
-
3106
-
3107
-
3108
-    /**
3109
-     * Consolidates code for preparing  a value supplied to the model for use int eh db. Calls the field's
3110
-     * prepare_for_use_in_db method on the value, and depending on $value_already_prepare_by_model_obj, may also call
3111
-     * the field's prepare_for_set() method.
3112
-     *
3113
-     * @param mixed               $value value in the client code domain if $value_already_prepared_by_model_object is
3114
-     *                                   false, otherwise a value in the model object's domain (see lengthy comment at
3115
-     *                                   top of file)
3116
-     * @param EE_Model_Field_Base $field field which will be doing the preparing of the value. If null, we assume
3117
-     *                                   $value is a custom selection
3118
-     * @return mixed a value ready for use in the database for insertions, updating, or in a where clause
3119
-     */
3120
-    private function _prepare_value_for_use_in_db($value, $field)
3121
-    {
3122
-        if ($field && $field instanceof EE_Model_Field_Base) {
3123
-            // phpcs:disable PSR2.ControlStructures.SwitchDeclaration.TerminatingComment
3124
-            switch ($this->_values_already_prepared_by_model_object) {
3125
-                /** @noinspection PhpMissingBreakStatementInspection */
3126
-                case self::not_prepared_by_model_object:
3127
-                    $value = $field->prepare_for_set($value);
3128
-                // purposefully left out "return"
3129
-                // no break
3130
-                case self::prepared_by_model_object:
3131
-                    /** @noinspection SuspiciousAssignmentsInspection */
3132
-                    $value = $field->prepare_for_use_in_db($value);
3133
-                    // no break
3134
-                case self::prepared_for_use_in_db:
3135
-                    // leave the value alone
3136
-            }
3137
-            return $value;
3138
-            // phpcs:enable
3139
-        }
3140
-        return $value;
3141
-    }
3142
-
3143
-
3144
-
3145
-    /**
3146
-     * Returns the main table on this model
3147
-     *
3148
-     * @return EE_Primary_Table
3149
-     * @throws EE_Error
3150
-     */
3151
-    protected function _get_main_table()
3152
-    {
3153
-        foreach ($this->_tables as $table) {
3154
-            if ($table instanceof EE_Primary_Table) {
3155
-                return $table;
3156
-            }
3157
-        }
3158
-        throw new EE_Error(sprintf(esc_html__(
3159
-            'There are no main tables on %s. They should be added to _tables array in the constructor',
3160
-            'event_espresso'
3161
-        ), get_class($this)));
3162
-    }
3163
-
3164
-
3165
-
3166
-    /**
3167
-     * table
3168
-     * returns EE_Primary_Table table name
3169
-     *
3170
-     * @return string
3171
-     * @throws EE_Error
3172
-     */
3173
-    public function table()
3174
-    {
3175
-        return $this->_get_main_table()->get_table_name();
3176
-    }
3177
-
3178
-
3179
-
3180
-    /**
3181
-     * table
3182
-     * returns first EE_Secondary_Table table name
3183
-     *
3184
-     * @return string
3185
-     */
3186
-    public function second_table()
3187
-    {
3188
-        // grab second table from tables array
3189
-        $second_table = end($this->_tables);
3190
-        return $second_table instanceof EE_Secondary_Table ? $second_table->get_table_name() : null;
3191
-    }
3192
-
3193
-
3194
-
3195
-    /**
3196
-     * get_table_obj_by_alias
3197
-     * returns table name given it's alias
3198
-     *
3199
-     * @param string $table_alias
3200
-     * @return EE_Primary_Table | EE_Secondary_Table
3201
-     */
3202
-    public function get_table_obj_by_alias($table_alias = '')
3203
-    {
3204
-        return isset($this->_tables[ $table_alias ]) ? $this->_tables[ $table_alias ] : null;
3205
-    }
3206
-
3207
-
3208
-
3209
-    /**
3210
-     * Gets all the tables of type EE_Other_Table from EEM_CPT_Basel_Model::_tables
3211
-     *
3212
-     * @return EE_Secondary_Table[]
3213
-     */
3214
-    protected function _get_other_tables()
3215
-    {
3216
-        $other_tables = array();
3217
-        foreach ($this->_tables as $table_alias => $table) {
3218
-            if ($table instanceof EE_Secondary_Table) {
3219
-                $other_tables[ $table_alias ] = $table;
3220
-            }
3221
-        }
3222
-        return $other_tables;
3223
-    }
3224
-
3225
-
3226
-
3227
-    /**
3228
-     * Finds all the fields that correspond to the given table
3229
-     *
3230
-     * @param string $table_alias , array key in EEM_Base::_tables
3231
-     * @return EE_Model_Field_Base[]
3232
-     */
3233
-    public function _get_fields_for_table($table_alias)
3234
-    {
3235
-        return $this->_fields[ $table_alias ];
3236
-    }
3237
-
3238
-
3239
-
3240
-    /**
3241
-     * Recurses through all the where parameters, and finds all the related models we'll need
3242
-     * to complete this query. Eg, given where parameters like array('EVT_ID'=>3) from within Event model, we won't
3243
-     * need any related models. But if the array were array('Registrations.REG_ID'=>3), we'd need the related
3244
-     * Registration model. If it were array('Registrations.Transactions.Payments.PAY_ID'=>3), then we'd need the
3245
-     * related Registration, Transaction, and Payment models.
3246
-     *
3247
-     * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
3248
-     * @return EE_Model_Query_Info_Carrier
3249
-     * @throws EE_Error
3250
-     */
3251
-    public function _extract_related_models_from_query($query_params)
3252
-    {
3253
-        $query_info_carrier = new EE_Model_Query_Info_Carrier();
3254
-        if (array_key_exists(0, $query_params)) {
3255
-            $this->_extract_related_models_from_sub_params_array_keys($query_params[0], $query_info_carrier, 0);
3256
-        }
3257
-        if (array_key_exists('group_by', $query_params)) {
3258
-            if (is_array($query_params['group_by'])) {
3259
-                $this->_extract_related_models_from_sub_params_array_values(
3260
-                    $query_params['group_by'],
3261
-                    $query_info_carrier,
3262
-                    'group_by'
3263
-                );
3264
-            } elseif (! empty($query_params['group_by'])) {
3265
-                $this->_extract_related_model_info_from_query_param(
3266
-                    $query_params['group_by'],
3267
-                    $query_info_carrier,
3268
-                    'group_by'
3269
-                );
3270
-            }
3271
-        }
3272
-        if (array_key_exists('having', $query_params)) {
3273
-            $this->_extract_related_models_from_sub_params_array_keys(
3274
-                $query_params[0],
3275
-                $query_info_carrier,
3276
-                'having'
3277
-            );
3278
-        }
3279
-        if (array_key_exists('order_by', $query_params)) {
3280
-            if (is_array($query_params['order_by'])) {
3281
-                $this->_extract_related_models_from_sub_params_array_keys(
3282
-                    $query_params['order_by'],
3283
-                    $query_info_carrier,
3284
-                    'order_by'
3285
-                );
3286
-            } elseif (! empty($query_params['order_by'])) {
3287
-                $this->_extract_related_model_info_from_query_param(
3288
-                    $query_params['order_by'],
3289
-                    $query_info_carrier,
3290
-                    'order_by'
3291
-                );
3292
-            }
3293
-        }
3294
-        if (array_key_exists('force_join', $query_params)) {
3295
-            $this->_extract_related_models_from_sub_params_array_values(
3296
-                $query_params['force_join'],
3297
-                $query_info_carrier,
3298
-                'force_join'
3299
-            );
3300
-        }
3301
-        $this->extractRelatedModelsFromCustomSelects($query_info_carrier);
3302
-        return $query_info_carrier;
3303
-    }
3304
-
3305
-
3306
-
3307
-    /**
3308
-     * For extracting related models from WHERE (0), HAVING (having), ORDER BY (order_by) or forced joins (force_join)
3309
-     *
3310
-     * @param array                       $sub_query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#-0-where-conditions
3311
-     * @param EE_Model_Query_Info_Carrier $model_query_info_carrier
3312
-     * @param string                      $query_param_type one of $this->_allowed_query_params
3313
-     * @throws EE_Error
3314
-     * @return \EE_Model_Query_Info_Carrier
3315
-     */
3316
-    private function _extract_related_models_from_sub_params_array_keys(
3317
-        $sub_query_params,
3318
-        EE_Model_Query_Info_Carrier $model_query_info_carrier,
3319
-        $query_param_type
3320
-    ) {
3321
-        if (! empty($sub_query_params)) {
3322
-            $sub_query_params = (array) $sub_query_params;
3323
-            foreach ($sub_query_params as $param => $possibly_array_of_params) {
3324
-                // $param could be simply 'EVT_ID', or it could be 'Registrations.REG_ID', or even 'Registrations.Transactions.Payments.PAY_amount'
3325
-                $this->_extract_related_model_info_from_query_param(
3326
-                    $param,
3327
-                    $model_query_info_carrier,
3328
-                    $query_param_type
3329
-                );
3330
-                // if $possibly_array_of_params is an array, try recursing into it, searching for keys which
3331
-                // indicate needed joins. Eg, array('NOT'=>array('Registration.TXN_ID'=>23)). In this case, we tried
3332
-                // extracting models out of the 'NOT', which obviously wasn't successful, and then we recurse into the value
3333
-                // of array('Registration.TXN_ID'=>23)
3334
-                $query_param_sans_stars = $this->_remove_stars_and_anything_after_from_condition_query_param_key($param);
3335
-                if (in_array($query_param_sans_stars, $this->_logic_query_param_keys, true)) {
3336
-                    if (! is_array($possibly_array_of_params)) {
3337
-                        throw new EE_Error(sprintf(
3338
-                            esc_html__(
3339
-                                "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'))",
3340
-                                "event_espresso"
3341
-                            ),
3342
-                            $param,
3343
-                            $possibly_array_of_params
3344
-                        ));
3345
-                    }
3346
-                    $this->_extract_related_models_from_sub_params_array_keys(
3347
-                        $possibly_array_of_params,
3348
-                        $model_query_info_carrier,
3349
-                        $query_param_type
3350
-                    );
3351
-                } elseif (
3352
-                    $query_param_type === 0 // ie WHERE
3353
-                          && is_array($possibly_array_of_params)
3354
-                          && isset($possibly_array_of_params[2])
3355
-                          && $possibly_array_of_params[2] == true
3356
-                ) {
3357
-                    // then $possible_array_of_params looks something like array('<','DTT_sold',true)
3358
-                    // indicating that $possible_array_of_params[1] is actually a field name,
3359
-                    // from which we should extract query parameters!
3360
-                    if (! isset($possibly_array_of_params[0], $possibly_array_of_params[1])) {
3361
-                        throw new EE_Error(sprintf(esc_html__(
3362
-                            "Improperly formed query parameter %s. It should be numerically indexed like array('<','DTT_sold',true); but you provided %s",
3363
-                            "event_espresso"
3364
-                        ), $query_param_type, implode(",", $possibly_array_of_params)));
3365
-                    }
3366
-                    $this->_extract_related_model_info_from_query_param(
3367
-                        $possibly_array_of_params[1],
3368
-                        $model_query_info_carrier,
3369
-                        $query_param_type
3370
-                    );
3371
-                }
3372
-            }
3373
-        }
3374
-        return $model_query_info_carrier;
3375
-    }
3376
-
3377
-
3378
-
3379
-    /**
3380
-     * For extracting related models from forced_joins, where the array values contain the info about what
3381
-     * models to join with. Eg an array like array('Attendee','Price.Price_Type');
3382
-     *
3383
-     * @param array                       $sub_query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3384
-     * @param EE_Model_Query_Info_Carrier $model_query_info_carrier
3385
-     * @param string                      $query_param_type one of $this->_allowed_query_params
3386
-     * @throws EE_Error
3387
-     * @return \EE_Model_Query_Info_Carrier
3388
-     */
3389
-    private function _extract_related_models_from_sub_params_array_values(
3390
-        $sub_query_params,
3391
-        EE_Model_Query_Info_Carrier $model_query_info_carrier,
3392
-        $query_param_type
3393
-    ) {
3394
-        if (! empty($sub_query_params)) {
3395
-            if (! is_array($sub_query_params)) {
3396
-                throw new EE_Error(sprintf(
3397
-                    esc_html__("Query parameter %s should be an array, but it isn't.", "event_espresso"),
3398
-                    $sub_query_params
3399
-                ));
3400
-            }
3401
-            foreach ($sub_query_params as $param) {
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
-            }
3409
-        }
3410
-        return $model_query_info_carrier;
3411
-    }
3412
-
3413
-
3414
-    /**
3415
-     * Extract all the query parts from  model query params
3416
-     * and put into a EEM_Related_Model_Info_Carrier for easy extraction into a query. We create this object
3417
-     * instead of directly constructing the SQL because often we need to extract info from the $query_params
3418
-     * but use them in a different order. Eg, we need to know what models we are querying
3419
-     * before we know what joins to perform. However, we need to know what data types correspond to which fields on
3420
-     * other models before we can finalize the where clause SQL.
3421
-     *
3422
-     * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
3423
-     * @throws EE_Error
3424
-     * @return EE_Model_Query_Info_Carrier
3425
-     * @throws ModelConfigurationException
3426
-     */
3427
-    public function _create_model_query_info_carrier($query_params)
3428
-    {
3429
-        if (! is_array($query_params)) {
3430
-            EE_Error::doing_it_wrong(
3431
-                'EEM_Base::_create_model_query_info_carrier',
3432
-                sprintf(
3433
-                    esc_html__(
3434
-                        '$query_params should be an array, you passed a variable of type %s',
3435
-                        'event_espresso'
3436
-                    ),
3437
-                    gettype($query_params)
3438
-                ),
3439
-                '4.6.0'
3440
-            );
3441
-            $query_params = array();
3442
-        }
3443
-        $query_params[0] = isset($query_params[0]) ? $query_params[0] : array();
3444
-        // first check if we should alter the query to account for caps or not
3445
-        // because the caps might require us to do extra joins
3446
-        if (isset($query_params['caps']) && $query_params['caps'] !== 'none') {
3447
-            $query_params[0] = array_replace_recursive(
3448
-                $query_params[0],
3449
-                $this->caps_where_conditions($query_params['caps'])
3450
-            );
3451
-        }
3452
-
3453
-        // check if we should alter the query to remove data related to protected
3454
-        // custom post types
3455
-        if (isset($query_params['exclude_protected']) && $query_params['exclude_protected'] === true) {
3456
-            $where_param_key_for_password = $this->modelChainAndPassword();
3457
-            // only include if related to a cpt where no password has been set
3458
-            $query_params[0]['OR*nopassword'] = array(
3459
-                $where_param_key_for_password => '',
3460
-                $where_param_key_for_password . '*' => array('IS_NULL')
3461
-            );
3462
-        }
3463
-        $query_object = $this->_extract_related_models_from_query($query_params);
3464
-        // verify where_query_params has NO numeric indexes.... that's simply not how you use it!
3465
-        foreach ($query_params[0] as $key => $value) {
3466
-            if (is_int($key)) {
3467
-                throw new EE_Error(
3468
-                    sprintf(
3469
-                        esc_html__(
3470
-                            "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.",
3471
-                            "event_espresso"
3472
-                        ),
3473
-                        $key,
3474
-                        var_export($value, true),
3475
-                        var_export($query_params, true),
3476
-                        get_class($this)
3477
-                    )
3478
-                );
3479
-            }
3480
-        }
3481
-        if (
3482
-            array_key_exists('default_where_conditions', $query_params)
3483
-            && ! empty($query_params['default_where_conditions'])
3484
-        ) {
3485
-            $use_default_where_conditions = $query_params['default_where_conditions'];
3486
-        } else {
3487
-            $use_default_where_conditions = EEM_Base::default_where_conditions_all;
3488
-        }
3489
-        $query_params[0] = array_merge(
3490
-            $this->_get_default_where_conditions_for_models_in_query(
3491
-                $query_object,
3492
-                $use_default_where_conditions,
3493
-                $query_params[0]
3494
-            ),
3495
-            $query_params[0]
3496
-        );
3497
-        $query_object->set_where_sql($this->_construct_where_clause($query_params[0]));
3498
-        // if this is a "on_join_limit" then we are limiting on on a specific table in a multi_table join.
3499
-        // So we need to setup a subquery and use that for the main join.
3500
-        // Note for now this only works on the primary table for the model.
3501
-        // So for instance, you could set the limit array like this:
3502
-        // array( 'on_join_limit' => array('Primary_Table_Alias', array(1,10) ) )
3503
-        if (array_key_exists('on_join_limit', $query_params) && ! empty($query_params['on_join_limit'])) {
3504
-            $query_object->set_main_model_join_sql(
3505
-                $this->_construct_limit_join_select(
3506
-                    $query_params['on_join_limit'][0],
3507
-                    $query_params['on_join_limit'][1]
3508
-                )
3509
-            );
3510
-        }
3511
-        // set limit
3512
-        if (array_key_exists('limit', $query_params)) {
3513
-            if (is_array($query_params['limit'])) {
3514
-                if (! isset($query_params['limit'][0], $query_params['limit'][1])) {
3515
-                    $e = sprintf(
3516
-                        esc_html__(
3517
-                            "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)",
3518
-                            "event_espresso"
3519
-                        ),
3520
-                        http_build_query($query_params['limit'])
3521
-                    );
3522
-                    throw new EE_Error($e . "|" . $e);
3523
-                }
3524
-                // they passed us an array for the limit. Assume it's like array(50,25), meaning offset by 50, and get 25
3525
-                $query_object->set_limit_sql(" LIMIT " . $query_params['limit'][0] . "," . $query_params['limit'][1]);
3526
-            } elseif (! empty($query_params['limit'])) {
3527
-                $query_object->set_limit_sql(" LIMIT " . $query_params['limit']);
3528
-            }
3529
-        }
3530
-        // set order by
3531
-        if (array_key_exists('order_by', $query_params)) {
3532
-            if (is_array($query_params['order_by'])) {
3533
-                // if they're using 'order_by' as an array, they can't use 'order' (because 'order_by' must
3534
-                // specify whether to ascend or descend on each field. Eg 'order_by'=>array('EVT_ID'=>'ASC'). So
3535
-                // including 'order' wouldn't make any sense if 'order_by' has already specified which way to order!
3536
-                if (array_key_exists('order', $query_params)) {
3537
-                    throw new EE_Error(
3538
-                        sprintf(
3539
-                            esc_html__(
3540
-                                "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 ",
3541
-                                "event_espresso"
3542
-                            ),
3543
-                            get_class($this),
3544
-                            implode(", ", array_keys($query_params['order_by'])),
3545
-                            implode(", ", $query_params['order_by']),
3546
-                            $query_params['order']
3547
-                        )
3548
-                    );
3549
-                }
3550
-                $this->_extract_related_models_from_sub_params_array_keys(
3551
-                    $query_params['order_by'],
3552
-                    $query_object,
3553
-                    'order_by'
3554
-                );
3555
-                // assume it's an array of fields to order by
3556
-                $order_array = array();
3557
-                foreach ($query_params['order_by'] as $field_name_to_order_by => $order) {
3558
-                    $order = $this->_extract_order($order);
3559
-                    $order_array[] = $this->_deduce_column_name_from_query_param($field_name_to_order_by) . SP . $order;
3560
-                }
3561
-                $query_object->set_order_by_sql(" ORDER BY " . implode(",", $order_array));
3562
-            } elseif (! empty($query_params['order_by'])) {
3563
-                $this->_extract_related_model_info_from_query_param(
3564
-                    $query_params['order_by'],
3565
-                    $query_object,
3566
-                    'order',
3567
-                    $query_params['order_by']
3568
-                );
3569
-                $order = isset($query_params['order'])
3570
-                    ? $this->_extract_order($query_params['order'])
3571
-                    : 'DESC';
3572
-                $query_object->set_order_by_sql(
3573
-                    " ORDER BY " . $this->_deduce_column_name_from_query_param($query_params['order_by']) . SP . $order
3574
-                );
3575
-            }
3576
-        }
3577
-        // if 'order_by' wasn't set, maybe they are just using 'order' on its own?
3578
-        if (
3579
-            ! array_key_exists('order_by', $query_params)
3580
-            && array_key_exists('order', $query_params)
3581
-            && ! empty($query_params['order'])
3582
-        ) {
3583
-            $pk_field = $this->get_primary_key_field();
3584
-            $order = $this->_extract_order($query_params['order']);
3585
-            $query_object->set_order_by_sql(" ORDER BY " . $pk_field->get_qualified_column() . SP . $order);
3586
-        }
3587
-        // set group by
3588
-        if (array_key_exists('group_by', $query_params)) {
3589
-            if (is_array($query_params['group_by'])) {
3590
-                // it's an array, so assume we'll be grouping by a bunch of stuff
3591
-                $group_by_array = array();
3592
-                foreach ($query_params['group_by'] as $field_name_to_group_by) {
3593
-                    $group_by_array[] = $this->_deduce_column_name_from_query_param($field_name_to_group_by);
3594
-                }
3595
-                $query_object->set_group_by_sql(" GROUP BY " . implode(", ", $group_by_array));
3596
-            } elseif (! empty($query_params['group_by'])) {
3597
-                $query_object->set_group_by_sql(
3598
-                    " GROUP BY " . $this->_deduce_column_name_from_query_param($query_params['group_by'])
3599
-                );
3600
-            }
3601
-        }
3602
-        // set having
3603
-        if (array_key_exists('having', $query_params) && $query_params['having']) {
3604
-            $query_object->set_having_sql($this->_construct_having_clause($query_params['having']));
3605
-        }
3606
-        // now, just verify they didn't pass anything wack
3607
-        foreach ($query_params as $query_key => $query_value) {
3608
-            if (! in_array($query_key, $this->_allowed_query_params, true)) {
3609
-                throw new EE_Error(
3610
-                    sprintf(
3611
-                        esc_html__(
3612
-                            "You passed %s as a query parameter to %s, which is illegal! The allowed query parameters are %s",
3613
-                            'event_espresso'
3614
-                        ),
3615
-                        $query_key,
3616
-                        get_class($this),
3617
-                        //                      print_r( $this->_allowed_query_params, TRUE )
3618
-                        implode(',', $this->_allowed_query_params)
3619
-                    )
3620
-                );
3621
-            }
3622
-        }
3623
-        $main_model_join_sql = $query_object->get_main_model_join_sql();
3624
-        if (empty($main_model_join_sql)) {
3625
-            $query_object->set_main_model_join_sql($this->_construct_internal_join());
3626
-        }
3627
-        return $query_object;
3628
-    }
3629
-
3630
-
3631
-
3632
-    /**
3633
-     * Gets the where conditions that should be imposed on the query based on the
3634
-     * context (eg reading frontend, backend, edit or delete).
3635
-     *
3636
-     * @param string $context one of EEM_Base::valid_cap_contexts()
3637
-     * @return array @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3638
-     * @throws EE_Error
3639
-     */
3640
-    public function caps_where_conditions($context = self::caps_read)
3641
-    {
3642
-        EEM_Base::verify_is_valid_cap_context($context);
3643
-        $cap_where_conditions = array();
3644
-        $cap_restrictions = $this->caps_missing($context);
3645
-        /**
3646
-         * @var $cap_restrictions EE_Default_Where_Conditions[]
3647
-         */
3648
-        foreach ($cap_restrictions as $cap => $restriction_if_no_cap) {
3649
-            $cap_where_conditions = array_replace_recursive(
3650
-                $cap_where_conditions,
3651
-                $restriction_if_no_cap->get_default_where_conditions()
3652
-            );
3653
-        }
3654
-        return apply_filters(
3655
-            'FHEE__EEM_Base__caps_where_conditions__return',
3656
-            $cap_where_conditions,
3657
-            $this,
3658
-            $context,
3659
-            $cap_restrictions
3660
-        );
3661
-    }
3662
-
3663
-
3664
-
3665
-    /**
3666
-     * Verifies that $should_be_order_string is in $this->_allowed_order_values,
3667
-     * otherwise throws an exception
3668
-     *
3669
-     * @param string $should_be_order_string
3670
-     * @return string either ASC, asc, DESC or desc
3671
-     * @throws EE_Error
3672
-     */
3673
-    private function _extract_order($should_be_order_string)
3674
-    {
3675
-        if (in_array($should_be_order_string, $this->_allowed_order_values)) {
3676
-            return $should_be_order_string;
3677
-        }
3678
-        throw new EE_Error(
3679
-            sprintf(
3680
-                esc_html__(
3681
-                    "While performing a query on '%s', tried to use '%s' as an order parameter. ",
3682
-                    "event_espresso"
3683
-                ),
3684
-                get_class($this),
3685
-                $should_be_order_string
3686
-            )
3687
-        );
3688
-    }
3689
-
3690
-
3691
-
3692
-    /**
3693
-     * Looks at all the models which are included in this query, and asks each
3694
-     * for their universal_where_params, and returns them in the same format as $query_params[0] (where),
3695
-     * so they can be merged
3696
-     *
3697
-     * @param EE_Model_Query_Info_Carrier $query_info_carrier
3698
-     * @param string                      $use_default_where_conditions can be 'none','other_models_only', or 'all'.
3699
-     *                                                                  'none' means NO default where conditions will
3700
-     *                                                                  be used AT ALL during this query.
3701
-     *                                                                  'other_models_only' means default where
3702
-     *                                                                  conditions from other models will be used, but
3703
-     *                                                                  not for this primary model. 'all', the default,
3704
-     *                                                                  means default where conditions will apply as
3705
-     *                                                                  normal
3706
-     * @param array                       $where_query_params           @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3707
-     * @throws EE_Error
3708
-     * @return array @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3709
-     */
3710
-    private function _get_default_where_conditions_for_models_in_query(
3711
-        EE_Model_Query_Info_Carrier $query_info_carrier,
3712
-        $use_default_where_conditions = EEM_Base::default_where_conditions_all,
3713
-        $where_query_params = array()
3714
-    ) {
3715
-        $allowed_used_default_where_conditions_values = EEM_Base::valid_default_where_conditions();
3716
-        if (! in_array($use_default_where_conditions, $allowed_used_default_where_conditions_values)) {
3717
-            throw new EE_Error(sprintf(
3718
-                esc_html__(
3719
-                    "You passed an invalid value to the query parameter 'default_where_conditions' of '%s'. Allowed values are %s",
3720
-                    "event_espresso"
3721
-                ),
3722
-                $use_default_where_conditions,
3723
-                implode(", ", $allowed_used_default_where_conditions_values)
3724
-            ));
3725
-        }
3726
-        $universal_query_params = array();
3727
-        if ($this->_should_use_default_where_conditions($use_default_where_conditions, true)) {
3728
-            $universal_query_params = $this->_get_default_where_conditions();
3729
-        } elseif ($this->_should_use_minimum_where_conditions($use_default_where_conditions, true)) {
3730
-            $universal_query_params = $this->_get_minimum_where_conditions();
3731
-        }
3732
-        foreach ($query_info_carrier->get_model_names_included() as $model_relation_path => $model_name) {
3733
-            $related_model = $this->get_related_model_obj($model_name);
3734
-            if ($this->_should_use_default_where_conditions($use_default_where_conditions, false)) {
3735
-                $related_model_universal_where_params = $related_model->_get_default_where_conditions($model_relation_path);
3736
-            } elseif ($this->_should_use_minimum_where_conditions($use_default_where_conditions, false)) {
3737
-                $related_model_universal_where_params = $related_model->_get_minimum_where_conditions($model_relation_path);
3738
-            } else {
3739
-                // we don't want to add full or even minimum default where conditions from this model, so just continue
3740
-                continue;
3741
-            }
3742
-            $overrides = $this->_override_defaults_or_make_null_friendly(
3743
-                $related_model_universal_where_params,
3744
-                $where_query_params,
3745
-                $related_model,
3746
-                $model_relation_path
3747
-            );
3748
-            $universal_query_params = EEH_Array::merge_arrays_and_overwrite_keys(
3749
-                $universal_query_params,
3750
-                $overrides
3751
-            );
3752
-        }
3753
-        return $universal_query_params;
3754
-    }
3755
-
3756
-
3757
-
3758
-    /**
3759
-     * Determines whether or not we should use default where conditions for the model in question
3760
-     * (this model, or other related models).
3761
-     * Basically, we should use default where conditions on this model if they have requested to use them on all models,
3762
-     * this model only, or to use minimum where conditions on all other models and normal where conditions on this one.
3763
-     * We should use default where conditions on related models when they requested to use default where conditions
3764
-     * on all models, or specifically just on other related models
3765
-     * @param      $default_where_conditions_value
3766
-     * @param bool $for_this_model false means this is for OTHER related models
3767
-     * @return bool
3768
-     */
3769
-    private function _should_use_default_where_conditions($default_where_conditions_value, $for_this_model = true)
3770
-    {
3771
-        return (
3772
-                   $for_this_model
3773
-                   && in_array(
3774
-                       $default_where_conditions_value,
3775
-                       array(
3776
-                           EEM_Base::default_where_conditions_all,
3777
-                           EEM_Base::default_where_conditions_this_only,
3778
-                           EEM_Base::default_where_conditions_minimum_others,
3779
-                       ),
3780
-                       true
3781
-                   )
3782
-               )
3783
-               || (
3784
-                   ! $for_this_model
3785
-                   && in_array(
3786
-                       $default_where_conditions_value,
3787
-                       array(
3788
-                           EEM_Base::default_where_conditions_all,
3789
-                           EEM_Base::default_where_conditions_others_only,
3790
-                       ),
3791
-                       true
3792
-                   )
3793
-               );
3794
-    }
3795
-
3796
-    /**
3797
-     * Determines whether or not we should use default minimum conditions for the model in question
3798
-     * (this model, or other related models).
3799
-     * Basically, we should use minimum where conditions on this model only if they requested all models to use minimum
3800
-     * where conditions.
3801
-     * We should use minimum where conditions on related models if they requested to use minimum where conditions
3802
-     * on this model or others
3803
-     * @param      $default_where_conditions_value
3804
-     * @param bool $for_this_model false means this is for OTHER related models
3805
-     * @return bool
3806
-     */
3807
-    private function _should_use_minimum_where_conditions($default_where_conditions_value, $for_this_model = true)
3808
-    {
3809
-        return (
3810
-                   $for_this_model
3811
-                   && $default_where_conditions_value === EEM_Base::default_where_conditions_minimum_all
3812
-               )
3813
-               || (
3814
-                   ! $for_this_model
3815
-                   && in_array(
3816
-                       $default_where_conditions_value,
3817
-                       array(
3818
-                           EEM_Base::default_where_conditions_minimum_others,
3819
-                           EEM_Base::default_where_conditions_minimum_all,
3820
-                       ),
3821
-                       true
3822
-                   )
3823
-               );
3824
-    }
3825
-
3826
-
3827
-    /**
3828
-     * Checks if any of the defaults have been overridden. If there are any that AREN'T overridden,
3829
-     * then we also add a special where condition which allows for that model's primary key
3830
-     * to be null (which is important for JOINs. Eg, if you want to see all Events ordered by Venue's name,
3831
-     * then Event's with NO Venue won't appear unless you allow VNU_ID to be NULL)
3832
-     *
3833
-     * @param array    $default_where_conditions
3834
-     * @param array    $provided_where_conditions
3835
-     * @param EEM_Base $model
3836
-     * @param string   $model_relation_path like 'Transaction.Payment.'
3837
-     * @return array @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3838
-     * @throws EE_Error
3839
-     */
3840
-    private function _override_defaults_or_make_null_friendly(
3841
-        $default_where_conditions,
3842
-        $provided_where_conditions,
3843
-        $model,
3844
-        $model_relation_path
3845
-    ) {
3846
-        $null_friendly_where_conditions = array();
3847
-        $none_overridden = true;
3848
-        $or_condition_key_for_defaults = 'OR*' . get_class($model);
3849
-        foreach ($default_where_conditions as $key => $val) {
3850
-            if (isset($provided_where_conditions[ $key ])) {
3851
-                $none_overridden = false;
3852
-            } else {
3853
-                $null_friendly_where_conditions[ $or_condition_key_for_defaults ]['AND'][ $key ] = $val;
3854
-            }
3855
-        }
3856
-        if ($none_overridden && $default_where_conditions) {
3857
-            if ($model->has_primary_key_field()) {
3858
-                $null_friendly_where_conditions[ $or_condition_key_for_defaults ][ $model_relation_path
3859
-                                                                                . "."
3860
-                                                                                . $model->primary_key_name() ] = array('IS NULL');
3861
-            }/*else{
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 = array();
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
+	 * @var string |null
146
+	 */
147
+	protected $model_chain_to_password = null;
148
+
149
+	/**
150
+	 * This is a flag typically set by updates so that we don't load the where strategy on updates because updates
151
+	 * don't need it (particularly CPT models)
152
+	 *
153
+	 * @var bool
154
+	 */
155
+	protected $_ignore_where_strategy = false;
156
+
157
+	/**
158
+	 * String used in caps relating to this model. Eg, if the caps relating to this
159
+	 * model are 'ee_edit_events', 'ee_read_events', etc, it would be 'events'.
160
+	 *
161
+	 * @var string. If null it hasn't been initialized yet. If false then we
162
+	 * have indicated capabilities don't apply to this
163
+	 */
164
+	protected $_caps_slug = null;
165
+
166
+	/**
167
+	 * 2d array where top-level keys are one of EEM_Base::valid_cap_contexts(),
168
+	 * and next-level keys are capability names, and each's value is a
169
+	 * EE_Default_Where_Condition. If the requester requests to apply caps to the query,
170
+	 * they specify which context to use (ie, frontend, backend, edit or delete)
171
+	 * and then each capability in the corresponding sub-array that they're missing
172
+	 * adds the where conditions onto the query.
173
+	 *
174
+	 * @var array
175
+	 */
176
+	protected $_cap_restrictions = array(
177
+		self::caps_read       => array(),
178
+		self::caps_read_admin => array(),
179
+		self::caps_edit       => array(),
180
+		self::caps_delete     => array(),
181
+	);
182
+
183
+	/**
184
+	 * Array defining which cap restriction generators to use to create default
185
+	 * cap restrictions to put in EEM_Base::_cap_restrictions.
186
+	 * Array-keys are one of EEM_Base::valid_cap_contexts(), and values are a child of
187
+	 * EE_Restriction_Generator_Base. If you don't want any cap restrictions generated
188
+	 * automatically set this to false (not just null).
189
+	 *
190
+	 * @var EE_Restriction_Generator_Base[]
191
+	 */
192
+	protected $_cap_restriction_generators = array();
193
+
194
+	/**
195
+	 * constants used to categorize capability restrictions on EEM_Base::_caps_restrictions
196
+	 */
197
+	const caps_read       = 'read';
198
+
199
+	const caps_read_admin = 'read_admin';
200
+
201
+	const caps_edit       = 'edit';
202
+
203
+	const caps_delete     = 'delete';
204
+
205
+	/**
206
+	 * Keys are all the cap contexts (ie constants EEM_Base::_caps_*) and values are their 'action'
207
+	 * as how they'd be used in capability names. Eg EEM_Base::caps_read ('read_frontend')
208
+	 * maps to 'read' because when looking for relevant permissions we're going to use
209
+	 * 'read' in teh capabilities names like 'ee_read_events' etc.
210
+	 *
211
+	 * @var array
212
+	 */
213
+	protected $_cap_contexts_to_cap_action_map = array(
214
+		self::caps_read       => 'read',
215
+		self::caps_read_admin => 'read',
216
+		self::caps_edit       => 'edit',
217
+		self::caps_delete     => 'delete',
218
+	);
219
+
220
+	/**
221
+	 * Timezone
222
+	 * This gets set via the constructor so that we know what timezone incoming strings|timestamps are in when there
223
+	 * are EE_Datetime_Fields in use.  This can also be used before a get to set what timezone you want strings coming
224
+	 * out of the created objects.  NOT all EEM_Base child classes use this property but any that use a
225
+	 * EE_Datetime_Field data type will have access to it.
226
+	 *
227
+	 * @var string
228
+	 */
229
+	protected $_timezone;
230
+
231
+
232
+	/**
233
+	 * This holds the id of the blog currently making the query.  Has no bearing on single site but is used for
234
+	 * multisite.
235
+	 *
236
+	 * @var int
237
+	 */
238
+	protected static $_model_query_blog_id;
239
+
240
+	/**
241
+	 * A copy of _fields, except the array keys are the model names pointed to by
242
+	 * the field
243
+	 *
244
+	 * @var EE_Model_Field_Base[]
245
+	 */
246
+	private $_cache_foreign_key_to_fields = array();
247
+
248
+	/**
249
+	 * Cached list of all the fields on the model, indexed by their name
250
+	 *
251
+	 * @var EE_Model_Field_Base[]
252
+	 */
253
+	private $_cached_fields = null;
254
+
255
+	/**
256
+	 * Cached list of all the fields on the model, except those that are
257
+	 * marked as only pertinent to the database
258
+	 *
259
+	 * @var EE_Model_Field_Base[]
260
+	 */
261
+	private $_cached_fields_non_db_only = null;
262
+
263
+	/**
264
+	 * A cached reference to the primary key for quick lookup
265
+	 *
266
+	 * @var EE_Model_Field_Base
267
+	 */
268
+	private $_primary_key_field = null;
269
+
270
+	/**
271
+	 * Flag indicating whether this model has a primary key or not
272
+	 *
273
+	 * @var boolean
274
+	 */
275
+	protected $_has_primary_key_field = null;
276
+
277
+	/**
278
+	 * array in the format:  [ FK alias => full PK ]
279
+	 * where keys are local column name aliases for foreign keys
280
+	 * and values are the fully qualified column name for the primary key they represent
281
+	 *  ex:
282
+	 *      [ 'Event.EVT_wp_user' => 'WP_User.ID' ]
283
+	 *
284
+	 * @var array $foreign_key_aliases
285
+	 */
286
+	protected $foreign_key_aliases = [];
287
+
288
+	/**
289
+	 * Whether or not this model is based off a table in WP core only (CPTs should set
290
+	 * this to FALSE, but if we were to make an EE_WP_Post model, it should set this to true).
291
+	 * This should be true for models that deal with data that should exist independent of EE.
292
+	 * For example, if the model can read and insert data that isn't used by EE, this should be true.
293
+	 * It would be false, however, if you could guarantee the model would only interact with EE data,
294
+	 * even if it uses a WP core table (eg event and venue models set this to false for that reason:
295
+	 * they can only read and insert events and venues custom post types, not arbitrary post types)
296
+	 * @var boolean
297
+	 */
298
+	protected $_wp_core_model = false;
299
+
300
+	/**
301
+	 * @var bool stores whether this model has a password field or not.
302
+	 * null until initialized by hasPasswordField()
303
+	 */
304
+	protected $has_password_field;
305
+
306
+	/**
307
+	 * @var EE_Password_Field|null Automatically set when calling getPasswordField()
308
+	 */
309
+	protected $password_field;
310
+
311
+	/**
312
+	 *    List of valid operators that can be used for querying.
313
+	 * The keys are all operators we'll accept, the values are the real SQL
314
+	 * operators used
315
+	 *
316
+	 * @var array
317
+	 */
318
+	protected $_valid_operators = array(
319
+		'='           => '=',
320
+		'<='          => '<=',
321
+		'<'           => '<',
322
+		'>='          => '>=',
323
+		'>'           => '>',
324
+		'!='          => '!=',
325
+		'LIKE'        => 'LIKE',
326
+		'like'        => 'LIKE',
327
+		'NOT_LIKE'    => 'NOT LIKE',
328
+		'not_like'    => 'NOT LIKE',
329
+		'NOT LIKE'    => 'NOT LIKE',
330
+		'not like'    => 'NOT LIKE',
331
+		'IN'          => 'IN',
332
+		'in'          => 'IN',
333
+		'NOT_IN'      => 'NOT IN',
334
+		'not_in'      => 'NOT IN',
335
+		'NOT IN'      => 'NOT IN',
336
+		'not in'      => 'NOT IN',
337
+		'between'     => 'BETWEEN',
338
+		'BETWEEN'     => 'BETWEEN',
339
+		'IS_NOT_NULL' => 'IS NOT NULL',
340
+		'is_not_null' => 'IS NOT NULL',
341
+		'IS NOT NULL' => 'IS NOT NULL',
342
+		'is not null' => 'IS NOT NULL',
343
+		'IS_NULL'     => 'IS NULL',
344
+		'is_null'     => 'IS NULL',
345
+		'IS NULL'     => 'IS NULL',
346
+		'is null'     => 'IS NULL',
347
+		'REGEXP'      => 'REGEXP',
348
+		'regexp'      => 'REGEXP',
349
+		'NOT_REGEXP'  => 'NOT REGEXP',
350
+		'not_regexp'  => 'NOT REGEXP',
351
+		'NOT REGEXP'  => 'NOT REGEXP',
352
+		'not regexp'  => 'NOT REGEXP',
353
+	);
354
+
355
+	/**
356
+	 * operators that work like 'IN', accepting a comma-separated list of values inside brackets. Eg '(1,2,3)'
357
+	 *
358
+	 * @var array
359
+	 */
360
+	protected $_in_style_operators = array('IN', 'NOT IN');
361
+
362
+	/**
363
+	 * operators that work like 'BETWEEN'.  Typically used for datetime calculations, i.e. "BETWEEN '12-1-2011' AND
364
+	 * '12-31-2012'"
365
+	 *
366
+	 * @var array
367
+	 */
368
+	protected $_between_style_operators = array('BETWEEN');
369
+
370
+	/**
371
+	 * Operators that work like SQL's like: input should be assumed to be a string, already prepared for a LIKE query.
372
+	 * @var array
373
+	 */
374
+	protected $_like_style_operators = array('LIKE', 'NOT LIKE');
375
+	/**
376
+	 * operators that are used for handling NUll and !NULL queries.  Typically used for when checking if a row exists
377
+	 * on a join table.
378
+	 *
379
+	 * @var array
380
+	 */
381
+	protected $_null_style_operators = array('IS NOT NULL', 'IS NULL');
382
+
383
+	/**
384
+	 * Allowed values for $query_params['order'] for ordering in queries
385
+	 *
386
+	 * @var array
387
+	 */
388
+	protected $_allowed_order_values = array('asc', 'desc', 'ASC', 'DESC');
389
+
390
+	/**
391
+	 * When these are keys in a WHERE or HAVING clause, they are handled much differently
392
+	 * than regular field names. It is assumed that their values are an array of WHERE conditions
393
+	 *
394
+	 * @var array
395
+	 */
396
+	private $_logic_query_param_keys = array('not', 'and', 'or', 'NOT', 'AND', 'OR');
397
+
398
+	/**
399
+	 * Allowed keys in $query_params arrays passed into queries. Note that 0 is meant to always be a
400
+	 * 'where', but 'where' clauses are so common that we thought we'd omit it
401
+	 *
402
+	 * @var array
403
+	 */
404
+	private $_allowed_query_params = array(
405
+		0,
406
+		'limit',
407
+		'order_by',
408
+		'group_by',
409
+		'having',
410
+		'force_join',
411
+		'order',
412
+		'on_join_limit',
413
+		'default_where_conditions',
414
+		'caps',
415
+		'extra_selects',
416
+		'exclude_protected',
417
+	);
418
+
419
+	/**
420
+	 * All the data types that can be used in $wpdb->prepare statements.
421
+	 *
422
+	 * @var array
423
+	 */
424
+	private $_valid_wpdb_data_types = array('%d', '%s', '%f');
425
+
426
+	/**
427
+	 * @var EE_Registry $EE
428
+	 */
429
+	protected $EE = null;
430
+
431
+
432
+	/**
433
+	 * Property which, when set, will have this model echo out the next X queries to the page for debugging.
434
+	 *
435
+	 * @var int
436
+	 */
437
+	protected $_show_next_x_db_queries = 0;
438
+
439
+	/**
440
+	 * When using _get_all_wpdb_results, you can specify a custom selection. If you do so,
441
+	 * it gets saved on this property as an instance of CustomSelects so those selections can be used in
442
+	 * WHERE, GROUP_BY, etc.
443
+	 *
444
+	 * @var CustomSelects
445
+	 */
446
+	protected $_custom_selections = array();
447
+
448
+	/**
449
+	 * key => value Entity Map using  array( EEM_Base::$_model_query_blog_id => array( ID => model object ) )
450
+	 * caches every model object we've fetched from the DB on this request
451
+	 *
452
+	 * @var array
453
+	 */
454
+	protected $_entity_map;
455
+
456
+	/**
457
+	 * @var LoaderInterface
458
+	 */
459
+	protected static $loader;
460
+
461
+	/**
462
+	 * @var Mirror
463
+	 */
464
+	private static $mirror;
465
+
466
+
467
+
468
+	/**
469
+	 * constant used to show EEM_Base has not yet verified the db on this http request
470
+	 */
471
+	const db_verified_none = 0;
472
+
473
+	/**
474
+	 * constant used to show EEM_Base has verified the EE core db on this http request,
475
+	 * but not the addons' dbs
476
+	 */
477
+	const db_verified_core = 1;
478
+
479
+	/**
480
+	 * constant used to show EEM_Base has verified the addons' dbs (and implicitly
481
+	 * the EE core db too)
482
+	 */
483
+	const db_verified_addons = 2;
484
+
485
+	/**
486
+	 * indicates whether an EEM_Base child has already re-verified the DB
487
+	 * is ok (we don't want to do it repetitively). Should be set to one the constants
488
+	 * looking like EEM_Base::db_verified_*
489
+	 *
490
+	 * @var int - 0 = none, 1 = core, 2 = addons
491
+	 */
492
+	protected static $_db_verification_level = EEM_Base::db_verified_none;
493
+
494
+	/**
495
+	 * @const constant for 'default_where_conditions' to apply default where conditions to ALL queried models
496
+	 *        (eg, if retrieving registrations ordered by their datetimes, this will only return non-trashed
497
+	 *        registrations for non-trashed tickets for non-trashed datetimes)
498
+	 */
499
+	const default_where_conditions_all = 'all';
500
+
501
+	/**
502
+	 * @const constant for 'default_where_conditions' to apply default where conditions to THIS model only, but
503
+	 *        no other models which are joined to (eg, if retrieving registrations ordered by their datetimes, this will
504
+	 *        return non-trashed registrations, regardless of the related datetimes and tickets' statuses).
505
+	 *        It is preferred to use EEM_Base::default_where_conditions_minimum_others because, when joining to
506
+	 *        models which share tables with other models, this can return data for the wrong model.
507
+	 */
508
+	const default_where_conditions_this_only = 'this_model_only';
509
+
510
+	/**
511
+	 * @const constant for 'default_where_conditions' to apply default where conditions to other models queried,
512
+	 *        but not the current model (eg, if retrieving registrations ordered by their datetimes, this will
513
+	 *        return all registrations related to non-trashed tickets and non-trashed datetimes)
514
+	 */
515
+	const default_where_conditions_others_only = 'other_models_only';
516
+
517
+	/**
518
+	 * @const constant for 'default_where_conditions' to apply minimum where conditions to all models queried.
519
+	 *        For most models this the same as EEM_Base::default_where_conditions_none, except for models which share
520
+	 *        their table with other models, like the Event and Venue models. For example, when querying for events
521
+	 *        ordered by their venues' name, this will be sure to only return real events with associated real venues
522
+	 *        (regardless of whether those events and venues are trashed)
523
+	 *        In contrast, using EEM_Base::default_where_conditions_none would could return WP posts other than EE
524
+	 *        events.
525
+	 */
526
+	const default_where_conditions_minimum_all = 'minimum';
527
+
528
+	/**
529
+	 * @const constant for 'default_where_conditions' to apply apply where conditions to other models, and full default
530
+	 *        where conditions for the queried model (eg, when querying events ordered by venues' names, this will
531
+	 *        return non-trashed events for any venues, regardless of whether those associated venues are trashed or
532
+	 *        not)
533
+	 */
534
+	const default_where_conditions_minimum_others = 'full_this_minimum_others';
535
+
536
+	/**
537
+	 * @const constant for 'default_where_conditions' to NOT apply any where conditions. This should very rarely be
538
+	 *        used, because when querying from a model which shares its table with another model (eg Events and Venues)
539
+	 *        it's possible it will return table entries for other models. You should use
540
+	 *        EEM_Base::default_where_conditions_minimum_all instead.
541
+	 */
542
+	const default_where_conditions_none = 'none';
543
+
544
+
545
+
546
+	/**
547
+	 * About all child constructors:
548
+	 * they should define the _tables, _fields and _model_relations arrays.
549
+	 * Should ALWAYS be called after child constructor.
550
+	 * In order to make the child constructors to be as simple as possible, this parent constructor
551
+	 * finalizes constructing all the object's attributes.
552
+	 * Generally, rather than requiring a child to code
553
+	 * $this->_tables = array(
554
+	 *        'Event_Post_Table' => new EE_Table('Event_Post_Table','wp_posts')
555
+	 *        ...);
556
+	 *  (thus repeating itself in the array key and in the constructor of the new EE_Table,)
557
+	 * each EE_Table has a function to set the table's alias after the constructor, using
558
+	 * the array key ('Event_Post_Table'), instead of repeating it. The model fields and model relations
559
+	 * do something similar.
560
+	 *
561
+	 * @param null $timezone
562
+	 * @throws EE_Error
563
+	 */
564
+	protected function __construct($timezone = null)
565
+	{
566
+		// check that the model has not been loaded too soon
567
+		if (! did_action('AHEE__EE_System__load_espresso_addons')) {
568
+			throw new EE_Error(
569
+				sprintf(
570
+					esc_html__(
571
+						'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.',
572
+						'event_espresso'
573
+					),
574
+					get_class($this)
575
+				)
576
+			);
577
+		}
578
+		/**
579
+		 * Set blogid for models to current blog. However we ONLY do this if $_model_query_blog_id is not already set.
580
+		 */
581
+		if (empty(EEM_Base::$_model_query_blog_id)) {
582
+			EEM_Base::set_model_query_blog_id();
583
+		}
584
+		/**
585
+		 * Filters the list of tables on a model. It is best to NOT use this directly and instead
586
+		 * just use EE_Register_Model_Extension
587
+		 *
588
+		 * @var EE_Table_Base[] $_tables
589
+		 */
590
+		$this->_tables = (array) apply_filters('FHEE__' . get_class($this) . '__construct__tables', $this->_tables);
591
+		foreach ($this->_tables as $table_alias => $table_obj) {
592
+			/** @var $table_obj EE_Table_Base */
593
+			$table_obj->_construct_finalize_with_alias($table_alias);
594
+			if ($table_obj instanceof EE_Secondary_Table) {
595
+				/** @var $table_obj EE_Secondary_Table */
596
+				$table_obj->_construct_finalize_set_table_to_join_with($this->_get_main_table());
597
+			}
598
+		}
599
+		/**
600
+		 * Filters the list of fields on a model. It is best to NOT use this directly and instead just use
601
+		 * EE_Register_Model_Extension
602
+		 *
603
+		 * @param EE_Model_Field_Base[] $_fields
604
+		 */
605
+		$this->_fields = (array) apply_filters('FHEE__' . get_class($this) . '__construct__fields', $this->_fields);
606
+		$this->_invalidate_field_caches();
607
+		foreach ($this->_fields as $table_alias => $fields_for_table) {
608
+			if (! array_key_exists($table_alias, $this->_tables)) {
609
+				throw new EE_Error(sprintf(esc_html__(
610
+					"Table alias %s does not exist in EEM_Base child's _tables array. Only tables defined are %s",
611
+					'event_espresso'
612
+				), $table_alias, implode(",", $this->_fields)));
613
+			}
614
+			foreach ($fields_for_table as $field_name => $field_obj) {
615
+				/** @var $field_obj EE_Model_Field_Base | EE_Primary_Key_Field_Base */
616
+				// primary key field base has a slightly different _construct_finalize
617
+				/** @var $field_obj EE_Model_Field_Base */
618
+				$field_obj->_construct_finalize($table_alias, $field_name, $this->get_this_model_name());
619
+			}
620
+		}
621
+		// everything is related to Extra_Meta
622
+		if (get_class($this) !== 'EEM_Extra_Meta') {
623
+			// make extra meta related to everything, but don't block deleting things just
624
+			// because they have related extra meta info. For now just orphan those extra meta
625
+			// in the future we should automatically delete them
626
+			$this->_model_relations['Extra_Meta'] = new EE_Has_Many_Any_Relation(false);
627
+		}
628
+		// and change logs
629
+		if (get_class($this) !== 'EEM_Change_Log') {
630
+			$this->_model_relations['Change_Log'] = new EE_Has_Many_Any_Relation(false);
631
+		}
632
+		/**
633
+		 * Filters the list of relations on a model. It is best to NOT use this directly and instead just use
634
+		 * EE_Register_Model_Extension
635
+		 *
636
+		 * @param EE_Model_Relation_Base[] $_model_relations
637
+		 */
638
+		$this->_model_relations = (array) apply_filters(
639
+			'FHEE__' . get_class($this) . '__construct__model_relations',
640
+			$this->_model_relations
641
+		);
642
+		foreach ($this->_model_relations as $model_name => $relation_obj) {
643
+			/** @var $relation_obj EE_Model_Relation_Base */
644
+			$relation_obj->_construct_finalize_set_models($this->get_this_model_name(), $model_name);
645
+		}
646
+		foreach ($this->_indexes as $index_name => $index_obj) {
647
+			$index_obj->_construct_finalize($index_name, $this->get_this_model_name());
648
+		}
649
+		$this->set_timezone($timezone);
650
+		// finalize default where condition strategy, or set default
651
+		if (! $this->_default_where_conditions_strategy) {
652
+			// nothing was set during child constructor, so set default
653
+			$this->_default_where_conditions_strategy = new EE_Default_Where_Conditions();
654
+		}
655
+		$this->_default_where_conditions_strategy->_finalize_construct($this);
656
+		if (! $this->_minimum_where_conditions_strategy) {
657
+			// nothing was set during child constructor, so set default
658
+			$this->_minimum_where_conditions_strategy = new EE_Default_Where_Conditions();
659
+		}
660
+		$this->_minimum_where_conditions_strategy->_finalize_construct($this);
661
+		// if the cap slug hasn't been set, and we haven't set it to false on purpose
662
+		// to indicate to NOT set it, set it to the logical default
663
+		if ($this->_caps_slug === null) {
664
+			$this->_caps_slug = EEH_Inflector::pluralize_and_lower($this->get_this_model_name());
665
+		}
666
+		// initialize the standard cap restriction generators if none were specified by the child constructor
667
+		if (is_array($this->_cap_restriction_generators)) {
668
+			foreach ($this->cap_contexts_to_cap_action_map() as $cap_context => $action) {
669
+				if (! isset($this->_cap_restriction_generators[ $cap_context ])) {
670
+					$this->_cap_restriction_generators[ $cap_context ] = apply_filters(
671
+						'FHEE__EEM_Base___construct__standard_cap_restriction_generator',
672
+						new EE_Restriction_Generator_Protected(),
673
+						$cap_context,
674
+						$this
675
+					);
676
+				}
677
+			}
678
+		}
679
+		// if there are cap restriction generators, use them to make the default cap restrictions
680
+		if (is_array($this->_cap_restriction_generators)) {
681
+			foreach ($this->_cap_restriction_generators as $context => $generator_object) {
682
+				if (! $generator_object) {
683
+					continue;
684
+				}
685
+				if (! $generator_object instanceof EE_Restriction_Generator_Base) {
686
+					throw new EE_Error(
687
+						sprintf(
688
+							esc_html__(
689
+								'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.',
690
+								'event_espresso'
691
+							),
692
+							$context,
693
+							$this->get_this_model_name()
694
+						)
695
+					);
696
+				}
697
+				$action = $this->cap_action_for_context($context);
698
+				if (! $generator_object->construction_finalized()) {
699
+					$generator_object->_construct_finalize($this, $action);
700
+				}
701
+			}
702
+		}
703
+		do_action('AHEE__' . get_class($this) . '__construct__end');
704
+	}
705
+
706
+
707
+	/**
708
+	 * @return LoaderInterface
709
+	 * @throws InvalidArgumentException
710
+	 * @throws InvalidDataTypeException
711
+	 * @throws InvalidInterfaceException
712
+	 */
713
+	protected static function getLoader(): LoaderInterface
714
+	{
715
+		if (! EEM_Base::$loader instanceof LoaderInterface) {
716
+			EEM_Base::$loader = LoaderFactory::getLoader();
717
+		}
718
+		return EEM_Base::$loader;
719
+	}
720
+
721
+
722
+	/**
723
+	 * @return Mirror
724
+	 * @since   $VID:$
725
+	 */
726
+	private static function getMirror(): Mirror
727
+	{
728
+		if (! EEM_Base::$mirror instanceof Mirror) {
729
+			EEM_Base::$mirror = EEM_Base::getLoader()->getShared(Mirror::class);
730
+		}
731
+		return EEM_Base::$mirror;
732
+	}
733
+
734
+
735
+	/**
736
+	 * @param string $model_class_Name
737
+	 * @param string $timezone
738
+	 * @return array
739
+	 * @throws ReflectionException
740
+	 * @since   $VID:$
741
+	 */
742
+	private static function getModelArguments(string $model_class_Name, string $timezone): array
743
+	{
744
+		$arguments = [$timezone];
745
+		$params    = EEM_Base::getMirror()->getParameters($model_class_Name);
746
+		if (count($params) > 1) {
747
+			if ($params[1]->getName() === 'model_field_factory') {
748
+				$arguments = [
749
+					$timezone,
750
+					EEM_Base::getLoader()->getShared(ModelFieldFactory::class)
751
+				];
752
+			} elseif ($model_class_Name === 'EEM_Form_Section') {
753
+				$arguments = [
754
+					EEM_Base::getLoader()->getShared('EventEspresso\core\services\form\meta\FormStatus'),
755
+					$timezone
756
+				];
757
+			} elseif ($model_class_Name === 'EEM_Form_Element') {
758
+				$arguments = [
759
+					EEM_Base::getLoader()->getShared('EventEspresso\core\services\form\meta\FormStatus'),
760
+					EEM_Base::getLoader()->getShared('EventEspresso\core\services\form\meta\InputTypes'),
761
+					$timezone,
762
+				];
763
+			}
764
+		}
765
+		return $arguments;
766
+	}
767
+
768
+
769
+	/**
770
+	 * This function is a singleton method used to instantiate the Espresso_model object
771
+	 *
772
+	 * @param string|null $timezone   string representing the timezone we want to set for returned Date Time Strings
773
+	 *                                (and any incoming timezone data that gets saved).
774
+	 *                                Note this just sends the timezone info to the date time model field objects.
775
+	 *                                Default is NULL
776
+	 *                                (and will be assumed using the set timezone in the 'timezone_string' wp option)
777
+	 * @return static (as in the concrete child class)
778
+	 * @throws EE_Error
779
+	 * @throws ReflectionException
780
+	 */
781
+	public static function instance($timezone = null)
782
+	{
783
+		// check if instance of Espresso_model already exists
784
+		if (! static::$_instance instanceof static) {
785
+			$arguments = EEM_Base::getModelArguments(static::class, (string) $timezone);
786
+			$model = new static(...$arguments);
787
+			EEM_Base::getLoader()->share(static::class, $model, $arguments);
788
+			static::$_instance = $model;
789
+		}
790
+		// we might have a timezone set, let set_timezone decide what to do with it
791
+		if ($timezone) {
792
+			static::$_instance->set_timezone($timezone);
793
+		}
794
+		// Espresso_model object
795
+		return static::$_instance;
796
+	}
797
+
798
+
799
+
800
+	/**
801
+	 * resets the model and returns it
802
+	 *
803
+	 * @param string|null $timezone
804
+	 * @return EEM_Base|null (if the model was already instantiated, returns it, with
805
+	 * all its properties reset; if it wasn't instantiated, returns null)
806
+	 * @throws EE_Error
807
+	 * @throws ReflectionException
808
+	 * @throws InvalidArgumentException
809
+	 * @throws InvalidDataTypeException
810
+	 * @throws InvalidInterfaceException
811
+	 */
812
+	public static function reset($timezone = null)
813
+	{
814
+		if (! static::$_instance instanceof EEM_Base) {
815
+			return null;
816
+		}
817
+		// Let's NOT swap out the current instance for a new one
818
+		// because if someone has a reference to it, we can't remove their reference.
819
+		// It's best to keep using the same reference but change the original object instead,
820
+		// so reset all its properties to their original values as defined in the class.
821
+		$static_properties = EEM_Base::getMirror()->getStaticProperties(static::class);
822
+		foreach (EEM_Base::getMirror()->getDefaultProperties(static::class) as $property => $value) {
823
+			// don't set instance to null like it was originally,
824
+			// but it's static anyways, and we're ignoring static properties (for now at least)
825
+			if (! isset($static_properties[ $property ])) {
826
+				static::$_instance->{$property} = $value;
827
+			}
828
+		}
829
+		// and then directly call its constructor again, like we would if we were creating a new one
830
+		$arguments = EEM_Base::getModelArguments(static::class, (string) $timezone);
831
+		static::$_instance->__construct(...$arguments);
832
+		return self::instance();
833
+	}
834
+
835
+
836
+	/**
837
+	 * Used to set the $_model_query_blog_id static property.
838
+	 *
839
+	 * @param int $blog_id  If provided then will set the blog_id for the models to this id.  If not provided then the
840
+	 *                      value for get_current_blog_id() will be used.
841
+	 */
842
+	public static function set_model_query_blog_id($blog_id = 0)
843
+	{
844
+		EEM_Base::$_model_query_blog_id = $blog_id > 0
845
+			? (int) $blog_id
846
+			: get_current_blog_id();
847
+	}
848
+
849
+
850
+	/**
851
+	 * Returns whatever is set as the internal $model_query_blog_id.
852
+	 *
853
+	 * @return int
854
+	 */
855
+	public static function get_model_query_blog_id()
856
+	{
857
+		return EEM_Base::$_model_query_blog_id;
858
+	}
859
+
860
+
861
+
862
+	/**
863
+	 * retrieve the status details from esp_status table as an array IF this model has the status table as a relation.
864
+	 *
865
+	 * @param  boolean $translated return localized strings or JUST the array.
866
+	 * @return array
867
+	 * @throws EE_Error
868
+	 * @throws InvalidArgumentException
869
+	 * @throws InvalidDataTypeException
870
+	 * @throws InvalidInterfaceException
871
+	 */
872
+	public function status_array($translated = false)
873
+	{
874
+		if (! array_key_exists('Status', $this->_model_relations)) {
875
+			return array();
876
+		}
877
+		$model_name = $this->get_this_model_name();
878
+		$status_type = str_replace(' ', '_', strtolower(str_replace('_', ' ', $model_name)));
879
+		$stati = EEM_Status::instance()->get_all(array(array('STS_type' => $status_type)));
880
+		$status_array = array();
881
+		foreach ($stati as $status) {
882
+			$status_array[ $status->ID() ] = $status->get('STS_code');
883
+		}
884
+		return $translated
885
+			? EEM_Status::instance()->localized_status($status_array, false, 'sentence')
886
+			: $status_array;
887
+	}
888
+
889
+
890
+
891
+	/**
892
+	 * Gets all the EE_Base_Class objects which match the $query_params, by querying the DB.
893
+	 *
894
+	 * @param array $query_params  @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
895
+	 *                             or if you have the development copy of EE you can view this at the path:
896
+	 *                             /docs/G--Model-System/model-query-params.md
897
+	 * @return EE_Base_Class[]  *note that there is NO option to pass the output type. If you want results different
898
+	 *                                        from EE_Base_Class[], use get_all_wpdb_results(). Array keys are object IDs (if there is a primary key on the model.
899
+	 *                                        if not, numerically indexed) Some full examples: get 10 transactions
900
+	 *                                        which have Scottish attendees: EEM_Transaction::instance()->get_all(
901
+	 *                                        array( array(
902
+	 *                                        'OR'=>array(
903
+	 *                                        'Registration.Attendee.ATT_fname'=>array('like','Mc%'),
904
+	 *                                        'Registration.Attendee.ATT_fname*other'=>array('like','Mac%')
905
+	 *                                        )
906
+	 *                                        ),
907
+	 *                                        'limit'=>10,
908
+	 *                                        'group_by'=>'TXN_ID'
909
+	 *                                        ));
910
+	 *                                        get all the answers to the question titled "shirt size" for event with id
911
+	 *                                        12, ordered by their answer EEM_Answer::instance()->get_all(array( array(
912
+	 *                                        'Question.QST_display_text'=>'shirt size',
913
+	 *                                        'Registration.Event.EVT_ID'=>12
914
+	 *                                        ),
915
+	 *                                        'order_by'=>array('ANS_value'=>'ASC')
916
+	 *                                        ));
917
+	 * @throws EE_Error
918
+	 */
919
+	public function get_all($query_params = array())
920
+	{
921
+		if (
922
+			isset($query_params['limit'])
923
+			&& ! isset($query_params['group_by'])
924
+		) {
925
+			$query_params['group_by'] = array_keys($this->get_combined_primary_key_fields());
926
+		}
927
+		return $this->_create_objects($this->_get_all_wpdb_results($query_params));
928
+	}
929
+
930
+
931
+
932
+	/**
933
+	 * Modifies the query parameters so we only get back model objects
934
+	 * that "belong" to the current user
935
+	 *
936
+	 * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
937
+	 * @return array @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
938
+	 */
939
+	public function alter_query_params_to_only_include_mine($query_params = array())
940
+	{
941
+		$wp_user_field_name = $this->wp_user_field_name();
942
+		if ($wp_user_field_name) {
943
+			$query_params[0][ $wp_user_field_name ] = get_current_user_id();
944
+		}
945
+		return $query_params;
946
+	}
947
+
948
+
949
+
950
+	/**
951
+	 * Returns the name of the field's name that points to the WP_User table
952
+	 *  on this model (or follows the _model_chain_to_wp_user and uses that model's
953
+	 * foreign key to the WP_User table)
954
+	 *
955
+	 * @return string|boolean string on success, boolean false when there is no
956
+	 * foreign key to the WP_User table
957
+	 */
958
+	public function wp_user_field_name()
959
+	{
960
+		try {
961
+			if (! empty($this->_model_chain_to_wp_user)) {
962
+				$models_to_follow_to_wp_users = explode('.', $this->_model_chain_to_wp_user);
963
+				$last_model_name = end($models_to_follow_to_wp_users);
964
+				$model_with_fk_to_wp_users = EE_Registry::instance()->load_model($last_model_name);
965
+				$model_chain_to_wp_user = $this->_model_chain_to_wp_user . '.';
966
+			} else {
967
+				$model_with_fk_to_wp_users = $this;
968
+				$model_chain_to_wp_user = '';
969
+			}
970
+			$wp_user_field = $model_with_fk_to_wp_users->get_foreign_key_to('WP_User');
971
+			return $model_chain_to_wp_user . $wp_user_field->get_name();
972
+		} catch (EE_Error $e) {
973
+			return false;
974
+		}
975
+	}
976
+
977
+
978
+
979
+	/**
980
+	 * Returns the _model_chain_to_wp_user string, which indicates which related model
981
+	 * (or transiently-related model) has a foreign key to the wp_users table;
982
+	 * useful for finding if model objects of this type are 'owned' by the current user.
983
+	 * This is an empty string when the foreign key is on this model and when it isn't,
984
+	 * but is only non-empty when this model's ownership is indicated by a RELATED model
985
+	 * (or transiently-related model)
986
+	 *
987
+	 * @return string
988
+	 */
989
+	public function model_chain_to_wp_user()
990
+	{
991
+		return $this->_model_chain_to_wp_user;
992
+	}
993
+
994
+
995
+
996
+	/**
997
+	 * Whether this model is 'owned' by a specific wordpress user (even indirectly,
998
+	 * like how registrations don't have a foreign key to wp_users, but the
999
+	 * events they are for are), or is unrelated to wp users.
1000
+	 * generally available
1001
+	 *
1002
+	 * @return boolean
1003
+	 */
1004
+	public function is_owned()
1005
+	{
1006
+		if ($this->model_chain_to_wp_user()) {
1007
+			return true;
1008
+		}
1009
+		try {
1010
+			$this->get_foreign_key_to('WP_User');
1011
+			return true;
1012
+		} catch (EE_Error $e) {
1013
+			return false;
1014
+		}
1015
+	}
1016
+
1017
+
1018
+	/**
1019
+	 * Used internally to get WPDB results, because other functions, besides get_all, may want to do some queries, but
1020
+	 * may want to preserve the WPDB results (eg, update, which first queries to make sure we have all the tables on
1021
+	 * the model)
1022
+	 *
1023
+	 * @param array  $query_params      @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1024
+	 * @param string $output            ARRAY_A, OBJECT_K, etc. Just like
1025
+	 * @param mixed  $columns_to_select , What columns to select. By default, we select all columns specified by the
1026
+	 *                                  fields on the model, and the models we joined to in the query. However, you can
1027
+	 *                                  override this and set the select to "*", or a specific column name, like
1028
+	 *                                  "ATT_ID", etc. If you would like to use these custom selections in WHERE,
1029
+	 *                                  GROUP_BY, or HAVING clauses, you must instead provide an array. Array keys are
1030
+	 *                                  the aliases used to refer to this selection, and values are to be
1031
+	 *                                  numerically-indexed arrays, where 0 is the selection and 1 is the data type.
1032
+	 *                                  Eg, array('count'=>array('COUNT(REG_ID)','%d'))
1033
+	 * @return array | stdClass[] like results of $wpdb->get_results($sql,OBJECT), (ie, output type is OBJECT)
1034
+	 * @throws EE_Error
1035
+	 * @throws InvalidArgumentException
1036
+	 */
1037
+	protected function _get_all_wpdb_results($query_params = array(), $output = ARRAY_A, $columns_to_select = null)
1038
+	{
1039
+		$this->_custom_selections = $this->getCustomSelection($query_params, $columns_to_select);
1040
+		$model_query_info = $this->_create_model_query_info_carrier($query_params);
1041
+		$select_expressions = $columns_to_select === null
1042
+			? $this->_construct_default_select_sql($model_query_info)
1043
+			: '';
1044
+		if ($this->_custom_selections instanceof CustomSelects) {
1045
+			$custom_expressions = $this->_custom_selections->columnsToSelectExpression();
1046
+			$select_expressions .= $select_expressions
1047
+				? ', ' . $custom_expressions
1048
+				: $custom_expressions;
1049
+		}
1050
+
1051
+		$SQL = "SELECT $select_expressions " . $this->_construct_2nd_half_of_select_query($model_query_info);
1052
+		return $this->_do_wpdb_query('get_results', array($SQL, $output));
1053
+	}
1054
+
1055
+
1056
+	/**
1057
+	 * Get a CustomSelects object if the $query_params or $columns_to_select allows for it.
1058
+	 * Note: $query_params['extra_selects'] will always override any $columns_to_select values. It is the preferred
1059
+	 * method of including extra select information.
1060
+	 *
1061
+	 * @param array             $query_params
1062
+	 * @param null|array|string $columns_to_select
1063
+	 * @return null|CustomSelects
1064
+	 * @throws InvalidArgumentException
1065
+	 */
1066
+	protected function getCustomSelection(array $query_params, $columns_to_select = null)
1067
+	{
1068
+		if (! isset($query_params['extra_selects']) && $columns_to_select === null) {
1069
+			return null;
1070
+		}
1071
+		$selects = isset($query_params['extra_selects']) ? $query_params['extra_selects'] : $columns_to_select;
1072
+		$selects = is_string($selects) ? explode(',', $selects) : $selects;
1073
+		return new CustomSelects($selects);
1074
+	}
1075
+
1076
+
1077
+
1078
+	/**
1079
+	 * Gets an array of rows from the database just like $wpdb->get_results would,
1080
+	 * but you can use the model query params to more easily
1081
+	 * take care of joins, field preparation etc.
1082
+	 *
1083
+	 * @param array  $query_params      @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1084
+	 * @param string $output            ARRAY_A, OBJECT_K, etc. Just like
1085
+	 * @param mixed  $columns_to_select , What columns to select. By default, we select all columns specified by the
1086
+	 *                                  fields on the model, and the models we joined to in the query. However, you can
1087
+	 *                                  override this and set the select to "*", or a specific column name, like
1088
+	 *                                  "ATT_ID", etc. If you would like to use these custom selections in WHERE,
1089
+	 *                                  GROUP_BY, or HAVING clauses, you must instead provide an array. Array keys are
1090
+	 *                                  the aliases used to refer to this selection, and values are to be
1091
+	 *                                  numerically-indexed arrays, where 0 is the selection and 1 is the data type.
1092
+	 *                                  Eg, array('count'=>array('COUNT(REG_ID)','%d'))
1093
+	 * @return array|stdClass[] like results of $wpdb->get_results($sql,OBJECT), (ie, output type is OBJECT)
1094
+	 * @throws EE_Error
1095
+	 */
1096
+	public function get_all_wpdb_results($query_params = array(), $output = ARRAY_A, $columns_to_select = null)
1097
+	{
1098
+		return $this->_get_all_wpdb_results($query_params, $output, $columns_to_select);
1099
+	}
1100
+
1101
+
1102
+
1103
+	/**
1104
+	 * For creating a custom select statement
1105
+	 *
1106
+	 * @param mixed $columns_to_select either a string to be inserted directly as the select statement,
1107
+	 *                                 or an array where keys are aliases, and values are arrays where 0=>the selection
1108
+	 *                                 SQL, and 1=>is the datatype
1109
+	 * @throws EE_Error
1110
+	 * @return string
1111
+	 */
1112
+	private function _construct_select_from_input($columns_to_select)
1113
+	{
1114
+		if (is_array($columns_to_select)) {
1115
+			$select_sql_array = array();
1116
+			foreach ($columns_to_select as $alias => $selection_and_datatype) {
1117
+				if (! is_array($selection_and_datatype) || ! isset($selection_and_datatype[1])) {
1118
+					throw new EE_Error(
1119
+						sprintf(
1120
+							esc_html__(
1121
+								"Custom selection %s (alias %s) needs to be an array like array('COUNT(REG_ID)','%%d')",
1122
+								'event_espresso'
1123
+							),
1124
+							$selection_and_datatype,
1125
+							$alias
1126
+						)
1127
+					);
1128
+				}
1129
+				if (! in_array($selection_and_datatype[1], $this->_valid_wpdb_data_types, true)) {
1130
+					throw new EE_Error(
1131
+						sprintf(
1132
+							esc_html__(
1133
+								"Datatype %s (for selection '%s' and alias '%s') is not a valid wpdb datatype (eg %%s)",
1134
+								'event_espresso'
1135
+							),
1136
+							$selection_and_datatype[1],
1137
+							$selection_and_datatype[0],
1138
+							$alias,
1139
+							implode(', ', $this->_valid_wpdb_data_types)
1140
+						)
1141
+					);
1142
+				}
1143
+				$select_sql_array[] = "{$selection_and_datatype[0]} AS $alias";
1144
+			}
1145
+			$columns_to_select_string = implode(', ', $select_sql_array);
1146
+		} else {
1147
+			$columns_to_select_string = $columns_to_select;
1148
+		}
1149
+		return $columns_to_select_string;
1150
+	}
1151
+
1152
+
1153
+
1154
+	/**
1155
+	 * Convenient wrapper for getting the primary key field's name. Eg, on Registration, this would be 'REG_ID'
1156
+	 *
1157
+	 * @return string
1158
+	 * @throws EE_Error
1159
+	 */
1160
+	public function primary_key_name()
1161
+	{
1162
+		return $this->get_primary_key_field()->get_name();
1163
+	}
1164
+
1165
+
1166
+	/**
1167
+	 * Gets a single item for this model from the DB, given only its ID (or null if none is found).
1168
+	 * If there is no primary key on this model, $id is treated as primary key string
1169
+	 *
1170
+	 * @param mixed $id int or string, depending on the type of the model's primary key
1171
+	 * @return EE_Base_Class|mixed|null
1172
+	 * @throws EE_Error
1173
+	 */
1174
+	public function get_one_by_ID($id)
1175
+	{
1176
+		if ($this->get_from_entity_map($id)) {
1177
+			return $this->get_from_entity_map($id);
1178
+		}
1179
+		$model_object = $this->get_one(
1180
+			$this->alter_query_params_to_restrict_by_ID(
1181
+				$id,
1182
+				array('default_where_conditions' => EEM_Base::default_where_conditions_minimum_all)
1183
+			)
1184
+		);
1185
+		$className = $this->_get_class_name();
1186
+		if ($model_object instanceof $className) {
1187
+			// make sure valid objects get added to the entity map
1188
+			// so that the next call to this method doesn't trigger another trip to the db
1189
+			$this->add_to_entity_map($model_object);
1190
+		}
1191
+		return $model_object;
1192
+	}
1193
+
1194
+
1195
+
1196
+	/**
1197
+	 * Alters query parameters to only get items with this ID are returned.
1198
+	 * Takes into account that the ID might be a string produced by EEM_Base::get_index_primary_key_string(),
1199
+	 * or could just be a simple primary key ID
1200
+	 *
1201
+	 * @param int   $id
1202
+	 * @param array $query_params
1203
+	 * @return array of normal query params, @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1204
+	 * @throws EE_Error
1205
+	 */
1206
+	public function alter_query_params_to_restrict_by_ID($id, $query_params = array())
1207
+	{
1208
+		if (! isset($query_params[0])) {
1209
+			$query_params[0] = array();
1210
+		}
1211
+		$conditions_from_id = $this->parse_index_primary_key_string($id);
1212
+		if ($conditions_from_id === null) {
1213
+			$query_params[0][ $this->primary_key_name() ] = $id;
1214
+		} else {
1215
+			// no primary key, so the $id must be from the get_index_primary_key_string()
1216
+			$query_params[0] = array_replace_recursive($query_params[0], $this->parse_index_primary_key_string($id));
1217
+		}
1218
+		return $query_params;
1219
+	}
1220
+
1221
+
1222
+
1223
+	/**
1224
+	 * Gets a single item for this model from the DB, given the $query_params. Only returns a single class, not an
1225
+	 * array. If no item is found, null is returned.
1226
+	 *
1227
+	 * @param array $query_params like EEM_Base's $query_params variable.
1228
+	 * @return EE_Base_Class|EE_Soft_Delete_Base_Class|NULL
1229
+	 * @throws EE_Error
1230
+	 */
1231
+	public function get_one($query_params = array())
1232
+	{
1233
+		if (! is_array($query_params)) {
1234
+			EE_Error::doing_it_wrong(
1235
+				'EEM_Base::get_one',
1236
+				sprintf(
1237
+					esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1238
+					gettype($query_params)
1239
+				),
1240
+				'4.6.0'
1241
+			);
1242
+			$query_params = array();
1243
+		}
1244
+		$query_params['limit'] = 1;
1245
+		$items = $this->get_all($query_params);
1246
+		if (empty($items)) {
1247
+			return null;
1248
+		}
1249
+		return array_shift($items);
1250
+	}
1251
+
1252
+
1253
+
1254
+	/**
1255
+	 * Returns the next x number of items in sequence from the given value as
1256
+	 * found in the database matching the given query conditions.
1257
+	 *
1258
+	 * @param mixed $current_field_value    Value used for the reference point.
1259
+	 * @param null  $field_to_order_by      What field is used for the
1260
+	 *                                      reference point.
1261
+	 * @param int   $limit                  How many to return.
1262
+	 * @param array $query_params           Extra conditions on the query.
1263
+	 * @param null  $columns_to_select      If left null, then an array of
1264
+	 *                                      EE_Base_Class objects is returned,
1265
+	 *                                      otherwise you can indicate just the
1266
+	 *                                      columns you want returned.
1267
+	 * @return EE_Base_Class[]|array
1268
+	 * @throws EE_Error
1269
+	 */
1270
+	public function next_x(
1271
+		$current_field_value,
1272
+		$field_to_order_by = null,
1273
+		$limit = 1,
1274
+		$query_params = array(),
1275
+		$columns_to_select = null
1276
+	) {
1277
+		return $this->_get_consecutive(
1278
+			$current_field_value,
1279
+			'>',
1280
+			$field_to_order_by,
1281
+			$limit,
1282
+			$query_params,
1283
+			$columns_to_select
1284
+		);
1285
+	}
1286
+
1287
+
1288
+
1289
+	/**
1290
+	 * Returns the previous x number of items in sequence from the given value
1291
+	 * as found in the database matching the given query conditions.
1292
+	 *
1293
+	 * @param mixed $current_field_value    Value used for the reference point.
1294
+	 * @param null  $field_to_order_by      What field is used for the
1295
+	 *                                      reference point.
1296
+	 * @param int   $limit                  How many to return.
1297
+	 * @param array $query_params           Extra conditions on the query.
1298
+	 * @param null  $columns_to_select      If left null, then an array of
1299
+	 *                                      EE_Base_Class objects is returned,
1300
+	 *                                      otherwise you can indicate just the
1301
+	 *                                      columns you want returned.
1302
+	 * @return EE_Base_Class[]|array
1303
+	 * @throws EE_Error
1304
+	 */
1305
+	public function previous_x(
1306
+		$current_field_value,
1307
+		$field_to_order_by = null,
1308
+		$limit = 1,
1309
+		$query_params = array(),
1310
+		$columns_to_select = null
1311
+	) {
1312
+		return $this->_get_consecutive(
1313
+			$current_field_value,
1314
+			'<',
1315
+			$field_to_order_by,
1316
+			$limit,
1317
+			$query_params,
1318
+			$columns_to_select
1319
+		);
1320
+	}
1321
+
1322
+
1323
+
1324
+	/**
1325
+	 * Returns the next item in sequence from the given value as found in the
1326
+	 * database matching the given query conditions.
1327
+	 *
1328
+	 * @param mixed $current_field_value    Value used for the reference point.
1329
+	 * @param null  $field_to_order_by      What field is used for the
1330
+	 *                                      reference point.
1331
+	 * @param array $query_params           Extra conditions on the query.
1332
+	 * @param null  $columns_to_select      If left null, then an EE_Base_Class
1333
+	 *                                      object is returned, otherwise you
1334
+	 *                                      can indicate just the columns you
1335
+	 *                                      want and a single array indexed by
1336
+	 *                                      the columns will be returned.
1337
+	 * @return EE_Base_Class|null|array()
1338
+	 * @throws EE_Error
1339
+	 */
1340
+	public function next(
1341
+		$current_field_value,
1342
+		$field_to_order_by = null,
1343
+		$query_params = array(),
1344
+		$columns_to_select = null
1345
+	) {
1346
+		$results = $this->_get_consecutive(
1347
+			$current_field_value,
1348
+			'>',
1349
+			$field_to_order_by,
1350
+			1,
1351
+			$query_params,
1352
+			$columns_to_select
1353
+		);
1354
+		return empty($results) ? null : reset($results);
1355
+	}
1356
+
1357
+
1358
+
1359
+	/**
1360
+	 * Returns the previous item in sequence from the given value as found in
1361
+	 * the database matching the given query conditions.
1362
+	 *
1363
+	 * @param mixed $current_field_value    Value used for the reference point.
1364
+	 * @param null  $field_to_order_by      What field is used for the
1365
+	 *                                      reference point.
1366
+	 * @param array $query_params           Extra conditions on the query.
1367
+	 * @param null  $columns_to_select      If left null, then an EE_Base_Class
1368
+	 *                                      object is returned, otherwise you
1369
+	 *                                      can indicate just the columns you
1370
+	 *                                      want and a single array indexed by
1371
+	 *                                      the columns will be returned.
1372
+	 * @return EE_Base_Class|null|array()
1373
+	 * @throws EE_Error
1374
+	 */
1375
+	public function previous(
1376
+		$current_field_value,
1377
+		$field_to_order_by = null,
1378
+		$query_params = array(),
1379
+		$columns_to_select = null
1380
+	) {
1381
+		$results = $this->_get_consecutive(
1382
+			$current_field_value,
1383
+			'<',
1384
+			$field_to_order_by,
1385
+			1,
1386
+			$query_params,
1387
+			$columns_to_select
1388
+		);
1389
+		return empty($results) ? null : reset($results);
1390
+	}
1391
+
1392
+
1393
+
1394
+	/**
1395
+	 * Returns the a consecutive number of items in sequence from the given
1396
+	 * value as found in the database matching the given query conditions.
1397
+	 *
1398
+	 * @param mixed  $current_field_value   Value used for the reference point.
1399
+	 * @param string $operand               What operand is used for the sequence.
1400
+	 * @param string $field_to_order_by     What field is used for the reference point.
1401
+	 * @param int    $limit                 How many to return.
1402
+	 * @param array  $query_params          Extra conditions on the query.
1403
+	 * @param null   $columns_to_select     If left null, then an array of EE_Base_Class objects is returned,
1404
+	 *                                      otherwise you can indicate just the columns you want returned.
1405
+	 * @return EE_Base_Class[]|array
1406
+	 * @throws EE_Error
1407
+	 */
1408
+	protected function _get_consecutive(
1409
+		$current_field_value,
1410
+		$operand = '>',
1411
+		$field_to_order_by = null,
1412
+		$limit = 1,
1413
+		$query_params = array(),
1414
+		$columns_to_select = null
1415
+	) {
1416
+		// if $field_to_order_by is empty then let's assume we're ordering by the primary key.
1417
+		if (empty($field_to_order_by)) {
1418
+			if ($this->has_primary_key_field()) {
1419
+				$field_to_order_by = $this->get_primary_key_field()->get_name();
1420
+			} else {
1421
+				if (WP_DEBUG) {
1422
+					throw new EE_Error(esc_html__(
1423
+						'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).',
1424
+						'event_espresso'
1425
+					));
1426
+				}
1427
+				EE_Error::add_error(esc_html__('There was an error with the query.', 'event_espresso'));
1428
+				return array();
1429
+			}
1430
+		}
1431
+		if (! is_array($query_params)) {
1432
+			EE_Error::doing_it_wrong(
1433
+				'EEM_Base::_get_consecutive',
1434
+				sprintf(
1435
+					esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1436
+					gettype($query_params)
1437
+				),
1438
+				'4.6.0'
1439
+			);
1440
+			$query_params = array();
1441
+		}
1442
+		// let's add the where query param for consecutive look up.
1443
+		$query_params[0][ $field_to_order_by ] = array($operand, $current_field_value);
1444
+		$query_params['limit'] = $limit;
1445
+		// set direction
1446
+		$incoming_orderby = isset($query_params['order_by']) ? (array) $query_params['order_by'] : array();
1447
+		$query_params['order_by'] = $operand === '>'
1448
+			? array($field_to_order_by => 'ASC') + $incoming_orderby
1449
+			: array($field_to_order_by => 'DESC') + $incoming_orderby;
1450
+		// if $columns_to_select is empty then that means we're returning EE_Base_Class objects
1451
+		if (empty($columns_to_select)) {
1452
+			return $this->get_all($query_params);
1453
+		}
1454
+		// getting just the fields
1455
+		return $this->_get_all_wpdb_results($query_params, ARRAY_A, $columns_to_select);
1456
+	}
1457
+
1458
+
1459
+
1460
+	/**
1461
+	 * This sets the _timezone property after model object has been instantiated.
1462
+	 *
1463
+	 * @param null | string $timezone valid PHP DateTimeZone timezone string
1464
+	 */
1465
+	public function set_timezone($timezone)
1466
+	{
1467
+		if ($timezone !== null) {
1468
+			$this->_timezone = $timezone;
1469
+		}
1470
+		// note we need to loop through relations and set the timezone on those objects as well.
1471
+		foreach ($this->_model_relations as $relation) {
1472
+			$relation->set_timezone($timezone);
1473
+		}
1474
+		// and finally we do the same for any datetime fields
1475
+		foreach ($this->_fields as $field) {
1476
+			if ($field instanceof EE_Datetime_Field) {
1477
+				$field->set_timezone($timezone);
1478
+			}
1479
+		}
1480
+	}
1481
+
1482
+
1483
+
1484
+	/**
1485
+	 * This just returns whatever is set for the current timezone.
1486
+	 *
1487
+	 * @access public
1488
+	 * @return string
1489
+	 */
1490
+	public function get_timezone()
1491
+	{
1492
+		// first validate if timezone is set.  If not, then let's set it be whatever is set on the model fields.
1493
+		if (empty($this->_timezone)) {
1494
+			foreach ($this->_fields as $field) {
1495
+				if ($field instanceof EE_Datetime_Field) {
1496
+					$this->set_timezone($field->get_timezone());
1497
+					break;
1498
+				}
1499
+			}
1500
+		}
1501
+		// if timezone STILL empty then return the default timezone for the site.
1502
+		if (empty($this->_timezone)) {
1503
+			$this->set_timezone(EEH_DTT_Helper::get_timezone());
1504
+		}
1505
+		return $this->_timezone;
1506
+	}
1507
+
1508
+
1509
+
1510
+	/**
1511
+	 * This returns the date formats set for the given field name and also ensures that
1512
+	 * $this->_timezone property is set correctly.
1513
+	 *
1514
+	 * @since 4.6.x
1515
+	 * @param string $field_name The name of the field the formats are being retrieved for.
1516
+	 * @param bool   $pretty     Whether to return the pretty formats (true) or not (false).
1517
+	 * @throws EE_Error   If the given field_name is not of the EE_Datetime_Field type.
1518
+	 * @return array formats in an array with the date format first, and the time format last.
1519
+	 */
1520
+	public function get_formats_for($field_name, $pretty = false)
1521
+	{
1522
+		$field_settings = $this->field_settings_for($field_name);
1523
+		// if not a valid EE_Datetime_Field then throw error
1524
+		if (! $field_settings instanceof EE_Datetime_Field) {
1525
+			throw new EE_Error(sprintf(esc_html__(
1526
+				'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.',
1527
+				'event_espresso'
1528
+			), $field_name));
1529
+		}
1530
+		// while we are here, let's make sure the timezone internally in EEM_Base matches what is stored on
1531
+		// the field.
1532
+		$this->_timezone = $field_settings->get_timezone();
1533
+		return array($field_settings->get_date_format($pretty), $field_settings->get_time_format($pretty));
1534
+	}
1535
+
1536
+
1537
+
1538
+	/**
1539
+	 * This returns the current time in a format setup for a query on this model.
1540
+	 * Usage of this method makes it easier to setup queries against EE_Datetime_Field columns because
1541
+	 * it will return:
1542
+	 *  - a formatted string in the timezone and format currently set on the EE_Datetime_Field for the given field for
1543
+	 *  NOW
1544
+	 *  - or a unix timestamp (equivalent to time())
1545
+	 * Note: When requesting a formatted string, if the date or time format doesn't include seconds, for example,
1546
+	 * the time returned, because it uses that format, will also NOT include seconds. For this reason, if you want
1547
+	 * the time returned to be the current time down to the exact second, set $timestamp to true.
1548
+	 * @since 4.6.x
1549
+	 * @param string $field_name       The field the current time is needed for.
1550
+	 * @param bool   $timestamp        True means to return a unix timestamp. Otherwise a
1551
+	 *                                 formatted string matching the set format for the field in the set timezone will
1552
+	 *                                 be returned.
1553
+	 * @param string $what             Whether to return the string in just the time format, the date format, or both.
1554
+	 * @throws EE_Error    If the given field_name is not of the EE_Datetime_Field type.
1555
+	 * @return int|string  If the given field_name is not of the EE_Datetime_Field type, then an EE_Error
1556
+	 *                                 exception is triggered.
1557
+	 */
1558
+	public function current_time_for_query($field_name, $timestamp = false, $what = 'both')
1559
+	{
1560
+		$formats = $this->get_formats_for($field_name);
1561
+		$DateTime = new DateTime("now", new DateTimeZone($this->_timezone));
1562
+		if ($timestamp) {
1563
+			return $DateTime->format('U');
1564
+		}
1565
+		// not returning timestamp, so return formatted string in timezone.
1566
+		switch ($what) {
1567
+			case 'time':
1568
+				return $DateTime->format($formats[1]);
1569
+				break;
1570
+			case 'date':
1571
+				return $DateTime->format($formats[0]);
1572
+				break;
1573
+			default:
1574
+				return $DateTime->format(implode(' ', $formats));
1575
+				break;
1576
+		}
1577
+	}
1578
+
1579
+
1580
+
1581
+	/**
1582
+	 * This receives a time string for a given field and ensures that it is setup to match what the internal settings
1583
+	 * for the model are.  Returns a DateTime object.
1584
+	 * Note: a gotcha for when you send in unix timestamp.  Remember a unix timestamp is already timezone agnostic,
1585
+	 * (functionally the equivalent of UTC+0).  So when you send it in, whatever timezone string you include is
1586
+	 * ignored.
1587
+	 *
1588
+	 * @param string $field_name      The field being setup.
1589
+	 * @param string $timestring      The date time string being used.
1590
+	 * @param string $incoming_format The format for the time string.
1591
+	 * @param string $timezone        By default, it is assumed the incoming time string is in timezone for
1592
+	 *                                the blog.  If this is not the case, then it can be specified here.  If incoming
1593
+	 *                                format is
1594
+	 *                                'U', this is ignored.
1595
+	 * @return DateTime
1596
+	 * @throws EE_Error
1597
+	 */
1598
+	public function convert_datetime_for_query($field_name, $timestring, $incoming_format, $timezone = '')
1599
+	{
1600
+		// just using this to ensure the timezone is set correctly internally
1601
+		$this->get_formats_for($field_name);
1602
+		// load EEH_DTT_Helper
1603
+		$set_timezone = empty($timezone) ? EEH_DTT_Helper::get_timezone() : $timezone;
1604
+		$incomingDateTime = date_create_from_format($incoming_format, $timestring, new DateTimeZone($set_timezone));
1605
+		EEH_DTT_Helper::setTimezone($incomingDateTime, new DateTimeZone($this->_timezone));
1606
+		return \EventEspresso\core\domain\entities\DbSafeDateTime::createFromDateTime($incomingDateTime);
1607
+	}
1608
+
1609
+
1610
+
1611
+	/**
1612
+	 * Gets all the tables comprising this model. Array keys are the table aliases, and values are EE_Table objects
1613
+	 *
1614
+	 * @return EE_Table_Base[]
1615
+	 */
1616
+	public function get_tables()
1617
+	{
1618
+		return $this->_tables;
1619
+	}
1620
+
1621
+
1622
+
1623
+	/**
1624
+	 * Updates all the database entries (in each table for this model) according to $fields_n_values and optionally
1625
+	 * also updates all the model objects, where the criteria expressed in $query_params are met..
1626
+	 * Also note: if this model has multiple tables, this update verifies all the secondary tables have an entry for
1627
+	 * each row (in the primary table) we're trying to update; if not, it inserts an entry in the secondary table. Eg:
1628
+	 * if our model has 2 tables: wp_posts (primary), and wp_esp_event (secondary). Let's say we are trying to update a
1629
+	 * model object with EVT_ID = 1
1630
+	 * (which means where wp_posts has ID = 1, because wp_posts.ID is the primary key's column), which exists, but
1631
+	 * there is no entry in wp_esp_event for this entry in wp_posts. So, this update script will insert a row into
1632
+	 * wp_esp_event, using any available parameters from $fields_n_values (eg, if "EVT_limit" => 40 is in
1633
+	 * $fields_n_values, the new entry in wp_esp_event will set EVT_limit = 40, and use default for other columns which
1634
+	 * are not specified)
1635
+	 *
1636
+	 * @param array   $fields_n_values         keys are model fields (exactly like keys in EEM_Base::_fields, NOT db
1637
+	 *                                         columns!), values are strings, ints, floats, and maybe arrays if they
1638
+	 *                                         are to be serialized. Basically, the values are what you'd expect to be
1639
+	 *                                         values on the model, NOT necessarily what's in the DB. For example, if
1640
+	 *                                         we wanted to update only the TXN_details on any Transactions where its
1641
+	 *                                         ID=34, we'd use this method as follows:
1642
+	 *                                         EEM_Transaction::instance()->update(
1643
+	 *                                         array('TXN_details'=>array('detail1'=>'monkey','detail2'=>'banana'),
1644
+	 *                                         array(array('TXN_ID'=>34)));
1645
+	 * @param array   $query_params            @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1646
+	 *                                         Eg, consider updating Question's QST_admin_label field is of type
1647
+	 *                                         Simple_HTML. If you use this function to update that field to $new_value
1648
+	 *                                         = (note replace 8's with appropriate opening and closing tags in the
1649
+	 *                                         following example)"8script8alert('I hack all');8/script88b8boom
1650
+	 *                                         baby8/b8", then if you set $values_already_prepared_by_model_object to
1651
+	 *                                         TRUE, it is assumed that you've already called
1652
+	 *                                         EE_Simple_HTML_Field->prepare_for_set($new_value), which removes the
1653
+	 *                                         malicious javascript. However, if
1654
+	 *                                         $values_already_prepared_by_model_object is left as FALSE, then
1655
+	 *                                         EE_Simple_HTML_Field->prepare_for_set($new_value) will be called on it,
1656
+	 *                                         and every other field, before insertion. We provide this parameter
1657
+	 *                                         because model objects perform their prepare_for_set function on all
1658
+	 *                                         their values, and so don't need to be called again (and in many cases,
1659
+	 *                                         shouldn't be called again. Eg: if we escape HTML characters in the
1660
+	 *                                         prepare_for_set method...)
1661
+	 * @param boolean $keep_model_objs_in_sync if TRUE, makes sure we ALSO update model objects
1662
+	 *                                         in this model's entity map according to $fields_n_values that match
1663
+	 *                                         $query_params. This obviously has some overhead, so you can disable it
1664
+	 *                                         by setting this to FALSE, but be aware that model objects being used
1665
+	 *                                         could get out-of-sync with the database
1666
+	 * @return int how many rows got updated or FALSE if something went wrong with the query (wp returns FALSE or num
1667
+	 *                                         rows affected which *could* include 0 which DOES NOT mean the query was
1668
+	 *                                         bad)
1669
+	 * @throws EE_Error
1670
+	 */
1671
+	public function update($fields_n_values, $query_params, $keep_model_objs_in_sync = true)
1672
+	{
1673
+		if (! is_array($query_params)) {
1674
+			EE_Error::doing_it_wrong(
1675
+				'EEM_Base::update',
1676
+				sprintf(
1677
+					esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1678
+					gettype($query_params)
1679
+				),
1680
+				'4.6.0'
1681
+			);
1682
+			$query_params = array();
1683
+		}
1684
+		/**
1685
+		 * Action called before a model update call has been made.
1686
+		 *
1687
+		 * @param EEM_Base $model
1688
+		 * @param array    $fields_n_values the updated fields and their new values
1689
+		 * @param array    $query_params    @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1690
+		 */
1691
+		do_action('AHEE__EEM_Base__update__begin', $this, $fields_n_values, $query_params);
1692
+		/**
1693
+		 * Filters the fields about to be updated given the query parameters. You can provide the
1694
+		 * $query_params to $this->get_all() to find exactly which records will be updated
1695
+		 *
1696
+		 * @param array    $fields_n_values fields and their new values
1697
+		 * @param EEM_Base $model           the model being queried
1698
+		 * @param array    $query_params    @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1699
+		 */
1700
+		$fields_n_values = (array) apply_filters(
1701
+			'FHEE__EEM_Base__update__fields_n_values',
1702
+			$fields_n_values,
1703
+			$this,
1704
+			$query_params
1705
+		);
1706
+		// need to verify that, for any entry we want to update, there are entries in each secondary table.
1707
+		// to do that, for each table, verify that it's PK isn't null.
1708
+		$tables = $this->get_tables();
1709
+		// 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
1710
+		// NOTE: we should make this code more efficient by NOT querying twice
1711
+		// before the real update, but that needs to first go through ALPHA testing
1712
+		// as it's dangerous. says Mike August 8 2014
1713
+		// we want to make sure the default_where strategy is ignored
1714
+		$this->_ignore_where_strategy = true;
1715
+		$wpdb_select_results = $this->_get_all_wpdb_results($query_params);
1716
+		foreach ($wpdb_select_results as $wpdb_result) {
1717
+			// type cast stdClass as array
1718
+			$wpdb_result = (array) $wpdb_result;
1719
+			// get the model object's PK, as we'll want this if we need to insert a row into secondary tables
1720
+			if ($this->has_primary_key_field()) {
1721
+				$main_table_pk_value = $wpdb_result[ $this->get_primary_key_field()->get_qualified_column() ];
1722
+			} else {
1723
+				// 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)
1724
+				$main_table_pk_value = null;
1725
+			}
1726
+			// 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
1727
+			// 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
1728
+			if (count($tables) > 1) {
1729
+				// foreach matching row in the DB, ensure that each table's PK isn't null. If so, there must not be an entry
1730
+				// in that table, and so we'll want to insert one
1731
+				foreach ($tables as $table_obj) {
1732
+					$this_table_pk_column = $table_obj->get_fully_qualified_pk_column();
1733
+					// if there is no private key for this table on the results, it means there's no entry
1734
+					// in this table, right? so insert a row in the current table, using any fields available
1735
+					if (
1736
+						! (array_key_exists($this_table_pk_column, $wpdb_result)
1737
+						   && $wpdb_result[ $this_table_pk_column ])
1738
+					) {
1739
+						$success = $this->_insert_into_specific_table(
1740
+							$table_obj,
1741
+							$fields_n_values,
1742
+							$main_table_pk_value
1743
+						);
1744
+						// if we died here, report the error
1745
+						if (! $success) {
1746
+							return false;
1747
+						}
1748
+					}
1749
+				}
1750
+			}
1751
+			//              //and now check that if we have cached any models by that ID on the model, that
1752
+			//              //they also get updated properly
1753
+			//              $model_object = $this->get_from_entity_map( $main_table_pk_value );
1754
+			//              if( $model_object ){
1755
+			//                  foreach( $fields_n_values as $field => $value ){
1756
+			//                      $model_object->set($field, $value);
1757
+			// let's make sure default_where strategy is followed now
1758
+			$this->_ignore_where_strategy = false;
1759
+		}
1760
+		// if we want to keep model objects in sync, AND
1761
+		// if this wasn't called from a model object (to update itself)
1762
+		// then we want to make sure we keep all the existing
1763
+		// model objects in sync with the db
1764
+		if ($keep_model_objs_in_sync && ! $this->_values_already_prepared_by_model_object) {
1765
+			if ($this->has_primary_key_field()) {
1766
+				$model_objs_affected_ids = $this->get_col($query_params);
1767
+			} else {
1768
+				// we need to select a bunch of columns and then combine them into the the "index primary key string"s
1769
+				$models_affected_key_columns = $this->_get_all_wpdb_results($query_params, ARRAY_A);
1770
+				$model_objs_affected_ids = array();
1771
+				foreach ($models_affected_key_columns as $row) {
1772
+					$combined_index_key = $this->get_index_primary_key_string($row);
1773
+					$model_objs_affected_ids[ $combined_index_key ] = $combined_index_key;
1774
+				}
1775
+			}
1776
+			if (! $model_objs_affected_ids) {
1777
+				// wait wait wait- if nothing was affected let's stop here
1778
+				return 0;
1779
+			}
1780
+			foreach ($model_objs_affected_ids as $id) {
1781
+				$model_obj_in_entity_map = $this->get_from_entity_map($id);
1782
+				if ($model_obj_in_entity_map) {
1783
+					foreach ($fields_n_values as $field => $new_value) {
1784
+						$model_obj_in_entity_map->set($field, $new_value);
1785
+					}
1786
+				}
1787
+			}
1788
+			// if there is a primary key on this model, we can now do a slight optimization
1789
+			if ($this->has_primary_key_field()) {
1790
+				// we already know what we want to update. So let's make the query simpler so it's a little more efficient
1791
+				$query_params = array(
1792
+					array($this->primary_key_name() => array('IN', $model_objs_affected_ids)),
1793
+					'limit'                    => count($model_objs_affected_ids),
1794
+					'default_where_conditions' => EEM_Base::default_where_conditions_none,
1795
+				);
1796
+			}
1797
+		}
1798
+		$model_query_info = $this->_create_model_query_info_carrier($query_params);
1799
+		$SQL = "UPDATE "
1800
+			   . $model_query_info->get_full_join_sql()
1801
+			   . " SET "
1802
+			   . $this->_construct_update_sql($fields_n_values)
1803
+			   . $model_query_info->get_where_sql();// note: doesn't use _construct_2nd_half_of_select_query() because doesn't accept LIMIT, ORDER BY, etc.
1804
+		$rows_affected = $this->_do_wpdb_query('query', array($SQL));
1805
+		/**
1806
+		 * Action called after a model update call has been made.
1807
+		 *
1808
+		 * @param EEM_Base $model
1809
+		 * @param array    $fields_n_values the updated fields and their new values
1810
+		 * @param array    $query_params    @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1811
+		 * @param int      $rows_affected
1812
+		 */
1813
+		do_action('AHEE__EEM_Base__update__end', $this, $fields_n_values, $query_params, $rows_affected);
1814
+		return $rows_affected;// how many supposedly got updated
1815
+	}
1816
+
1817
+
1818
+
1819
+	/**
1820
+	 * Analogous to $wpdb->get_col, returns a 1-dimensional array where teh values
1821
+	 * are teh values of the field specified (or by default the primary key field)
1822
+	 * that matched the query params. Note that you should pass the name of the
1823
+	 * model FIELD, not the database table's column name.
1824
+	 *
1825
+	 * @param array  $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1826
+	 * @param string $field_to_select
1827
+	 * @return array just like $wpdb->get_col()
1828
+	 * @throws EE_Error
1829
+	 */
1830
+	public function get_col($query_params = array(), $field_to_select = null)
1831
+	{
1832
+		if ($field_to_select) {
1833
+			$field = $this->field_settings_for($field_to_select);
1834
+		} elseif ($this->has_primary_key_field()) {
1835
+			$field = $this->get_primary_key_field();
1836
+		} else {
1837
+			// no primary key, just grab the first column
1838
+			$field_settings = $this->field_settings();
1839
+			$field = reset($field_settings);
1840
+		}
1841
+		$model_query_info = $this->_create_model_query_info_carrier($query_params);
1842
+		$select_expressions = $field->get_qualified_column();
1843
+		$SQL = "SELECT $select_expressions " . $this->_construct_2nd_half_of_select_query($model_query_info);
1844
+		return $this->_do_wpdb_query('get_col', array($SQL));
1845
+	}
1846
+
1847
+
1848
+
1849
+	/**
1850
+	 * Returns a single column value for a single row from the database
1851
+	 *
1852
+	 * @param array  $query_params    @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1853
+	 * @param string $field_to_select @see EEM_Base::get_col()
1854
+	 * @return string
1855
+	 * @throws EE_Error
1856
+	 */
1857
+	public function get_var($query_params = array(), $field_to_select = null)
1858
+	{
1859
+		$query_params['limit'] = 1;
1860
+		$col = $this->get_col($query_params, $field_to_select);
1861
+		if (! empty($col)) {
1862
+			return reset($col);
1863
+		}
1864
+		return null;
1865
+	}
1866
+
1867
+
1868
+
1869
+	/**
1870
+	 * Makes the SQL for after "UPDATE table_X inner join table_Y..." and before "...WHERE". Eg "Question.name='party
1871
+	 * time?', Question.desc='what do you think?',..." Values are filtered through wpdb->prepare to avoid against SQL
1872
+	 * injection, but currently no further filtering is done
1873
+	 *
1874
+	 * @global      $wpdb
1875
+	 * @param array $fields_n_values array keys are field names on this model, and values are what those fields should
1876
+	 *                               be updated to in the DB
1877
+	 * @return string of SQL
1878
+	 * @throws EE_Error
1879
+	 */
1880
+	public function _construct_update_sql($fields_n_values)
1881
+	{
1882
+		/** @type WPDB $wpdb */
1883
+		global $wpdb;
1884
+		$cols_n_values = array();
1885
+		foreach ($fields_n_values as $field_name => $value) {
1886
+			$field_obj = $this->field_settings_for($field_name);
1887
+			// if the value is NULL, we want to assign the value to that.
1888
+			// wpdb->prepare doesn't really handle that properly
1889
+			$prepared_value = $this->_prepare_value_or_use_default($field_obj, $fields_n_values);
1890
+			$value_sql = $prepared_value === null ? 'NULL'
1891
+				: $wpdb->prepare($field_obj->get_wpdb_data_type(), $prepared_value);
1892
+			$cols_n_values[] = $field_obj->get_qualified_column() . "=" . $value_sql;
1893
+		}
1894
+		return implode(",", $cols_n_values);
1895
+	}
1896
+
1897
+
1898
+
1899
+	/**
1900
+	 * Deletes a single row from the DB given the model object's primary key value. (eg, EE_Attendee->ID()'s value).
1901
+	 * Performs a HARD delete, meaning the database row should always be removed,
1902
+	 * not just have a flag field on it switched
1903
+	 * Wrapper for EEM_Base::delete_permanently()
1904
+	 *
1905
+	 * @param mixed $id
1906
+	 * @param boolean $allow_blocking
1907
+	 * @return int the number of rows deleted
1908
+	 * @throws EE_Error
1909
+	 */
1910
+	public function delete_permanently_by_ID($id, $allow_blocking = true)
1911
+	{
1912
+		return $this->delete_permanently(
1913
+			array(
1914
+				array($this->get_primary_key_field()->get_name() => $id),
1915
+				'limit' => 1,
1916
+			),
1917
+			$allow_blocking
1918
+		);
1919
+	}
1920
+
1921
+
1922
+
1923
+	/**
1924
+	 * Deletes a single row from the DB given the model object's primary key value. (eg, EE_Attendee->ID()'s value).
1925
+	 * Wrapper for EEM_Base::delete()
1926
+	 *
1927
+	 * @param mixed $id
1928
+	 * @param boolean $allow_blocking
1929
+	 * @return int the number of rows deleted
1930
+	 * @throws EE_Error
1931
+	 */
1932
+	public function delete_by_ID($id, $allow_blocking = true)
1933
+	{
1934
+		return $this->delete(
1935
+			array(
1936
+				array($this->get_primary_key_field()->get_name() => $id),
1937
+				'limit' => 1,
1938
+			),
1939
+			$allow_blocking
1940
+		);
1941
+	}
1942
+
1943
+
1944
+
1945
+	/**
1946
+	 * Identical to delete_permanently, but does a "soft" delete if possible,
1947
+	 * meaning if the model has a field that indicates its been "trashed" or
1948
+	 * "soft deleted", we will just set that instead of actually deleting the rows.
1949
+	 *
1950
+	 * @see EEM_Base::delete_permanently
1951
+	 * @param array   $query_params
1952
+	 * @param boolean $allow_blocking
1953
+	 * @return int how many rows got deleted
1954
+	 * @throws EE_Error
1955
+	 */
1956
+	public function delete($query_params, $allow_blocking = true)
1957
+	{
1958
+		return $this->delete_permanently($query_params, $allow_blocking);
1959
+	}
1960
+
1961
+
1962
+
1963
+	/**
1964
+	 * Deletes the model objects that meet the query params. Note: this method is overridden
1965
+	 * in EEM_Soft_Delete_Base so that soft-deleted model objects are instead only flagged
1966
+	 * as archived, not actually deleted
1967
+	 *
1968
+	 * @param array   $query_params   @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1969
+	 * @param boolean $allow_blocking if TRUE, matched objects will only be deleted if there is no related model info
1970
+	 *                                that blocks it (ie, there' sno other data that depends on this data); if false,
1971
+	 *                                deletes regardless of other objects which may depend on it. Its generally
1972
+	 *                                advisable to always leave this as TRUE, otherwise you could easily corrupt your
1973
+	 *                                DB
1974
+	 * @return int how many rows got deleted
1975
+	 * @throws EE_Error
1976
+	 */
1977
+	public function delete_permanently($query_params, $allow_blocking = true)
1978
+	{
1979
+		/**
1980
+		 * Action called just before performing a real deletion query. You can use the
1981
+		 * model and its $query_params to find exactly which items will be deleted
1982
+		 *
1983
+		 * @param EEM_Base $model
1984
+		 * @param array    $query_params   @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1985
+		 * @param boolean  $allow_blocking whether or not to allow related model objects
1986
+		 *                                 to block (prevent) this deletion
1987
+		 */
1988
+		do_action('AHEE__EEM_Base__delete__begin', $this, $query_params, $allow_blocking);
1989
+		// some MySQL databases may be running safe mode, which may restrict
1990
+		// deletion if there is no KEY column used in the WHERE statement of a deletion.
1991
+		// to get around this, we first do a SELECT, get all the IDs, and then run another query
1992
+		// to delete them
1993
+		$items_for_deletion = $this->_get_all_wpdb_results($query_params);
1994
+		$columns_and_ids_for_deleting = $this->_get_ids_for_delete($items_for_deletion, $allow_blocking);
1995
+		$deletion_where_query_part = $this->_build_query_part_for_deleting_from_columns_and_values(
1996
+			$columns_and_ids_for_deleting
1997
+		);
1998
+		/**
1999
+		 * Allows client code to act on the items being deleted before the query is actually executed.
2000
+		 *
2001
+		 * @param EEM_Base $this  The model instance being acted on.
2002
+		 * @param array    $query_params  The incoming array of query parameters influencing what gets deleted.
2003
+		 * @param bool     $allow_blocking @see param description in method phpdoc block.
2004
+		 * @param array $columns_and_ids_for_deleting       An array indicating what entities will get removed as
2005
+		 *                                                  derived from the incoming query parameters.
2006
+		 *                                                  @see details on the structure of this array in the phpdocs
2007
+		 *                                                  for the `_get_ids_for_delete_method`
2008
+		 *
2009
+		 */
2010
+		do_action(
2011
+			'AHEE__EEM_Base__delete__before_query',
2012
+			$this,
2013
+			$query_params,
2014
+			$allow_blocking,
2015
+			$columns_and_ids_for_deleting
2016
+		);
2017
+		if ($deletion_where_query_part) {
2018
+			$model_query_info = $this->_create_model_query_info_carrier($query_params);
2019
+			$table_aliases = array_keys($this->_tables);
2020
+			$SQL = "DELETE "
2021
+				   . implode(", ", $table_aliases)
2022
+				   . " FROM "
2023
+				   . $model_query_info->get_full_join_sql()
2024
+				   . " WHERE "
2025
+				   . $deletion_where_query_part;
2026
+			$rows_deleted = $this->_do_wpdb_query('query', array($SQL));
2027
+		} else {
2028
+			$rows_deleted = 0;
2029
+		}
2030
+
2031
+		// Next, make sure those items are removed from the entity map; if they could be put into it at all; and if
2032
+		// there was no error with the delete query.
2033
+		if (
2034
+			$this->has_primary_key_field()
2035
+			&& $rows_deleted !== false
2036
+			&& isset($columns_and_ids_for_deleting[ $this->get_primary_key_field()->get_qualified_column() ])
2037
+		) {
2038
+			$ids_for_removal = $columns_and_ids_for_deleting[ $this->get_primary_key_field()->get_qualified_column() ];
2039
+			foreach ($ids_for_removal as $id) {
2040
+				if (isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])) {
2041
+					unset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ]);
2042
+				}
2043
+			}
2044
+
2045
+			// delete any extra meta attached to the deleted entities but ONLY if this model is not an instance of
2046
+			// `EEM_Extra_Meta`.  In other words we want to prevent recursion on EEM_Extra_Meta::delete_permanently calls
2047
+			// unnecessarily.  It's very unlikely that users will have assigned Extra Meta to Extra Meta
2048
+			// (although it is possible).
2049
+			// Note this can be skipped by using the provided filter and returning false.
2050
+			if (
2051
+				apply_filters(
2052
+					'FHEE__EEM_Base__delete_permanently__dont_delete_extra_meta_for_extra_meta',
2053
+					! $this instanceof EEM_Extra_Meta,
2054
+					$this
2055
+				)
2056
+			) {
2057
+				EEM_Extra_Meta::instance()->delete_permanently(array(
2058
+					0 => array(
2059
+						'EXM_type' => $this->get_this_model_name(),
2060
+						'OBJ_ID'   => array(
2061
+							'IN',
2062
+							$ids_for_removal
2063
+						)
2064
+					)
2065
+				));
2066
+			}
2067
+		}
2068
+
2069
+		/**
2070
+		 * Action called just after performing a real deletion query. Although at this point the
2071
+		 * items should have been deleted
2072
+		 *
2073
+		 * @param EEM_Base $model
2074
+		 * @param array    $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2075
+		 * @param int      $rows_deleted
2076
+		 */
2077
+		do_action('AHEE__EEM_Base__delete__end', $this, $query_params, $rows_deleted, $columns_and_ids_for_deleting);
2078
+		return $rows_deleted;// how many supposedly got deleted
2079
+	}
2080
+
2081
+
2082
+
2083
+	/**
2084
+	 * Checks all the relations that throw error messages when there are blocking related objects
2085
+	 * for related model objects. If there are any related model objects on those relations,
2086
+	 * adds an EE_Error, and return true
2087
+	 *
2088
+	 * @param EE_Base_Class|int $this_model_obj_or_id
2089
+	 * @param EE_Base_Class     $ignore_this_model_obj a model object like 'EE_Event', or 'EE_Term_Taxonomy', which
2090
+	 *                                                 should be ignored when determining whether there are related
2091
+	 *                                                 model objects which block this model object's deletion. Useful
2092
+	 *                                                 if you know A is related to B and are considering deleting A,
2093
+	 *                                                 but want to see if A has any other objects blocking its deletion
2094
+	 *                                                 before removing the relation between A and B
2095
+	 * @return boolean
2096
+	 * @throws EE_Error
2097
+	 */
2098
+	public function delete_is_blocked_by_related_models($this_model_obj_or_id, $ignore_this_model_obj = null)
2099
+	{
2100
+		// first, if $ignore_this_model_obj was supplied, get its model
2101
+		if ($ignore_this_model_obj && $ignore_this_model_obj instanceof EE_Base_Class) {
2102
+			$ignored_model = $ignore_this_model_obj->get_model();
2103
+		} else {
2104
+			$ignored_model = null;
2105
+		}
2106
+		// now check all the relations of $this_model_obj_or_id and see if there
2107
+		// are any related model objects blocking it?
2108
+		$is_blocked = false;
2109
+		foreach ($this->_model_relations as $relation_name => $relation_obj) {
2110
+			if ($relation_obj->block_delete_if_related_models_exist()) {
2111
+				// if $ignore_this_model_obj was supplied, then for the query
2112
+				// on that model needs to be told to ignore $ignore_this_model_obj
2113
+				if ($ignored_model && $relation_name === $ignored_model->get_this_model_name()) {
2114
+					$related_model_objects = $relation_obj->get_all_related($this_model_obj_or_id, array(
2115
+						array(
2116
+							$ignored_model->get_primary_key_field()->get_name() => array(
2117
+								'!=',
2118
+								$ignore_this_model_obj->ID(),
2119
+							),
2120
+						),
2121
+					));
2122
+				} else {
2123
+					$related_model_objects = $relation_obj->get_all_related($this_model_obj_or_id);
2124
+				}
2125
+				if ($related_model_objects) {
2126
+					EE_Error::add_error($relation_obj->get_deletion_error_message(), __FILE__, __FUNCTION__, __LINE__);
2127
+					$is_blocked = true;
2128
+				}
2129
+			}
2130
+		}
2131
+		return $is_blocked;
2132
+	}
2133
+
2134
+
2135
+	/**
2136
+	 * Builds the columns and values for items to delete from the incoming $row_results_for_deleting array.
2137
+	 * @param array $row_results_for_deleting
2138
+	 * @param bool  $allow_blocking
2139
+	 * @return array   The shape of this array depends on whether the model `has_primary_key_field` or not.  If the
2140
+	 *                 model DOES have a primary_key_field, then the array will be a simple single dimension array where
2141
+	 *                 the key is the fully qualified primary key column and the value is an array of ids that will be
2142
+	 *                 deleted. Example:
2143
+	 *                      array('Event.EVT_ID' => array( 1,2,3))
2144
+	 *                 If the model DOES NOT have a primary_key_field, then the array will be a two dimensional array
2145
+	 *                 where each element is a group of columns and values that get deleted. Example:
2146
+	 *                      array(
2147
+	 *                          0 => array(
2148
+	 *                              'Term_Relationship.object_id' => 1
2149
+	 *                              'Term_Relationship.term_taxonomy_id' => 5
2150
+	 *                          ),
2151
+	 *                          1 => array(
2152
+	 *                              'Term_Relationship.object_id' => 1
2153
+	 *                              'Term_Relationship.term_taxonomy_id' => 6
2154
+	 *                          )
2155
+	 *                      )
2156
+	 * @throws EE_Error
2157
+	 */
2158
+	protected function _get_ids_for_delete(array $row_results_for_deleting, $allow_blocking = true)
2159
+	{
2160
+		$ids_to_delete_indexed_by_column = array();
2161
+		if ($this->has_primary_key_field()) {
2162
+			$primary_table = $this->_get_main_table();
2163
+			$primary_table_pk_field = $this->get_field_by_column($primary_table->get_fully_qualified_pk_column());
2164
+			$other_tables = $this->_get_other_tables();
2165
+			$ids_to_delete_indexed_by_column = $query = array();
2166
+			foreach ($row_results_for_deleting as $item_to_delete) {
2167
+				// before we mark this item for deletion,
2168
+				// make sure there's no related entities blocking its deletion (if we're checking)
2169
+				if (
2170
+					$allow_blocking
2171
+					&& $this->delete_is_blocked_by_related_models(
2172
+						$item_to_delete[ $primary_table->get_fully_qualified_pk_column() ]
2173
+					)
2174
+				) {
2175
+					continue;
2176
+				}
2177
+				// primary table deletes
2178
+				if (isset($item_to_delete[ $primary_table->get_fully_qualified_pk_column() ])) {
2179
+					$ids_to_delete_indexed_by_column[ $primary_table->get_fully_qualified_pk_column() ][] =
2180
+						$item_to_delete[ $primary_table->get_fully_qualified_pk_column() ];
2181
+				}
2182
+			}
2183
+		} elseif (count($this->get_combined_primary_key_fields()) > 1) {
2184
+			$fields = $this->get_combined_primary_key_fields();
2185
+			foreach ($row_results_for_deleting as $item_to_delete) {
2186
+				$ids_to_delete_indexed_by_column_for_row = array();
2187
+				foreach ($fields as $cpk_field) {
2188
+					if ($cpk_field instanceof EE_Model_Field_Base) {
2189
+						$ids_to_delete_indexed_by_column_for_row[ $cpk_field->get_qualified_column() ] =
2190
+							$item_to_delete[ $cpk_field->get_qualified_column() ];
2191
+					}
2192
+				}
2193
+				$ids_to_delete_indexed_by_column[] = $ids_to_delete_indexed_by_column_for_row;
2194
+			}
2195
+		} else {
2196
+			// so there's no primary key and no combined key...
2197
+			// sorry, can't help you
2198
+			throw new EE_Error(
2199
+				sprintf(
2200
+					esc_html__(
2201
+						"Cannot delete objects of type %s because there is no primary key NOR combined key",
2202
+						"event_espresso"
2203
+					),
2204
+					get_class($this)
2205
+				)
2206
+			);
2207
+		}
2208
+		return $ids_to_delete_indexed_by_column;
2209
+	}
2210
+
2211
+
2212
+	/**
2213
+	 * This receives an array of columns and values set to be deleted (as prepared by _get_ids_for_delete) and prepares
2214
+	 * the corresponding query_part for the query performing the delete.
2215
+	 *
2216
+	 * @param array $ids_to_delete_indexed_by_column @see _get_ids_for_delete for how this array might be shaped.
2217
+	 * @return string
2218
+	 * @throws EE_Error
2219
+	 */
2220
+	protected function _build_query_part_for_deleting_from_columns_and_values(array $ids_to_delete_indexed_by_column)
2221
+	{
2222
+		$query_part = '';
2223
+		if (empty($ids_to_delete_indexed_by_column)) {
2224
+			return $query_part;
2225
+		} elseif ($this->has_primary_key_field()) {
2226
+			$query = array();
2227
+			foreach ($ids_to_delete_indexed_by_column as $column => $ids) {
2228
+				$query[] = $column . ' IN' . $this->_construct_in_value($ids, $this->_primary_key_field);
2229
+			}
2230
+			$query_part = ! empty($query) ? implode(' AND ', $query) : $query_part;
2231
+		} elseif (count($this->get_combined_primary_key_fields()) > 1) {
2232
+			$ways_to_identify_a_row = array();
2233
+			foreach ($ids_to_delete_indexed_by_column as $ids_to_delete_indexed_by_column_for_each_row) {
2234
+				$values_for_each_combined_primary_key_for_a_row = array();
2235
+				foreach ($ids_to_delete_indexed_by_column_for_each_row as $column => $id) {
2236
+					$values_for_each_combined_primary_key_for_a_row[] = $column . '=' . $id;
2237
+				}
2238
+				$ways_to_identify_a_row[] = '('
2239
+											. implode(' AND ', $values_for_each_combined_primary_key_for_a_row)
2240
+											. ')';
2241
+			}
2242
+			$query_part = implode(' OR ', $ways_to_identify_a_row);
2243
+		}
2244
+		return $query_part;
2245
+	}
2246
+
2247
+
2248
+
2249
+	/**
2250
+	 * Gets the model field by the fully qualified name
2251
+	 * @param string $qualified_column_name eg 'Event_CPT.post_name' or $field_obj->get_qualified_column()
2252
+	 * @return EE_Model_Field_Base
2253
+	 */
2254
+	public function get_field_by_column($qualified_column_name)
2255
+	{
2256
+		foreach ($this->field_settings(true) as $field_name => $field_obj) {
2257
+			if ($field_obj->get_qualified_column() === $qualified_column_name) {
2258
+				return $field_obj;
2259
+			}
2260
+		}
2261
+		throw new EE_Error(
2262
+			sprintf(
2263
+				esc_html__('Could not find a field on the model "%1$s" for qualified column "%2$s"', 'event_espresso'),
2264
+				$this->get_this_model_name(),
2265
+				$qualified_column_name
2266
+			)
2267
+		);
2268
+	}
2269
+
2270
+
2271
+
2272
+	/**
2273
+	 * Count all the rows that match criteria the model query params.
2274
+	 * If $field_to_count isn't provided, the model's primary key is used. Otherwise, we count by field_to_count's
2275
+	 * column
2276
+	 *
2277
+	 * @param array  $query_params   @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2278
+	 * @param string $field_to_count field on model to count by (not column name)
2279
+	 * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2280
+	 *                               that by the setting $distinct to TRUE;
2281
+	 * @return int
2282
+	 * @throws EE_Error
2283
+	 */
2284
+	public function count($query_params = array(), $field_to_count = null, $distinct = false)
2285
+	{
2286
+		$model_query_info = $this->_create_model_query_info_carrier($query_params);
2287
+		if ($field_to_count) {
2288
+			$field_obj = $this->field_settings_for($field_to_count);
2289
+			$column_to_count = $field_obj->get_qualified_column();
2290
+		} elseif ($this->has_primary_key_field()) {
2291
+			$pk_field_obj = $this->get_primary_key_field();
2292
+			$column_to_count = $pk_field_obj->get_qualified_column();
2293
+		} else {
2294
+			// there's no primary key
2295
+			// if we're counting distinct items, and there's no primary key,
2296
+			// we need to list out the columns for distinction;
2297
+			// otherwise we can just use star
2298
+			if ($distinct) {
2299
+				$columns_to_use = array();
2300
+				foreach ($this->get_combined_primary_key_fields() as $field_obj) {
2301
+					$columns_to_use[] = $field_obj->get_qualified_column();
2302
+				}
2303
+				$column_to_count = implode(',', $columns_to_use);
2304
+			} else {
2305
+				$column_to_count = '*';
2306
+			}
2307
+		}
2308
+		$column_to_count = $distinct ? "DISTINCT " . $column_to_count : $column_to_count;
2309
+		$SQL = "SELECT COUNT(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2310
+		return (int) $this->_do_wpdb_query('get_var', array($SQL));
2311
+	}
2312
+
2313
+
2314
+
2315
+	/**
2316
+	 * Sums up the value of the $field_to_sum (defaults to the primary key, which isn't terribly useful)
2317
+	 *
2318
+	 * @param array  $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2319
+	 * @param string $field_to_sum name of field (array key in $_fields array)
2320
+	 * @return float
2321
+	 * @throws EE_Error
2322
+	 */
2323
+	public function sum($query_params, $field_to_sum = null)
2324
+	{
2325
+		$model_query_info = $this->_create_model_query_info_carrier($query_params);
2326
+		if ($field_to_sum) {
2327
+			$field_obj = $this->field_settings_for($field_to_sum);
2328
+		} else {
2329
+			$field_obj = $this->get_primary_key_field();
2330
+		}
2331
+		$column_to_count = $field_obj->get_qualified_column();
2332
+		$SQL = "SELECT SUM(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2333
+		$return_value = $this->_do_wpdb_query('get_var', array($SQL));
2334
+		$data_type = $field_obj->get_wpdb_data_type();
2335
+		if ($data_type === '%d' || $data_type === '%s') {
2336
+			return (float) $return_value;
2337
+		}
2338
+		// must be %f
2339
+		return (float) $return_value;
2340
+	}
2341
+
2342
+
2343
+
2344
+	/**
2345
+	 * Just calls the specified method on $wpdb with the given arguments
2346
+	 * Consolidates a little extra error handling code
2347
+	 *
2348
+	 * @param string $wpdb_method
2349
+	 * @param array  $arguments_to_provide
2350
+	 * @throws EE_Error
2351
+	 * @global wpdb  $wpdb
2352
+	 * @return mixed
2353
+	 */
2354
+	protected function _do_wpdb_query($wpdb_method, $arguments_to_provide)
2355
+	{
2356
+		// if we're in maintenance mode level 2, DON'T run any queries
2357
+		// because level 2 indicates the database needs updating and
2358
+		// is probably out of sync with the code
2359
+		if (! EE_Maintenance_Mode::instance()->models_can_query()) {
2360
+			throw new EE_Error(sprintf(esc_html__(
2361
+				"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.",
2362
+				"event_espresso"
2363
+			)));
2364
+		}
2365
+		/** @type WPDB $wpdb */
2366
+		global $wpdb;
2367
+		if (! method_exists($wpdb, $wpdb_method)) {
2368
+			throw new EE_Error(sprintf(esc_html__(
2369
+				'There is no method named "%s" on Wordpress\' $wpdb object',
2370
+				'event_espresso'
2371
+			), $wpdb_method));
2372
+		}
2373
+		if (WP_DEBUG) {
2374
+			$old_show_errors_value = $wpdb->show_errors;
2375
+			$wpdb->show_errors(false);
2376
+		}
2377
+		$result = $this->_process_wpdb_query($wpdb_method, $arguments_to_provide);
2378
+		$this->show_db_query_if_previously_requested($wpdb->last_query);
2379
+		if (WP_DEBUG) {
2380
+			$wpdb->show_errors($old_show_errors_value);
2381
+			if (! empty($wpdb->last_error)) {
2382
+				throw new EE_Error(sprintf(esc_html__('WPDB Error: "%s"', 'event_espresso'), $wpdb->last_error));
2383
+			}
2384
+			if ($result === false) {
2385
+				throw new EE_Error(sprintf(esc_html__(
2386
+					'WPDB Error occurred, but no error message was logged by wpdb! The wpdb method called was "%1$s" and the arguments were "%2$s"',
2387
+					'event_espresso'
2388
+				), $wpdb_method, var_export($arguments_to_provide, true)));
2389
+			}
2390
+		} elseif ($result === false) {
2391
+			EE_Error::add_error(
2392
+				sprintf(
2393
+					esc_html__(
2394
+						'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"',
2395
+						'event_espresso'
2396
+					),
2397
+					$wpdb_method,
2398
+					var_export($arguments_to_provide, true),
2399
+					$wpdb->last_error
2400
+				),
2401
+				__FILE__,
2402
+				__FUNCTION__,
2403
+				__LINE__
2404
+			);
2405
+		}
2406
+		return $result;
2407
+	}
2408
+
2409
+
2410
+
2411
+	/**
2412
+	 * Attempts to run the indicated WPDB method with the provided arguments,
2413
+	 * and if there's an error tries to verify the DB is correct. Uses
2414
+	 * the static property EEM_Base::$_db_verification_level to determine whether
2415
+	 * we should try to fix the EE core db, the addons, or just give up
2416
+	 *
2417
+	 * @param string $wpdb_method
2418
+	 * @param array  $arguments_to_provide
2419
+	 * @return mixed
2420
+	 */
2421
+	private function _process_wpdb_query($wpdb_method, $arguments_to_provide)
2422
+	{
2423
+		/** @type WPDB $wpdb */
2424
+		global $wpdb;
2425
+		$wpdb->last_error = null;
2426
+		$result = call_user_func_array(array($wpdb, $wpdb_method), $arguments_to_provide);
2427
+		// was there an error running the query? but we don't care on new activations
2428
+		// (we're going to setup the DB anyway on new activations)
2429
+		if (
2430
+			($result === false || ! empty($wpdb->last_error))
2431
+			&& EE_System::instance()->detect_req_type() !== EE_System::req_type_new_activation
2432
+		) {
2433
+			switch (EEM_Base::$_db_verification_level) {
2434
+				case EEM_Base::db_verified_none:
2435
+					// let's double-check core's DB
2436
+					$error_message = $this->_verify_core_db($wpdb_method, $arguments_to_provide);
2437
+					break;
2438
+				case EEM_Base::db_verified_core:
2439
+					// STILL NO LOVE?? verify all the addons too. Maybe they need to be fixed
2440
+					$error_message = $this->_verify_addons_db($wpdb_method, $arguments_to_provide);
2441
+					break;
2442
+				case EEM_Base::db_verified_addons:
2443
+					// ummmm... you in trouble
2444
+					return $result;
2445
+					break;
2446
+			}
2447
+			if (! empty($error_message)) {
2448
+				EE_Log::instance()->log(__FILE__, __FUNCTION__, $error_message, 'error');
2449
+				trigger_error($error_message);
2450
+			}
2451
+			return $this->_process_wpdb_query($wpdb_method, $arguments_to_provide);
2452
+		}
2453
+		return $result;
2454
+	}
2455
+
2456
+
2457
+
2458
+	/**
2459
+	 * Verifies the EE core database is up-to-date and records that we've done it on
2460
+	 * EEM_Base::$_db_verification_level
2461
+	 *
2462
+	 * @param string $wpdb_method
2463
+	 * @param array  $arguments_to_provide
2464
+	 * @return string
2465
+	 */
2466
+	private function _verify_core_db($wpdb_method, $arguments_to_provide)
2467
+	{
2468
+		/** @type WPDB $wpdb */
2469
+		global $wpdb;
2470
+		// ok remember that we've already attempted fixing the core db, in case the problem persists
2471
+		EEM_Base::$_db_verification_level = EEM_Base::db_verified_core;
2472
+		$error_message = sprintf(
2473
+			esc_html__(
2474
+				'WPDB Error "%1$s" while running wpdb method "%2$s" with arguments %3$s. Automatically attempting to fix EE Core DB',
2475
+				'event_espresso'
2476
+			),
2477
+			$wpdb->last_error,
2478
+			$wpdb_method,
2479
+			wp_json_encode($arguments_to_provide)
2480
+		);
2481
+		EE_System::instance()->initialize_db_if_no_migrations_required(false, true);
2482
+		return $error_message;
2483
+	}
2484
+
2485
+
2486
+
2487
+	/**
2488
+	 * Verifies the EE addons' database is up-to-date and records that we've done it on
2489
+	 * EEM_Base::$_db_verification_level
2490
+	 *
2491
+	 * @param $wpdb_method
2492
+	 * @param $arguments_to_provide
2493
+	 * @return string
2494
+	 */
2495
+	private function _verify_addons_db($wpdb_method, $arguments_to_provide)
2496
+	{
2497
+		/** @type WPDB $wpdb */
2498
+		global $wpdb;
2499
+		// ok remember that we've already attempted fixing the addons dbs, in case the problem persists
2500
+		EEM_Base::$_db_verification_level = EEM_Base::db_verified_addons;
2501
+		$error_message = sprintf(
2502
+			esc_html__(
2503
+				'WPDB AGAIN: Error "%1$s" while running the same method and arguments as before. Automatically attempting to fix EE Addons DB',
2504
+				'event_espresso'
2505
+			),
2506
+			$wpdb->last_error,
2507
+			$wpdb_method,
2508
+			wp_json_encode($arguments_to_provide)
2509
+		);
2510
+		EE_System::instance()->initialize_addons();
2511
+		return $error_message;
2512
+	}
2513
+
2514
+
2515
+
2516
+	/**
2517
+	 * In order to avoid repeating this code for the get_all, sum, and count functions, put the code parts
2518
+	 * that are identical in here. Returns a string of SQL of everything in a SELECT query except the beginning
2519
+	 * SELECT clause, eg " FROM wp_posts AS Event INNER JOIN ... WHERE ... ORDER BY ... LIMIT ... GROUP BY ... HAVING
2520
+	 * ..."
2521
+	 *
2522
+	 * @param EE_Model_Query_Info_Carrier $model_query_info
2523
+	 * @return string
2524
+	 */
2525
+	private function _construct_2nd_half_of_select_query(EE_Model_Query_Info_Carrier $model_query_info)
2526
+	{
2527
+		return " FROM " . $model_query_info->get_full_join_sql() .
2528
+			   $model_query_info->get_where_sql() .
2529
+			   $model_query_info->get_group_by_sql() .
2530
+			   $model_query_info->get_having_sql() .
2531
+			   $model_query_info->get_order_by_sql() .
2532
+			   $model_query_info->get_limit_sql();
2533
+	}
2534
+
2535
+
2536
+
2537
+	/**
2538
+	 * Set to easily debug the next X queries ran from this model.
2539
+	 *
2540
+	 * @param int $count
2541
+	 */
2542
+	public function show_next_x_db_queries($count = 1)
2543
+	{
2544
+		$this->_show_next_x_db_queries = $count;
2545
+	}
2546
+
2547
+
2548
+
2549
+	/**
2550
+	 * @param $sql_query
2551
+	 */
2552
+	public function show_db_query_if_previously_requested($sql_query)
2553
+	{
2554
+		if ($this->_show_next_x_db_queries > 0) {
2555
+			echo esc_html($sql_query);
2556
+			$this->_show_next_x_db_queries--;
2557
+		}
2558
+	}
2559
+
2560
+
2561
+
2562
+	/**
2563
+	 * Adds a relationship of the correct type between $modelObject and $otherModelObject.
2564
+	 * There are the 3 cases:
2565
+	 * 'belongsTo' relationship: sets $id_or_obj's foreign_key to be $other_model_id_or_obj's primary_key. If
2566
+	 * $otherModelObject has no ID, it is first saved.
2567
+	 * 'hasMany' relationship: sets $other_model_id_or_obj's foreign_key to be $id_or_obj's primary_key. If $id_or_obj
2568
+	 * has no ID, it is first saved.
2569
+	 * 'hasAndBelongsToMany' relationships: checks that there isn't already an entry in the join table, and adds one.
2570
+	 * If one of the model Objects has not yet been saved to the database, it is saved before adding the entry in the
2571
+	 * join table
2572
+	 *
2573
+	 * @param        EE_Base_Class                     /int $thisModelObject
2574
+	 * @param        EE_Base_Class                     /int $id_or_obj EE_base_Class or ID of other Model Object
2575
+	 * @param string $relationName                     , key in EEM_Base::_relations
2576
+	 *                                                 an attendee to a group, you also want to specify which role they
2577
+	 *                                                 will have in that group. So you would use this parameter to
2578
+	 *                                                 specify array('role-column-name'=>'role-id')
2579
+	 * @param array  $extra_join_model_fields_n_values This allows you to enter further query params for the relation
2580
+	 *                                                 to for relation to methods that allow you to further specify
2581
+	 *                                                 extra columns to join by (such as HABTM).  Keep in mind that the
2582
+	 *                                                 only acceptable query_params is strict "col" => "value" pairs
2583
+	 *                                                 because these will be inserted in any new rows created as well.
2584
+	 * @return EE_Base_Class which was added as a relation. Object referred to by $other_model_id_or_obj
2585
+	 * @throws EE_Error
2586
+	 */
2587
+	public function add_relationship_to(
2588
+		$id_or_obj,
2589
+		$other_model_id_or_obj,
2590
+		$relationName,
2591
+		$extra_join_model_fields_n_values = array()
2592
+	) {
2593
+		$relation_obj = $this->related_settings_for($relationName);
2594
+		return $relation_obj->add_relation_to($id_or_obj, $other_model_id_or_obj, $extra_join_model_fields_n_values);
2595
+	}
2596
+
2597
+
2598
+
2599
+	/**
2600
+	 * Removes a relationship of the correct type between $modelObject and $otherModelObject.
2601
+	 * There are the 3 cases:
2602
+	 * 'belongsTo' relationship: sets $modelObject's foreign_key to null, if that field is nullable.Otherwise throws an
2603
+	 * error
2604
+	 * 'hasMany' relationship: sets $otherModelObject's foreign_key to null,if that field is nullable.Otherwise throws
2605
+	 * an error
2606
+	 * 'hasAndBelongsToMany' relationships:removes any existing entry in the join table between the two models.
2607
+	 *
2608
+	 * @param        EE_Base_Class /int $id_or_obj
2609
+	 * @param        EE_Base_Class /int $other_model_id_or_obj EE_Base_Class or ID of other Model Object
2610
+	 * @param string $relationName key in EEM_Base::_relations
2611
+	 * @return boolean of success
2612
+	 * @throws EE_Error
2613
+	 * @param array  $where_query  This allows you to enter further query params for the relation to for relation to
2614
+	 *                             methods that allow you to further specify extra columns to join by (such as HABTM).
2615
+	 *                             Keep in mind that the only acceptable query_params is strict "col" => "value" pairs
2616
+	 *                             because these will be inserted in any new rows created as well.
2617
+	 */
2618
+	public function remove_relationship_to($id_or_obj, $other_model_id_or_obj, $relationName, $where_query = array())
2619
+	{
2620
+		$relation_obj = $this->related_settings_for($relationName);
2621
+		return $relation_obj->remove_relation_to($id_or_obj, $other_model_id_or_obj, $where_query);
2622
+	}
2623
+
2624
+
2625
+
2626
+	/**
2627
+	 * @param mixed           $id_or_obj
2628
+	 * @param string          $relationName
2629
+	 * @param array           $where_query_params
2630
+	 * @param EE_Base_Class[] objects to which relations were removed
2631
+	 * @return \EE_Base_Class[]
2632
+	 * @throws EE_Error
2633
+	 */
2634
+	public function remove_relations($id_or_obj, $relationName, $where_query_params = array())
2635
+	{
2636
+		$relation_obj = $this->related_settings_for($relationName);
2637
+		return $relation_obj->remove_relations($id_or_obj, $where_query_params);
2638
+	}
2639
+
2640
+
2641
+
2642
+	/**
2643
+	 * Gets all the related items of the specified $model_name, using $query_params.
2644
+	 * Note: by default, we remove the "default query params"
2645
+	 * because we want to get even deleted items etc.
2646
+	 *
2647
+	 * @param mixed  $id_or_obj    EE_Base_Class child or its ID
2648
+	 * @param string $model_name   like 'Event', 'Registration', etc. always singular
2649
+	 * @param array  $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2650
+	 * @return EE_Base_Class[]
2651
+	 * @throws EE_Error
2652
+	 */
2653
+	public function get_all_related($id_or_obj, $model_name, $query_params = null)
2654
+	{
2655
+		$model_obj = $this->ensure_is_obj($id_or_obj);
2656
+		$relation_settings = $this->related_settings_for($model_name);
2657
+		return $relation_settings->get_all_related($model_obj, $query_params);
2658
+	}
2659
+
2660
+
2661
+
2662
+	/**
2663
+	 * Deletes all the model objects across the relation indicated by $model_name
2664
+	 * which are related to $id_or_obj which meet the criteria set in $query_params.
2665
+	 * However, if the model objects can't be deleted because of blocking related model objects, then
2666
+	 * they aren't deleted. (Unless the thing that would have been deleted can be soft-deleted, that still happens).
2667
+	 *
2668
+	 * @param EE_Base_Class|int|string $id_or_obj
2669
+	 * @param string                   $model_name
2670
+	 * @param array                    $query_params
2671
+	 * @return int how many deleted
2672
+	 * @throws EE_Error
2673
+	 */
2674
+	public function delete_related($id_or_obj, $model_name, $query_params = array())
2675
+	{
2676
+		$model_obj = $this->ensure_is_obj($id_or_obj);
2677
+		$relation_settings = $this->related_settings_for($model_name);
2678
+		return $relation_settings->delete_all_related($model_obj, $query_params);
2679
+	}
2680
+
2681
+
2682
+
2683
+	/**
2684
+	 * Hard deletes all the model objects across the relation indicated by $model_name
2685
+	 * which are related to $id_or_obj which meet the criteria set in $query_params. If
2686
+	 * the model objects can't be hard deleted because of blocking related model objects,
2687
+	 * just does a soft-delete on them instead.
2688
+	 *
2689
+	 * @param EE_Base_Class|int|string $id_or_obj
2690
+	 * @param string                   $model_name
2691
+	 * @param array                    $query_params
2692
+	 * @return int how many deleted
2693
+	 * @throws EE_Error
2694
+	 */
2695
+	public function delete_related_permanently($id_or_obj, $model_name, $query_params = array())
2696
+	{
2697
+		$model_obj = $this->ensure_is_obj($id_or_obj);
2698
+		$relation_settings = $this->related_settings_for($model_name);
2699
+		return $relation_settings->delete_related_permanently($model_obj, $query_params);
2700
+	}
2701
+
2702
+
2703
+
2704
+	/**
2705
+	 * Instead of getting the related model objects, simply counts them. Ignores default_where_conditions by default,
2706
+	 * unless otherwise specified in the $query_params
2707
+	 *
2708
+	 * @param        int             /EE_Base_Class $id_or_obj
2709
+	 * @param string $model_name     like 'Event', or 'Registration'
2710
+	 * @param array  $query_params   @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2711
+	 * @param string $field_to_count name of field to count by. By default, uses primary key
2712
+	 * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2713
+	 *                               that by the setting $distinct to TRUE;
2714
+	 * @return int
2715
+	 * @throws EE_Error
2716
+	 */
2717
+	public function count_related(
2718
+		$id_or_obj,
2719
+		$model_name,
2720
+		$query_params = array(),
2721
+		$field_to_count = null,
2722
+		$distinct = false
2723
+	) {
2724
+		$related_model = $this->get_related_model_obj($model_name);
2725
+		// we're just going to use the query params on the related model's normal get_all query,
2726
+		// except add a condition to say to match the current mod
2727
+		if (! isset($query_params['default_where_conditions'])) {
2728
+			$query_params['default_where_conditions'] = EEM_Base::default_where_conditions_none;
2729
+		}
2730
+		$this_model_name = $this->get_this_model_name();
2731
+		$this_pk_field_name = $this->get_primary_key_field()->get_name();
2732
+		$query_params[0][ $this_model_name . "." . $this_pk_field_name ] = $id_or_obj;
2733
+		return $related_model->count($query_params, $field_to_count, $distinct);
2734
+	}
2735
+
2736
+
2737
+
2738
+	/**
2739
+	 * Instead of getting the related model objects, simply sums up the values of the specified field.
2740
+	 * Note: ignores default_where_conditions by default, unless otherwise specified in the $query_params
2741
+	 *
2742
+	 * @param        int           /EE_Base_Class $id_or_obj
2743
+	 * @param string $model_name   like 'Event', or 'Registration'
2744
+	 * @param array  $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2745
+	 * @param string $field_to_sum name of field to count by. By default, uses primary key
2746
+	 * @return float
2747
+	 * @throws EE_Error
2748
+	 */
2749
+	public function sum_related($id_or_obj, $model_name, $query_params, $field_to_sum = null)
2750
+	{
2751
+		$related_model = $this->get_related_model_obj($model_name);
2752
+		if (! is_array($query_params)) {
2753
+			EE_Error::doing_it_wrong(
2754
+				'EEM_Base::sum_related',
2755
+				sprintf(
2756
+					esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
2757
+					gettype($query_params)
2758
+				),
2759
+				'4.6.0'
2760
+			);
2761
+			$query_params = array();
2762
+		}
2763
+		// we're just going to use the query params on the related model's normal get_all query,
2764
+		// except add a condition to say to match the current mod
2765
+		if (! isset($query_params['default_where_conditions'])) {
2766
+			$query_params['default_where_conditions'] = EEM_Base::default_where_conditions_none;
2767
+		}
2768
+		$this_model_name = $this->get_this_model_name();
2769
+		$this_pk_field_name = $this->get_primary_key_field()->get_name();
2770
+		$query_params[0][ $this_model_name . "." . $this_pk_field_name ] = $id_or_obj;
2771
+		return $related_model->sum($query_params, $field_to_sum);
2772
+	}
2773
+
2774
+
2775
+
2776
+	/**
2777
+	 * Uses $this->_relatedModels info to find the first related model object of relation $relationName to the given
2778
+	 * $modelObject
2779
+	 *
2780
+	 * @param int | EE_Base_Class $id_or_obj        EE_Base_Class child or its ID
2781
+	 * @param string              $other_model_name , key in $this->_relatedModels, eg 'Registration', or 'Events'
2782
+	 * @param array               $query_params     @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2783
+	 * @return EE_Base_Class
2784
+	 * @throws EE_Error
2785
+	 */
2786
+	public function get_first_related(EE_Base_Class $id_or_obj, $other_model_name, $query_params)
2787
+	{
2788
+		$query_params['limit'] = 1;
2789
+		$results = $this->get_all_related($id_or_obj, $other_model_name, $query_params);
2790
+		if ($results) {
2791
+			return array_shift($results);
2792
+		}
2793
+		return null;
2794
+	}
2795
+
2796
+
2797
+
2798
+	/**
2799
+	 * Gets the model's name as it's expected in queries. For example, if this is EEM_Event model, that would be Event
2800
+	 *
2801
+	 * @return string
2802
+	 */
2803
+	public function get_this_model_name()
2804
+	{
2805
+		return str_replace("EEM_", "", get_class($this));
2806
+	}
2807
+
2808
+
2809
+
2810
+	/**
2811
+	 * Gets the model field on this model which is of type EE_Any_Foreign_Model_Name_Field
2812
+	 *
2813
+	 * @return EE_Any_Foreign_Model_Name_Field
2814
+	 * @throws EE_Error
2815
+	 */
2816
+	public function get_field_containing_related_model_name()
2817
+	{
2818
+		foreach ($this->field_settings(true) as $field) {
2819
+			if ($field instanceof EE_Any_Foreign_Model_Name_Field) {
2820
+				$field_with_model_name = $field;
2821
+			}
2822
+		}
2823
+		if (! isset($field_with_model_name) || ! $field_with_model_name) {
2824
+			throw new EE_Error(sprintf(
2825
+				esc_html__("There is no EE_Any_Foreign_Model_Name field on model %s", "event_espresso"),
2826
+				$this->get_this_model_name()
2827
+			));
2828
+		}
2829
+		return $field_with_model_name;
2830
+	}
2831
+
2832
+
2833
+
2834
+	/**
2835
+	 * Inserts a new entry into the database, for each table.
2836
+	 * Note: does not add the item to the entity map because that is done by EE_Base_Class::save() right after this.
2837
+	 * If client code uses EEM_Base::insert() directly, then although the item isn't in the entity map,
2838
+	 * we also know there is no model object with the newly inserted item's ID at the moment (because
2839
+	 * if there were, then they would already be in the DB and this would fail); and in the future if someone
2840
+	 * creates a model object with this ID (or grabs it from the DB) then it will be added to the
2841
+	 * entity map at that time anyways. SO, no need for EEM_Base::insert ot add to the entity map
2842
+	 *
2843
+	 * @param array $field_n_values keys are field names, values are their values (in the client code's domain if
2844
+	 *                              $values_already_prepared_by_model_object is false, in the model object's domain if
2845
+	 *                              $values_already_prepared_by_model_object is true. See comment about this at the top
2846
+	 *                              of EEM_Base)
2847
+	 * @return int|string new primary key on main table that got inserted
2848
+	 * @throws EE_Error
2849
+	 */
2850
+	public function insert($field_n_values)
2851
+	{
2852
+		/**
2853
+		 * Filters the fields and their values before inserting an item using the models
2854
+		 *
2855
+		 * @param array    $fields_n_values keys are the fields and values are their new values
2856
+		 * @param EEM_Base $model           the model used
2857
+		 */
2858
+		$field_n_values = (array) apply_filters('FHEE__EEM_Base__insert__fields_n_values', $field_n_values, $this);
2859
+		if ($this->_satisfies_unique_indexes($field_n_values)) {
2860
+			$main_table = $this->_get_main_table();
2861
+			$new_id = $this->_insert_into_specific_table($main_table, $field_n_values, false);
2862
+			if ($new_id !== false) {
2863
+				foreach ($this->_get_other_tables() as $other_table) {
2864
+					$this->_insert_into_specific_table($other_table, $field_n_values, $new_id);
2865
+				}
2866
+			}
2867
+			/**
2868
+			 * Done just after attempting to insert a new model object
2869
+			 *
2870
+			 * @param EEM_Base   $model           used
2871
+			 * @param array      $fields_n_values fields and their values
2872
+			 * @param int|string the              ID of the newly-inserted model object
2873
+			 */
2874
+			do_action('AHEE__EEM_Base__insert__end', $this, $field_n_values, $new_id);
2875
+			return $new_id;
2876
+		}
2877
+		return false;
2878
+	}
2879
+
2880
+
2881
+
2882
+	/**
2883
+	 * Checks that the result would satisfy the unique indexes on this model
2884
+	 *
2885
+	 * @param array  $field_n_values
2886
+	 * @param string $action
2887
+	 * @return boolean
2888
+	 * @throws EE_Error
2889
+	 */
2890
+	protected function _satisfies_unique_indexes($field_n_values, $action = 'insert')
2891
+	{
2892
+		foreach ($this->unique_indexes() as $index_name => $index) {
2893
+			$uniqueness_where_params = array_intersect_key($field_n_values, $index->fields());
2894
+			if ($this->exists(array($uniqueness_where_params))) {
2895
+				EE_Error::add_error(
2896
+					sprintf(
2897
+						esc_html__(
2898
+							"Could not %s %s. %s uniqueness index failed. Fields %s must form a unique set, but an entry already exists with values %s.",
2899
+							"event_espresso"
2900
+						),
2901
+						$action,
2902
+						$this->_get_class_name(),
2903
+						$index_name,
2904
+						implode(",", $index->field_names()),
2905
+						http_build_query($uniqueness_where_params)
2906
+					),
2907
+					__FILE__,
2908
+					__FUNCTION__,
2909
+					__LINE__
2910
+				);
2911
+				return false;
2912
+			}
2913
+		}
2914
+		return true;
2915
+	}
2916
+
2917
+
2918
+
2919
+	/**
2920
+	 * Checks the database for an item that conflicts (ie, if this item were
2921
+	 * saved to the DB would break some uniqueness requirement, like a primary key
2922
+	 * or an index primary key set) with the item specified. $id_obj_or_fields_array
2923
+	 * can be either an EE_Base_Class or an array of fields n values
2924
+	 *
2925
+	 * @param EE_Base_Class|array $obj_or_fields_array
2926
+	 * @param boolean             $include_primary_key whether to use the model object's primary key
2927
+	 *                                                 when looking for conflicts
2928
+	 *                                                 (ie, if false, we ignore the model object's primary key
2929
+	 *                                                 when finding "conflicts". If true, it's also considered).
2930
+	 *                                                 Only works for INT primary key,
2931
+	 *                                                 STRING primary keys cannot be ignored
2932
+	 * @throws EE_Error
2933
+	 * @return EE_Base_Class|array
2934
+	 */
2935
+	public function get_one_conflicting($obj_or_fields_array, $include_primary_key = true)
2936
+	{
2937
+		if ($obj_or_fields_array instanceof EE_Base_Class) {
2938
+			$fields_n_values = $obj_or_fields_array->model_field_array();
2939
+		} elseif (is_array($obj_or_fields_array)) {
2940
+			$fields_n_values = $obj_or_fields_array;
2941
+		} else {
2942
+			throw new EE_Error(
2943
+				sprintf(
2944
+					esc_html__(
2945
+						"%s get_all_conflicting should be called with a model object or an array of field names and values, you provided %d",
2946
+						"event_espresso"
2947
+					),
2948
+					get_class($this),
2949
+					$obj_or_fields_array
2950
+				)
2951
+			);
2952
+		}
2953
+		$query_params = array();
2954
+		if (
2955
+			$this->has_primary_key_field()
2956
+			&& ($include_primary_key
2957
+				|| $this->get_primary_key_field()
2958
+				   instanceof
2959
+				   EE_Primary_Key_String_Field)
2960
+			&& isset($fields_n_values[ $this->primary_key_name() ])
2961
+		) {
2962
+			$query_params[0]['OR'][ $this->primary_key_name() ] = $fields_n_values[ $this->primary_key_name() ];
2963
+		}
2964
+		foreach ($this->unique_indexes() as $unique_index_name => $unique_index) {
2965
+			$uniqueness_where_params = array_intersect_key($fields_n_values, $unique_index->fields());
2966
+			$query_params[0]['OR'][ 'AND*' . $unique_index_name ] = $uniqueness_where_params;
2967
+		}
2968
+		// if there is nothing to base this search on, then we shouldn't find anything
2969
+		if (empty($query_params)) {
2970
+			return array();
2971
+		}
2972
+		return $this->get_one($query_params);
2973
+	}
2974
+
2975
+
2976
+
2977
+	/**
2978
+	 * Like count, but is optimized and returns a boolean instead of an int
2979
+	 *
2980
+	 * @param array $query_params
2981
+	 * @return boolean
2982
+	 * @throws EE_Error
2983
+	 */
2984
+	public function exists($query_params)
2985
+	{
2986
+		$query_params['limit'] = 1;
2987
+		return $this->count($query_params) > 0;
2988
+	}
2989
+
2990
+
2991
+
2992
+	/**
2993
+	 * Wrapper for exists, except ignores default query parameters so we're only considering ID
2994
+	 *
2995
+	 * @param int|string $id
2996
+	 * @return boolean
2997
+	 * @throws EE_Error
2998
+	 */
2999
+	public function exists_by_ID($id)
3000
+	{
3001
+		return $this->exists(
3002
+			array(
3003
+				'default_where_conditions' => EEM_Base::default_where_conditions_none,
3004
+				array(
3005
+					$this->primary_key_name() => $id,
3006
+				),
3007
+			)
3008
+		);
3009
+	}
3010
+
3011
+
3012
+
3013
+	/**
3014
+	 * Inserts a new row in $table, using the $cols_n_values which apply to that table.
3015
+	 * If a $new_id is supplied and if $table is an EE_Other_Table, we assume
3016
+	 * we need to add a foreign key column to point to $new_id (which should be the primary key's value
3017
+	 * on the main table)
3018
+	 * This is protected rather than private because private is not accessible to any child methods and there MAY be
3019
+	 * cases where we want to call it directly rather than via insert().
3020
+	 *
3021
+	 * @access   protected
3022
+	 * @param EE_Table_Base $table
3023
+	 * @param array         $fields_n_values each key should be in field's keys, and value should be an int, string or
3024
+	 *                                       float
3025
+	 * @param int           $new_id          for now we assume only int keys
3026
+	 * @throws EE_Error
3027
+	 * @global WPDB         $wpdb            only used to get the $wpdb->insert_id after performing an insert
3028
+	 * @return int ID of new row inserted, or FALSE on failure
3029
+	 */
3030
+	protected function _insert_into_specific_table(EE_Table_Base $table, $fields_n_values, $new_id = 0)
3031
+	{
3032
+		global $wpdb;
3033
+		$insertion_col_n_values = array();
3034
+		$format_for_insertion = array();
3035
+		$fields_on_table = $this->_get_fields_for_table($table->get_table_alias());
3036
+		foreach ($fields_on_table as $field_name => $field_obj) {
3037
+			// check if its an auto-incrementing column, in which case we should just leave it to do its autoincrement thing
3038
+			if ($field_obj->is_auto_increment()) {
3039
+				continue;
3040
+			}
3041
+			$prepared_value = $this->_prepare_value_or_use_default($field_obj, $fields_n_values);
3042
+			// if the value we want to assign it to is NULL, just don't mention it for the insertion
3043
+			if ($prepared_value !== null) {
3044
+				$insertion_col_n_values[ $field_obj->get_table_column() ] = $prepared_value;
3045
+				$format_for_insertion[] = $field_obj->get_wpdb_data_type();
3046
+			}
3047
+		}
3048
+		if ($table instanceof EE_Secondary_Table && $new_id) {
3049
+			// its not the main table, so we should have already saved the main table's PK which we just inserted
3050
+			// so add the fk to the main table as a column
3051
+			$insertion_col_n_values[ $table->get_fk_on_table() ] = $new_id;
3052
+			$format_for_insertion[] = '%d';// yes right now we're only allowing these foreign keys to be INTs
3053
+		}
3054
+		// insert the new entry
3055
+		$result = $this->_do_wpdb_query(
3056
+			'insert',
3057
+			array($table->get_table_name(), $insertion_col_n_values, $format_for_insertion)
3058
+		);
3059
+		if ($result === false) {
3060
+			return false;
3061
+		}
3062
+		// ok, now what do we return for the ID of the newly-inserted thing?
3063
+		if ($this->has_primary_key_field()) {
3064
+			if ($this->get_primary_key_field()->is_auto_increment()) {
3065
+				return $wpdb->insert_id;
3066
+			}
3067
+			// it's not an auto-increment primary key, so
3068
+			// it must have been supplied
3069
+			return $fields_n_values[ $this->get_primary_key_field()->get_name() ];
3070
+		}
3071
+		// we can't return a  primary key because there is none. instead return
3072
+		// a unique string indicating this model
3073
+		return $this->get_index_primary_key_string($fields_n_values);
3074
+	}
3075
+
3076
+
3077
+
3078
+	/**
3079
+	 * Prepare the $field_obj 's value in $fields_n_values for use in the database.
3080
+	 * If the field doesn't allow NULL, try to use its default. (If it doesn't allow NULL,
3081
+	 * and there is no default, we pass it along. WPDB will take care of it)
3082
+	 *
3083
+	 * @param EE_Model_Field_Base $field_obj
3084
+	 * @param array               $fields_n_values
3085
+	 * @return mixed string|int|float depending on what the table column will be expecting
3086
+	 * @throws EE_Error
3087
+	 */
3088
+	protected function _prepare_value_or_use_default($field_obj, $fields_n_values)
3089
+	{
3090
+		// if this field doesn't allow nullable, don't allow it
3091
+		if (
3092
+			! $field_obj->is_nullable()
3093
+			&& (
3094
+				! isset($fields_n_values[ $field_obj->get_name() ])
3095
+				|| $fields_n_values[ $field_obj->get_name() ] === null
3096
+			)
3097
+		) {
3098
+			$fields_n_values[ $field_obj->get_name() ] = $field_obj->get_default_value();
3099
+		}
3100
+		$unprepared_value = isset($fields_n_values[ $field_obj->get_name() ])
3101
+			? $fields_n_values[ $field_obj->get_name() ]
3102
+			: null;
3103
+		return $this->_prepare_value_for_use_in_db($unprepared_value, $field_obj);
3104
+	}
3105
+
3106
+
3107
+
3108
+	/**
3109
+	 * Consolidates code for preparing  a value supplied to the model for use int eh db. Calls the field's
3110
+	 * prepare_for_use_in_db method on the value, and depending on $value_already_prepare_by_model_obj, may also call
3111
+	 * the field's prepare_for_set() method.
3112
+	 *
3113
+	 * @param mixed               $value value in the client code domain if $value_already_prepared_by_model_object is
3114
+	 *                                   false, otherwise a value in the model object's domain (see lengthy comment at
3115
+	 *                                   top of file)
3116
+	 * @param EE_Model_Field_Base $field field which will be doing the preparing of the value. If null, we assume
3117
+	 *                                   $value is a custom selection
3118
+	 * @return mixed a value ready for use in the database for insertions, updating, or in a where clause
3119
+	 */
3120
+	private function _prepare_value_for_use_in_db($value, $field)
3121
+	{
3122
+		if ($field && $field instanceof EE_Model_Field_Base) {
3123
+			// phpcs:disable PSR2.ControlStructures.SwitchDeclaration.TerminatingComment
3124
+			switch ($this->_values_already_prepared_by_model_object) {
3125
+				/** @noinspection PhpMissingBreakStatementInspection */
3126
+				case self::not_prepared_by_model_object:
3127
+					$value = $field->prepare_for_set($value);
3128
+				// purposefully left out "return"
3129
+				// no break
3130
+				case self::prepared_by_model_object:
3131
+					/** @noinspection SuspiciousAssignmentsInspection */
3132
+					$value = $field->prepare_for_use_in_db($value);
3133
+					// no break
3134
+				case self::prepared_for_use_in_db:
3135
+					// leave the value alone
3136
+			}
3137
+			return $value;
3138
+			// phpcs:enable
3139
+		}
3140
+		return $value;
3141
+	}
3142
+
3143
+
3144
+
3145
+	/**
3146
+	 * Returns the main table on this model
3147
+	 *
3148
+	 * @return EE_Primary_Table
3149
+	 * @throws EE_Error
3150
+	 */
3151
+	protected function _get_main_table()
3152
+	{
3153
+		foreach ($this->_tables as $table) {
3154
+			if ($table instanceof EE_Primary_Table) {
3155
+				return $table;
3156
+			}
3157
+		}
3158
+		throw new EE_Error(sprintf(esc_html__(
3159
+			'There are no main tables on %s. They should be added to _tables array in the constructor',
3160
+			'event_espresso'
3161
+		), get_class($this)));
3162
+	}
3163
+
3164
+
3165
+
3166
+	/**
3167
+	 * table
3168
+	 * returns EE_Primary_Table table name
3169
+	 *
3170
+	 * @return string
3171
+	 * @throws EE_Error
3172
+	 */
3173
+	public function table()
3174
+	{
3175
+		return $this->_get_main_table()->get_table_name();
3176
+	}
3177
+
3178
+
3179
+
3180
+	/**
3181
+	 * table
3182
+	 * returns first EE_Secondary_Table table name
3183
+	 *
3184
+	 * @return string
3185
+	 */
3186
+	public function second_table()
3187
+	{
3188
+		// grab second table from tables array
3189
+		$second_table = end($this->_tables);
3190
+		return $second_table instanceof EE_Secondary_Table ? $second_table->get_table_name() : null;
3191
+	}
3192
+
3193
+
3194
+
3195
+	/**
3196
+	 * get_table_obj_by_alias
3197
+	 * returns table name given it's alias
3198
+	 *
3199
+	 * @param string $table_alias
3200
+	 * @return EE_Primary_Table | EE_Secondary_Table
3201
+	 */
3202
+	public function get_table_obj_by_alias($table_alias = '')
3203
+	{
3204
+		return isset($this->_tables[ $table_alias ]) ? $this->_tables[ $table_alias ] : null;
3205
+	}
3206
+
3207
+
3208
+
3209
+	/**
3210
+	 * Gets all the tables of type EE_Other_Table from EEM_CPT_Basel_Model::_tables
3211
+	 *
3212
+	 * @return EE_Secondary_Table[]
3213
+	 */
3214
+	protected function _get_other_tables()
3215
+	{
3216
+		$other_tables = array();
3217
+		foreach ($this->_tables as $table_alias => $table) {
3218
+			if ($table instanceof EE_Secondary_Table) {
3219
+				$other_tables[ $table_alias ] = $table;
3220
+			}
3221
+		}
3222
+		return $other_tables;
3223
+	}
3224
+
3225
+
3226
+
3227
+	/**
3228
+	 * Finds all the fields that correspond to the given table
3229
+	 *
3230
+	 * @param string $table_alias , array key in EEM_Base::_tables
3231
+	 * @return EE_Model_Field_Base[]
3232
+	 */
3233
+	public function _get_fields_for_table($table_alias)
3234
+	{
3235
+		return $this->_fields[ $table_alias ];
3236
+	}
3237
+
3238
+
3239
+
3240
+	/**
3241
+	 * Recurses through all the where parameters, and finds all the related models we'll need
3242
+	 * to complete this query. Eg, given where parameters like array('EVT_ID'=>3) from within Event model, we won't
3243
+	 * need any related models. But if the array were array('Registrations.REG_ID'=>3), we'd need the related
3244
+	 * Registration model. If it were array('Registrations.Transactions.Payments.PAY_ID'=>3), then we'd need the
3245
+	 * related Registration, Transaction, and Payment models.
3246
+	 *
3247
+	 * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
3248
+	 * @return EE_Model_Query_Info_Carrier
3249
+	 * @throws EE_Error
3250
+	 */
3251
+	public function _extract_related_models_from_query($query_params)
3252
+	{
3253
+		$query_info_carrier = new EE_Model_Query_Info_Carrier();
3254
+		if (array_key_exists(0, $query_params)) {
3255
+			$this->_extract_related_models_from_sub_params_array_keys($query_params[0], $query_info_carrier, 0);
3256
+		}
3257
+		if (array_key_exists('group_by', $query_params)) {
3258
+			if (is_array($query_params['group_by'])) {
3259
+				$this->_extract_related_models_from_sub_params_array_values(
3260
+					$query_params['group_by'],
3261
+					$query_info_carrier,
3262
+					'group_by'
3263
+				);
3264
+			} elseif (! empty($query_params['group_by'])) {
3265
+				$this->_extract_related_model_info_from_query_param(
3266
+					$query_params['group_by'],
3267
+					$query_info_carrier,
3268
+					'group_by'
3269
+				);
3270
+			}
3271
+		}
3272
+		if (array_key_exists('having', $query_params)) {
3273
+			$this->_extract_related_models_from_sub_params_array_keys(
3274
+				$query_params[0],
3275
+				$query_info_carrier,
3276
+				'having'
3277
+			);
3278
+		}
3279
+		if (array_key_exists('order_by', $query_params)) {
3280
+			if (is_array($query_params['order_by'])) {
3281
+				$this->_extract_related_models_from_sub_params_array_keys(
3282
+					$query_params['order_by'],
3283
+					$query_info_carrier,
3284
+					'order_by'
3285
+				);
3286
+			} elseif (! empty($query_params['order_by'])) {
3287
+				$this->_extract_related_model_info_from_query_param(
3288
+					$query_params['order_by'],
3289
+					$query_info_carrier,
3290
+					'order_by'
3291
+				);
3292
+			}
3293
+		}
3294
+		if (array_key_exists('force_join', $query_params)) {
3295
+			$this->_extract_related_models_from_sub_params_array_values(
3296
+				$query_params['force_join'],
3297
+				$query_info_carrier,
3298
+				'force_join'
3299
+			);
3300
+		}
3301
+		$this->extractRelatedModelsFromCustomSelects($query_info_carrier);
3302
+		return $query_info_carrier;
3303
+	}
3304
+
3305
+
3306
+
3307
+	/**
3308
+	 * For extracting related models from WHERE (0), HAVING (having), ORDER BY (order_by) or forced joins (force_join)
3309
+	 *
3310
+	 * @param array                       $sub_query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#-0-where-conditions
3311
+	 * @param EE_Model_Query_Info_Carrier $model_query_info_carrier
3312
+	 * @param string                      $query_param_type one of $this->_allowed_query_params
3313
+	 * @throws EE_Error
3314
+	 * @return \EE_Model_Query_Info_Carrier
3315
+	 */
3316
+	private function _extract_related_models_from_sub_params_array_keys(
3317
+		$sub_query_params,
3318
+		EE_Model_Query_Info_Carrier $model_query_info_carrier,
3319
+		$query_param_type
3320
+	) {
3321
+		if (! empty($sub_query_params)) {
3322
+			$sub_query_params = (array) $sub_query_params;
3323
+			foreach ($sub_query_params as $param => $possibly_array_of_params) {
3324
+				// $param could be simply 'EVT_ID', or it could be 'Registrations.REG_ID', or even 'Registrations.Transactions.Payments.PAY_amount'
3325
+				$this->_extract_related_model_info_from_query_param(
3326
+					$param,
3327
+					$model_query_info_carrier,
3328
+					$query_param_type
3329
+				);
3330
+				// if $possibly_array_of_params is an array, try recursing into it, searching for keys which
3331
+				// indicate needed joins. Eg, array('NOT'=>array('Registration.TXN_ID'=>23)). In this case, we tried
3332
+				// extracting models out of the 'NOT', which obviously wasn't successful, and then we recurse into the value
3333
+				// of array('Registration.TXN_ID'=>23)
3334
+				$query_param_sans_stars = $this->_remove_stars_and_anything_after_from_condition_query_param_key($param);
3335
+				if (in_array($query_param_sans_stars, $this->_logic_query_param_keys, true)) {
3336
+					if (! is_array($possibly_array_of_params)) {
3337
+						throw new EE_Error(sprintf(
3338
+							esc_html__(
3339
+								"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'))",
3340
+								"event_espresso"
3341
+							),
3342
+							$param,
3343
+							$possibly_array_of_params
3344
+						));
3345
+					}
3346
+					$this->_extract_related_models_from_sub_params_array_keys(
3347
+						$possibly_array_of_params,
3348
+						$model_query_info_carrier,
3349
+						$query_param_type
3350
+					);
3351
+				} elseif (
3352
+					$query_param_type === 0 // ie WHERE
3353
+						  && is_array($possibly_array_of_params)
3354
+						  && isset($possibly_array_of_params[2])
3355
+						  && $possibly_array_of_params[2] == true
3356
+				) {
3357
+					// then $possible_array_of_params looks something like array('<','DTT_sold',true)
3358
+					// indicating that $possible_array_of_params[1] is actually a field name,
3359
+					// from which we should extract query parameters!
3360
+					if (! isset($possibly_array_of_params[0], $possibly_array_of_params[1])) {
3361
+						throw new EE_Error(sprintf(esc_html__(
3362
+							"Improperly formed query parameter %s. It should be numerically indexed like array('<','DTT_sold',true); but you provided %s",
3363
+							"event_espresso"
3364
+						), $query_param_type, implode(",", $possibly_array_of_params)));
3365
+					}
3366
+					$this->_extract_related_model_info_from_query_param(
3367
+						$possibly_array_of_params[1],
3368
+						$model_query_info_carrier,
3369
+						$query_param_type
3370
+					);
3371
+				}
3372
+			}
3373
+		}
3374
+		return $model_query_info_carrier;
3375
+	}
3376
+
3377
+
3378
+
3379
+	/**
3380
+	 * For extracting related models from forced_joins, where the array values contain the info about what
3381
+	 * models to join with. Eg an array like array('Attendee','Price.Price_Type');
3382
+	 *
3383
+	 * @param array                       $sub_query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3384
+	 * @param EE_Model_Query_Info_Carrier $model_query_info_carrier
3385
+	 * @param string                      $query_param_type one of $this->_allowed_query_params
3386
+	 * @throws EE_Error
3387
+	 * @return \EE_Model_Query_Info_Carrier
3388
+	 */
3389
+	private function _extract_related_models_from_sub_params_array_values(
3390
+		$sub_query_params,
3391
+		EE_Model_Query_Info_Carrier $model_query_info_carrier,
3392
+		$query_param_type
3393
+	) {
3394
+		if (! empty($sub_query_params)) {
3395
+			if (! is_array($sub_query_params)) {
3396
+				throw new EE_Error(sprintf(
3397
+					esc_html__("Query parameter %s should be an array, but it isn't.", "event_espresso"),
3398
+					$sub_query_params
3399
+				));
3400
+			}
3401
+			foreach ($sub_query_params as $param) {
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
+			}
3409
+		}
3410
+		return $model_query_info_carrier;
3411
+	}
3412
+
3413
+
3414
+	/**
3415
+	 * Extract all the query parts from  model query params
3416
+	 * and put into a EEM_Related_Model_Info_Carrier for easy extraction into a query. We create this object
3417
+	 * instead of directly constructing the SQL because often we need to extract info from the $query_params
3418
+	 * but use them in a different order. Eg, we need to know what models we are querying
3419
+	 * before we know what joins to perform. However, we need to know what data types correspond to which fields on
3420
+	 * other models before we can finalize the where clause SQL.
3421
+	 *
3422
+	 * @param array $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
3423
+	 * @throws EE_Error
3424
+	 * @return EE_Model_Query_Info_Carrier
3425
+	 * @throws ModelConfigurationException
3426
+	 */
3427
+	public function _create_model_query_info_carrier($query_params)
3428
+	{
3429
+		if (! is_array($query_params)) {
3430
+			EE_Error::doing_it_wrong(
3431
+				'EEM_Base::_create_model_query_info_carrier',
3432
+				sprintf(
3433
+					esc_html__(
3434
+						'$query_params should be an array, you passed a variable of type %s',
3435
+						'event_espresso'
3436
+					),
3437
+					gettype($query_params)
3438
+				),
3439
+				'4.6.0'
3440
+			);
3441
+			$query_params = array();
3442
+		}
3443
+		$query_params[0] = isset($query_params[0]) ? $query_params[0] : array();
3444
+		// first check if we should alter the query to account for caps or not
3445
+		// because the caps might require us to do extra joins
3446
+		if (isset($query_params['caps']) && $query_params['caps'] !== 'none') {
3447
+			$query_params[0] = array_replace_recursive(
3448
+				$query_params[0],
3449
+				$this->caps_where_conditions($query_params['caps'])
3450
+			);
3451
+		}
3452
+
3453
+		// check if we should alter the query to remove data related to protected
3454
+		// custom post types
3455
+		if (isset($query_params['exclude_protected']) && $query_params['exclude_protected'] === true) {
3456
+			$where_param_key_for_password = $this->modelChainAndPassword();
3457
+			// only include if related to a cpt where no password has been set
3458
+			$query_params[0]['OR*nopassword'] = array(
3459
+				$where_param_key_for_password => '',
3460
+				$where_param_key_for_password . '*' => array('IS_NULL')
3461
+			);
3462
+		}
3463
+		$query_object = $this->_extract_related_models_from_query($query_params);
3464
+		// verify where_query_params has NO numeric indexes.... that's simply not how you use it!
3465
+		foreach ($query_params[0] as $key => $value) {
3466
+			if (is_int($key)) {
3467
+				throw new EE_Error(
3468
+					sprintf(
3469
+						esc_html__(
3470
+							"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.",
3471
+							"event_espresso"
3472
+						),
3473
+						$key,
3474
+						var_export($value, true),
3475
+						var_export($query_params, true),
3476
+						get_class($this)
3477
+					)
3478
+				);
3479
+			}
3480
+		}
3481
+		if (
3482
+			array_key_exists('default_where_conditions', $query_params)
3483
+			&& ! empty($query_params['default_where_conditions'])
3484
+		) {
3485
+			$use_default_where_conditions = $query_params['default_where_conditions'];
3486
+		} else {
3487
+			$use_default_where_conditions = EEM_Base::default_where_conditions_all;
3488
+		}
3489
+		$query_params[0] = array_merge(
3490
+			$this->_get_default_where_conditions_for_models_in_query(
3491
+				$query_object,
3492
+				$use_default_where_conditions,
3493
+				$query_params[0]
3494
+			),
3495
+			$query_params[0]
3496
+		);
3497
+		$query_object->set_where_sql($this->_construct_where_clause($query_params[0]));
3498
+		// if this is a "on_join_limit" then we are limiting on on a specific table in a multi_table join.
3499
+		// So we need to setup a subquery and use that for the main join.
3500
+		// Note for now this only works on the primary table for the model.
3501
+		// So for instance, you could set the limit array like this:
3502
+		// array( 'on_join_limit' => array('Primary_Table_Alias', array(1,10) ) )
3503
+		if (array_key_exists('on_join_limit', $query_params) && ! empty($query_params['on_join_limit'])) {
3504
+			$query_object->set_main_model_join_sql(
3505
+				$this->_construct_limit_join_select(
3506
+					$query_params['on_join_limit'][0],
3507
+					$query_params['on_join_limit'][1]
3508
+				)
3509
+			);
3510
+		}
3511
+		// set limit
3512
+		if (array_key_exists('limit', $query_params)) {
3513
+			if (is_array($query_params['limit'])) {
3514
+				if (! isset($query_params['limit'][0], $query_params['limit'][1])) {
3515
+					$e = sprintf(
3516
+						esc_html__(
3517
+							"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)",
3518
+							"event_espresso"
3519
+						),
3520
+						http_build_query($query_params['limit'])
3521
+					);
3522
+					throw new EE_Error($e . "|" . $e);
3523
+				}
3524
+				// they passed us an array for the limit. Assume it's like array(50,25), meaning offset by 50, and get 25
3525
+				$query_object->set_limit_sql(" LIMIT " . $query_params['limit'][0] . "," . $query_params['limit'][1]);
3526
+			} elseif (! empty($query_params['limit'])) {
3527
+				$query_object->set_limit_sql(" LIMIT " . $query_params['limit']);
3528
+			}
3529
+		}
3530
+		// set order by
3531
+		if (array_key_exists('order_by', $query_params)) {
3532
+			if (is_array($query_params['order_by'])) {
3533
+				// if they're using 'order_by' as an array, they can't use 'order' (because 'order_by' must
3534
+				// specify whether to ascend or descend on each field. Eg 'order_by'=>array('EVT_ID'=>'ASC'). So
3535
+				// including 'order' wouldn't make any sense if 'order_by' has already specified which way to order!
3536
+				if (array_key_exists('order', $query_params)) {
3537
+					throw new EE_Error(
3538
+						sprintf(
3539
+							esc_html__(
3540
+								"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 ",
3541
+								"event_espresso"
3542
+							),
3543
+							get_class($this),
3544
+							implode(", ", array_keys($query_params['order_by'])),
3545
+							implode(", ", $query_params['order_by']),
3546
+							$query_params['order']
3547
+						)
3548
+					);
3549
+				}
3550
+				$this->_extract_related_models_from_sub_params_array_keys(
3551
+					$query_params['order_by'],
3552
+					$query_object,
3553
+					'order_by'
3554
+				);
3555
+				// assume it's an array of fields to order by
3556
+				$order_array = array();
3557
+				foreach ($query_params['order_by'] as $field_name_to_order_by => $order) {
3558
+					$order = $this->_extract_order($order);
3559
+					$order_array[] = $this->_deduce_column_name_from_query_param($field_name_to_order_by) . SP . $order;
3560
+				}
3561
+				$query_object->set_order_by_sql(" ORDER BY " . implode(",", $order_array));
3562
+			} elseif (! empty($query_params['order_by'])) {
3563
+				$this->_extract_related_model_info_from_query_param(
3564
+					$query_params['order_by'],
3565
+					$query_object,
3566
+					'order',
3567
+					$query_params['order_by']
3568
+				);
3569
+				$order = isset($query_params['order'])
3570
+					? $this->_extract_order($query_params['order'])
3571
+					: 'DESC';
3572
+				$query_object->set_order_by_sql(
3573
+					" ORDER BY " . $this->_deduce_column_name_from_query_param($query_params['order_by']) . SP . $order
3574
+				);
3575
+			}
3576
+		}
3577
+		// if 'order_by' wasn't set, maybe they are just using 'order' on its own?
3578
+		if (
3579
+			! array_key_exists('order_by', $query_params)
3580
+			&& array_key_exists('order', $query_params)
3581
+			&& ! empty($query_params['order'])
3582
+		) {
3583
+			$pk_field = $this->get_primary_key_field();
3584
+			$order = $this->_extract_order($query_params['order']);
3585
+			$query_object->set_order_by_sql(" ORDER BY " . $pk_field->get_qualified_column() . SP . $order);
3586
+		}
3587
+		// set group by
3588
+		if (array_key_exists('group_by', $query_params)) {
3589
+			if (is_array($query_params['group_by'])) {
3590
+				// it's an array, so assume we'll be grouping by a bunch of stuff
3591
+				$group_by_array = array();
3592
+				foreach ($query_params['group_by'] as $field_name_to_group_by) {
3593
+					$group_by_array[] = $this->_deduce_column_name_from_query_param($field_name_to_group_by);
3594
+				}
3595
+				$query_object->set_group_by_sql(" GROUP BY " . implode(", ", $group_by_array));
3596
+			} elseif (! empty($query_params['group_by'])) {
3597
+				$query_object->set_group_by_sql(
3598
+					" GROUP BY " . $this->_deduce_column_name_from_query_param($query_params['group_by'])
3599
+				);
3600
+			}
3601
+		}
3602
+		// set having
3603
+		if (array_key_exists('having', $query_params) && $query_params['having']) {
3604
+			$query_object->set_having_sql($this->_construct_having_clause($query_params['having']));
3605
+		}
3606
+		// now, just verify they didn't pass anything wack
3607
+		foreach ($query_params as $query_key => $query_value) {
3608
+			if (! in_array($query_key, $this->_allowed_query_params, true)) {
3609
+				throw new EE_Error(
3610
+					sprintf(
3611
+						esc_html__(
3612
+							"You passed %s as a query parameter to %s, which is illegal! The allowed query parameters are %s",
3613
+							'event_espresso'
3614
+						),
3615
+						$query_key,
3616
+						get_class($this),
3617
+						//                      print_r( $this->_allowed_query_params, TRUE )
3618
+						implode(',', $this->_allowed_query_params)
3619
+					)
3620
+				);
3621
+			}
3622
+		}
3623
+		$main_model_join_sql = $query_object->get_main_model_join_sql();
3624
+		if (empty($main_model_join_sql)) {
3625
+			$query_object->set_main_model_join_sql($this->_construct_internal_join());
3626
+		}
3627
+		return $query_object;
3628
+	}
3629
+
3630
+
3631
+
3632
+	/**
3633
+	 * Gets the where conditions that should be imposed on the query based on the
3634
+	 * context (eg reading frontend, backend, edit or delete).
3635
+	 *
3636
+	 * @param string $context one of EEM_Base::valid_cap_contexts()
3637
+	 * @return array @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3638
+	 * @throws EE_Error
3639
+	 */
3640
+	public function caps_where_conditions($context = self::caps_read)
3641
+	{
3642
+		EEM_Base::verify_is_valid_cap_context($context);
3643
+		$cap_where_conditions = array();
3644
+		$cap_restrictions = $this->caps_missing($context);
3645
+		/**
3646
+		 * @var $cap_restrictions EE_Default_Where_Conditions[]
3647
+		 */
3648
+		foreach ($cap_restrictions as $cap => $restriction_if_no_cap) {
3649
+			$cap_where_conditions = array_replace_recursive(
3650
+				$cap_where_conditions,
3651
+				$restriction_if_no_cap->get_default_where_conditions()
3652
+			);
3653
+		}
3654
+		return apply_filters(
3655
+			'FHEE__EEM_Base__caps_where_conditions__return',
3656
+			$cap_where_conditions,
3657
+			$this,
3658
+			$context,
3659
+			$cap_restrictions
3660
+		);
3661
+	}
3662
+
3663
+
3664
+
3665
+	/**
3666
+	 * Verifies that $should_be_order_string is in $this->_allowed_order_values,
3667
+	 * otherwise throws an exception
3668
+	 *
3669
+	 * @param string $should_be_order_string
3670
+	 * @return string either ASC, asc, DESC or desc
3671
+	 * @throws EE_Error
3672
+	 */
3673
+	private function _extract_order($should_be_order_string)
3674
+	{
3675
+		if (in_array($should_be_order_string, $this->_allowed_order_values)) {
3676
+			return $should_be_order_string;
3677
+		}
3678
+		throw new EE_Error(
3679
+			sprintf(
3680
+				esc_html__(
3681
+					"While performing a query on '%s', tried to use '%s' as an order parameter. ",
3682
+					"event_espresso"
3683
+				),
3684
+				get_class($this),
3685
+				$should_be_order_string
3686
+			)
3687
+		);
3688
+	}
3689
+
3690
+
3691
+
3692
+	/**
3693
+	 * Looks at all the models which are included in this query, and asks each
3694
+	 * for their universal_where_params, and returns them in the same format as $query_params[0] (where),
3695
+	 * so they can be merged
3696
+	 *
3697
+	 * @param EE_Model_Query_Info_Carrier $query_info_carrier
3698
+	 * @param string                      $use_default_where_conditions can be 'none','other_models_only', or 'all'.
3699
+	 *                                                                  'none' means NO default where conditions will
3700
+	 *                                                                  be used AT ALL during this query.
3701
+	 *                                                                  'other_models_only' means default where
3702
+	 *                                                                  conditions from other models will be used, but
3703
+	 *                                                                  not for this primary model. 'all', the default,
3704
+	 *                                                                  means default where conditions will apply as
3705
+	 *                                                                  normal
3706
+	 * @param array                       $where_query_params           @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3707
+	 * @throws EE_Error
3708
+	 * @return array @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3709
+	 */
3710
+	private function _get_default_where_conditions_for_models_in_query(
3711
+		EE_Model_Query_Info_Carrier $query_info_carrier,
3712
+		$use_default_where_conditions = EEM_Base::default_where_conditions_all,
3713
+		$where_query_params = array()
3714
+	) {
3715
+		$allowed_used_default_where_conditions_values = EEM_Base::valid_default_where_conditions();
3716
+		if (! in_array($use_default_where_conditions, $allowed_used_default_where_conditions_values)) {
3717
+			throw new EE_Error(sprintf(
3718
+				esc_html__(
3719
+					"You passed an invalid value to the query parameter 'default_where_conditions' of '%s'. Allowed values are %s",
3720
+					"event_espresso"
3721
+				),
3722
+				$use_default_where_conditions,
3723
+				implode(", ", $allowed_used_default_where_conditions_values)
3724
+			));
3725
+		}
3726
+		$universal_query_params = array();
3727
+		if ($this->_should_use_default_where_conditions($use_default_where_conditions, true)) {
3728
+			$universal_query_params = $this->_get_default_where_conditions();
3729
+		} elseif ($this->_should_use_minimum_where_conditions($use_default_where_conditions, true)) {
3730
+			$universal_query_params = $this->_get_minimum_where_conditions();
3731
+		}
3732
+		foreach ($query_info_carrier->get_model_names_included() as $model_relation_path => $model_name) {
3733
+			$related_model = $this->get_related_model_obj($model_name);
3734
+			if ($this->_should_use_default_where_conditions($use_default_where_conditions, false)) {
3735
+				$related_model_universal_where_params = $related_model->_get_default_where_conditions($model_relation_path);
3736
+			} elseif ($this->_should_use_minimum_where_conditions($use_default_where_conditions, false)) {
3737
+				$related_model_universal_where_params = $related_model->_get_minimum_where_conditions($model_relation_path);
3738
+			} else {
3739
+				// we don't want to add full or even minimum default where conditions from this model, so just continue
3740
+				continue;
3741
+			}
3742
+			$overrides = $this->_override_defaults_or_make_null_friendly(
3743
+				$related_model_universal_where_params,
3744
+				$where_query_params,
3745
+				$related_model,
3746
+				$model_relation_path
3747
+			);
3748
+			$universal_query_params = EEH_Array::merge_arrays_and_overwrite_keys(
3749
+				$universal_query_params,
3750
+				$overrides
3751
+			);
3752
+		}
3753
+		return $universal_query_params;
3754
+	}
3755
+
3756
+
3757
+
3758
+	/**
3759
+	 * Determines whether or not we should use default where conditions for the model in question
3760
+	 * (this model, or other related models).
3761
+	 * Basically, we should use default where conditions on this model if they have requested to use them on all models,
3762
+	 * this model only, or to use minimum where conditions on all other models and normal where conditions on this one.
3763
+	 * We should use default where conditions on related models when they requested to use default where conditions
3764
+	 * on all models, or specifically just on other related models
3765
+	 * @param      $default_where_conditions_value
3766
+	 * @param bool $for_this_model false means this is for OTHER related models
3767
+	 * @return bool
3768
+	 */
3769
+	private function _should_use_default_where_conditions($default_where_conditions_value, $for_this_model = true)
3770
+	{
3771
+		return (
3772
+				   $for_this_model
3773
+				   && in_array(
3774
+					   $default_where_conditions_value,
3775
+					   array(
3776
+						   EEM_Base::default_where_conditions_all,
3777
+						   EEM_Base::default_where_conditions_this_only,
3778
+						   EEM_Base::default_where_conditions_minimum_others,
3779
+					   ),
3780
+					   true
3781
+				   )
3782
+			   )
3783
+			   || (
3784
+				   ! $for_this_model
3785
+				   && in_array(
3786
+					   $default_where_conditions_value,
3787
+					   array(
3788
+						   EEM_Base::default_where_conditions_all,
3789
+						   EEM_Base::default_where_conditions_others_only,
3790
+					   ),
3791
+					   true
3792
+				   )
3793
+			   );
3794
+	}
3795
+
3796
+	/**
3797
+	 * Determines whether or not we should use default minimum conditions for the model in question
3798
+	 * (this model, or other related models).
3799
+	 * Basically, we should use minimum where conditions on this model only if they requested all models to use minimum
3800
+	 * where conditions.
3801
+	 * We should use minimum where conditions on related models if they requested to use minimum where conditions
3802
+	 * on this model or others
3803
+	 * @param      $default_where_conditions_value
3804
+	 * @param bool $for_this_model false means this is for OTHER related models
3805
+	 * @return bool
3806
+	 */
3807
+	private function _should_use_minimum_where_conditions($default_where_conditions_value, $for_this_model = true)
3808
+	{
3809
+		return (
3810
+				   $for_this_model
3811
+				   && $default_where_conditions_value === EEM_Base::default_where_conditions_minimum_all
3812
+			   )
3813
+			   || (
3814
+				   ! $for_this_model
3815
+				   && in_array(
3816
+					   $default_where_conditions_value,
3817
+					   array(
3818
+						   EEM_Base::default_where_conditions_minimum_others,
3819
+						   EEM_Base::default_where_conditions_minimum_all,
3820
+					   ),
3821
+					   true
3822
+				   )
3823
+			   );
3824
+	}
3825
+
3826
+
3827
+	/**
3828
+	 * Checks if any of the defaults have been overridden. If there are any that AREN'T overridden,
3829
+	 * then we also add a special where condition which allows for that model's primary key
3830
+	 * to be null (which is important for JOINs. Eg, if you want to see all Events ordered by Venue's name,
3831
+	 * then Event's with NO Venue won't appear unless you allow VNU_ID to be NULL)
3832
+	 *
3833
+	 * @param array    $default_where_conditions
3834
+	 * @param array    $provided_where_conditions
3835
+	 * @param EEM_Base $model
3836
+	 * @param string   $model_relation_path like 'Transaction.Payment.'
3837
+	 * @return array @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3838
+	 * @throws EE_Error
3839
+	 */
3840
+	private function _override_defaults_or_make_null_friendly(
3841
+		$default_where_conditions,
3842
+		$provided_where_conditions,
3843
+		$model,
3844
+		$model_relation_path
3845
+	) {
3846
+		$null_friendly_where_conditions = array();
3847
+		$none_overridden = true;
3848
+		$or_condition_key_for_defaults = 'OR*' . get_class($model);
3849
+		foreach ($default_where_conditions as $key => $val) {
3850
+			if (isset($provided_where_conditions[ $key ])) {
3851
+				$none_overridden = false;
3852
+			} else {
3853
+				$null_friendly_where_conditions[ $or_condition_key_for_defaults ]['AND'][ $key ] = $val;
3854
+			}
3855
+		}
3856
+		if ($none_overridden && $default_where_conditions) {
3857
+			if ($model->has_primary_key_field()) {
3858
+				$null_friendly_where_conditions[ $or_condition_key_for_defaults ][ $model_relation_path
3859
+																				. "."
3860
+																				. $model->primary_key_name() ] = array('IS NULL');
3861
+			}/*else{
3862 3862
                 //@todo NO PK, use other defaults
3863 3863
             }*/
3864
-        }
3865
-        return $null_friendly_where_conditions;
3866
-    }
3867
-
3868
-
3869
-
3870
-    /**
3871
-     * Uses the _default_where_conditions_strategy set during __construct() to get
3872
-     * default where conditions on all get_all, update, and delete queries done by this model.
3873
-     * Use the same syntax as client code. Eg on the Event model, use array('Event.EVT_post_type'=>'esp_event'),
3874
-     * NOT array('Event_CPT.post_type'=>'esp_event').
3875
-     *
3876
-     * @param string $model_relation_path eg, path from Event to Payment is "Registration.Transaction.Payment."
3877
-     * @return array @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3878
-     */
3879
-    private function _get_default_where_conditions($model_relation_path = '')
3880
-    {
3881
-        if ($this->_ignore_where_strategy) {
3882
-            return array();
3883
-        }
3884
-        return $this->_default_where_conditions_strategy->get_default_where_conditions($model_relation_path);
3885
-    }
3886
-
3887
-
3888
-
3889
-    /**
3890
-     * Uses the _minimum_where_conditions_strategy set during __construct() to get
3891
-     * minimum where conditions on all get_all, update, and delete queries done by this model.
3892
-     * Use the same syntax as client code. Eg on the Event model, use array('Event.EVT_post_type'=>'esp_event'),
3893
-     * NOT array('Event_CPT.post_type'=>'esp_event').
3894
-     * Similar to _get_default_where_conditions
3895
-     *
3896
-     * @param string $model_relation_path eg, path from Event to Payment is "Registration.Transaction.Payment."
3897
-     * @return array @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3898
-     */
3899
-    protected function _get_minimum_where_conditions($model_relation_path = '')
3900
-    {
3901
-        if ($this->_ignore_where_strategy) {
3902
-            return array();
3903
-        }
3904
-        return $this->_minimum_where_conditions_strategy->get_default_where_conditions($model_relation_path);
3905
-    }
3906
-
3907
-
3908
-
3909
-    /**
3910
-     * Creates the string of SQL for the select part of a select query, everything behind SELECT and before FROM.
3911
-     * Eg, "Event.post_id, Event.post_name,Event_Detail.EVT_ID..."
3912
-     *
3913
-     * @param EE_Model_Query_Info_Carrier $model_query_info
3914
-     * @return string
3915
-     * @throws EE_Error
3916
-     */
3917
-    private function _construct_default_select_sql(EE_Model_Query_Info_Carrier $model_query_info)
3918
-    {
3919
-        $selects = $this->_get_columns_to_select_for_this_model();
3920
-        foreach (
3921
-            $model_query_info->get_model_names_included() as $model_relation_chain => $name_of_other_model_included
3922
-        ) {
3923
-            $other_model_included = $this->get_related_model_obj($name_of_other_model_included);
3924
-            $other_model_selects = $other_model_included->_get_columns_to_select_for_this_model($model_relation_chain);
3925
-            foreach ($other_model_selects as $key => $value) {
3926
-                $selects[] = $value;
3927
-            }
3928
-        }
3929
-        return implode(", ", $selects);
3930
-    }
3931
-
3932
-
3933
-
3934
-    /**
3935
-     * Gets an array of columns to select for this model, which are necessary for it to create its objects.
3936
-     * So that's going to be the columns for all the fields on the model
3937
-     *
3938
-     * @param string $model_relation_chain like 'Question.Question_Group.Event'
3939
-     * @return array numerically indexed, values are columns to select and rename, eg "Event.ID AS 'Event.ID'"
3940
-     */
3941
-    public function _get_columns_to_select_for_this_model($model_relation_chain = '')
3942
-    {
3943
-        $fields = $this->field_settings();
3944
-        $selects = array();
3945
-        $table_alias_with_model_relation_chain_prefix = EE_Model_Parser::extract_table_alias_model_relation_chain_prefix(
3946
-            $model_relation_chain,
3947
-            $this->get_this_model_name()
3948
-        );
3949
-        foreach ($fields as $field_obj) {
3950
-            $selects[] = $table_alias_with_model_relation_chain_prefix
3951
-                         . $field_obj->get_table_alias()
3952
-                         . "."
3953
-                         . $field_obj->get_table_column()
3954
-                         . " AS '"
3955
-                         . $table_alias_with_model_relation_chain_prefix
3956
-                         . $field_obj->get_table_alias()
3957
-                         . "."
3958
-                         . $field_obj->get_table_column()
3959
-                         . "'";
3960
-        }
3961
-        // make sure we are also getting the PKs of each table
3962
-        $tables = $this->get_tables();
3963
-        if (count($tables) > 1) {
3964
-            foreach ($tables as $table_obj) {
3965
-                $qualified_pk_column = $table_alias_with_model_relation_chain_prefix
3966
-                                       . $table_obj->get_fully_qualified_pk_column();
3967
-                if (! in_array($qualified_pk_column, $selects)) {
3968
-                    $selects[] = "$qualified_pk_column AS '$qualified_pk_column'";
3969
-                }
3970
-            }
3971
-        }
3972
-        return $selects;
3973
-    }
3974
-
3975
-
3976
-
3977
-    /**
3978
-     * Given a $query_param like 'Registration.Transaction.TXN_ID', pops off 'Registration.',
3979
-     * gets the join statement for it; gets the data types for it; and passes the remaining 'Transaction.TXN_ID'
3980
-     * onto its related Transaction object to do the same. Returns an EE_Join_And_Data_Types object which contains the
3981
-     * SQL for joining, and the data types
3982
-     *
3983
-     * @param null|string                 $original_query_param
3984
-     * @param string                      $query_param          like Registration.Transaction.TXN_ID
3985
-     * @param EE_Model_Query_Info_Carrier $passed_in_query_info
3986
-     * @param    string                   $query_param_type     like Registration.Transaction.TXN_ID
3987
-     *                                                          or 'PAY_ID'. Otherwise, we don't expect there to be a
3988
-     *                                                          column name. We only want model names, eg 'Event.Venue'
3989
-     *                                                          or 'Registration's
3990
-     * @param string                      $original_query_param what it originally was (eg
3991
-     *                                                          Registration.Transaction.TXN_ID). If null, we assume it
3992
-     *                                                          matches $query_param
3993
-     * @throws EE_Error
3994
-     * @return void only modifies the EEM_Related_Model_Info_Carrier passed into it
3995
-     */
3996
-    private function _extract_related_model_info_from_query_param(
3997
-        $query_param,
3998
-        EE_Model_Query_Info_Carrier $passed_in_query_info,
3999
-        $query_param_type,
4000
-        $original_query_param = null
4001
-    ) {
4002
-        if ($original_query_param === null) {
4003
-            $original_query_param = $query_param;
4004
-        }
4005
-        $query_param = $this->_remove_stars_and_anything_after_from_condition_query_param_key($query_param);
4006
-        // check to see if we have a field on this model
4007
-        $this_model_fields = $this->field_settings(true);
4008
-        if (array_key_exists($query_param, $this_model_fields)) {
4009
-            $field_is_allowed = in_array(
4010
-                $query_param_type,
4011
-                [0, 'where', 'having', 'order_by', 'group_by', 'order', 'custom_selects'],
4012
-                true
4013
-            );
4014
-            if ($field_is_allowed) {
4015
-                return;
4016
-            }
4017
-            throw new EE_Error(
4018
-                sprintf(
4019
-                    esc_html__(
4020
-                        "Using a field name (%s) on model %s is not allowed on this query param type '%s'. Original query param was %s",
4021
-                        "event_espresso"
4022
-                    ),
4023
-                    $query_param,
4024
-                    get_class($this),
4025
-                    $query_param_type,
4026
-                    $original_query_param
4027
-                )
4028
-            );
4029
-        }
4030
-        // check if this is a special logic query param
4031
-        if (in_array($query_param, $this->_logic_query_param_keys, true)) {
4032
-            $operator_is_allowed = in_array($query_param_type, ['where', 'having', 0, 'custom_selects'], true);
4033
-            if ($operator_is_allowed) {
4034
-                return;
4035
-            }
4036
-            throw new EE_Error(
4037
-                sprintf(
4038
-                    esc_html__(
4039
-                        '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',
4040
-                        'event_espresso'
4041
-                    ),
4042
-                    implode('", "', $this->_logic_query_param_keys),
4043
-                    $query_param,
4044
-                    get_class($this),
4045
-                    '<br />',
4046
-                    "\t"
4047
-                    . ' $passed_in_query_info = <pre>'
4048
-                    . print_r($passed_in_query_info, true)
4049
-                    . '</pre>'
4050
-                    . "\n\t"
4051
-                    . ' $query_param_type = '
4052
-                    . $query_param_type
4053
-                    . "\n\t"
4054
-                    . ' $original_query_param = '
4055
-                    . $original_query_param
4056
-                )
4057
-            );
4058
-        }
4059
-        // check if it's a custom selection
4060
-        if (
4061
-            $this->_custom_selections instanceof CustomSelects
4062
-            && in_array($query_param, $this->_custom_selections->columnAliases(), true)
4063
-        ) {
4064
-            return;
4065
-        }
4066
-        // check if has a model name at the beginning
4067
-        // and
4068
-        // check if it's a field on a related model
4069
-        if (
4070
-            $this->extractJoinModelFromQueryParams(
4071
-                $passed_in_query_info,
4072
-                $query_param,
4073
-                $original_query_param,
4074
-                $query_param_type
4075
-            )
4076
-        ) {
4077
-            return;
4078
-        }
4079
-
4080
-        // ok so $query_param didn't start with a model name
4081
-        // and we previously confirmed it wasn't a logic query param or field on the current model
4082
-        // it's wack, that's what it is
4083
-        throw new EE_Error(
4084
-            sprintf(
4085
-                esc_html__(
4086
-                    "There is no model named '%s' related to %s. Query param type is %s and original query param is %s",
4087
-                    "event_espresso"
4088
-                ),
4089
-                $query_param,
4090
-                get_class($this),
4091
-                $query_param_type,
4092
-                $original_query_param
4093
-            )
4094
-        );
4095
-    }
4096
-
4097
-
4098
-    /**
4099
-     * Extracts any possible join model information from the provided possible_join_string.
4100
-     * This method will read the provided $possible_join_string value and determine if there are any possible model join
4101
-     * parts that should be added to the query.
4102
-     *
4103
-     * @param EE_Model_Query_Info_Carrier $query_info_carrier
4104
-     * @param string                      $possible_join_string  Such as Registration.REG_ID, or Registration
4105
-     * @param null|string                 $original_query_param
4106
-     * @param string                      $query_parameter_type  The type for the source of the $possible_join_string
4107
-     *                                                           ('where', 'order_by', 'group_by', 'custom_selects' etc.)
4108
-     * @return bool  returns true if a join was added and false if not.
4109
-     * @throws EE_Error
4110
-     */
4111
-    private function extractJoinModelFromQueryParams(
4112
-        EE_Model_Query_Info_Carrier $query_info_carrier,
4113
-        $possible_join_string,
4114
-        $original_query_param,
4115
-        $query_parameter_type
4116
-    ) {
4117
-        foreach ($this->_model_relations as $valid_related_model_name => $relation_obj) {
4118
-            if (strpos($possible_join_string, $valid_related_model_name . ".") === 0) {
4119
-                $this->_add_join_to_model($valid_related_model_name, $query_info_carrier, $original_query_param);
4120
-                $possible_join_string = substr($possible_join_string, strlen($valid_related_model_name . "."));
4121
-                if ($possible_join_string === '') {
4122
-                    // nothing left to $query_param
4123
-                    // we should actually end in a field name, not a model like this!
4124
-                    throw new EE_Error(
4125
-                        sprintf(
4126
-                            esc_html__(
4127
-                                "Query param '%s' (of type %s on model %s) shouldn't end on a period (.) ",
4128
-                                "event_espresso"
4129
-                            ),
4130
-                            $possible_join_string,
4131
-                            $query_parameter_type,
4132
-                            get_class($this),
4133
-                            $valid_related_model_name
4134
-                        )
4135
-                    );
4136
-                }
4137
-                $related_model_obj = $this->get_related_model_obj($valid_related_model_name);
4138
-                $related_model_obj->_extract_related_model_info_from_query_param(
4139
-                    $possible_join_string,
4140
-                    $query_info_carrier,
4141
-                    $query_parameter_type,
4142
-                    $original_query_param
4143
-                );
4144
-                return true;
4145
-            }
4146
-            if ($possible_join_string === $valid_related_model_name) {
4147
-                $this->_add_join_to_model(
4148
-                    $valid_related_model_name,
4149
-                    $query_info_carrier,
4150
-                    $original_query_param
4151
-                );
4152
-                return true;
4153
-            }
4154
-        }
4155
-        return false;
4156
-    }
4157
-
4158
-
4159
-    /**
4160
-     * Extracts related models from Custom Selects and sets up any joins for those related models.
4161
-     * @param EE_Model_Query_Info_Carrier $query_info_carrier
4162
-     * @throws EE_Error
4163
-     */
4164
-    private function extractRelatedModelsFromCustomSelects(EE_Model_Query_Info_Carrier $query_info_carrier)
4165
-    {
4166
-        if (
4167
-            $this->_custom_selections instanceof CustomSelects
4168
-            && (
4169
-                $this->_custom_selections->type() === CustomSelects::TYPE_STRUCTURED
4170
-                || $this->_custom_selections->type() == CustomSelects::TYPE_COMPLEX
4171
-            )
4172
-        ) {
4173
-            $original_selects = $this->_custom_selections->originalSelects();
4174
-            foreach ($original_selects as $alias => $select_configuration) {
4175
-                $this->extractJoinModelFromQueryParams(
4176
-                    $query_info_carrier,
4177
-                    $select_configuration[0],
4178
-                    $select_configuration[0],
4179
-                    'custom_selects'
4180
-                );
4181
-            }
4182
-        }
4183
-    }
4184
-
4185
-
4186
-
4187
-    /**
4188
-     * Privately used by _extract_related_model_info_from_query_param to add a join to $model_name
4189
-     * and store it on $passed_in_query_info
4190
-     *
4191
-     * @param string                      $model_name
4192
-     * @param EE_Model_Query_Info_Carrier $passed_in_query_info
4193
-     * @param string                      $original_query_param used to extract the relation chain between the queried
4194
-     *                                                          model and $model_name. Eg, if we are querying Event,
4195
-     *                                                          and are adding a join to 'Payment' with the original
4196
-     *                                                          query param key
4197
-     *                                                          'Registration.Transaction.Payment.PAY_amount', we want
4198
-     *                                                          to extract 'Registration.Transaction.Payment', in case
4199
-     *                                                          Payment wants to add default query params so that it
4200
-     *                                                          will know what models to prepend onto its default query
4201
-     *                                                          params or in case it wants to rename tables (in case
4202
-     *                                                          there are multiple joins to the same table)
4203
-     * @return void
4204
-     * @throws EE_Error
4205
-     */
4206
-    private function _add_join_to_model(
4207
-        $model_name,
4208
-        EE_Model_Query_Info_Carrier $passed_in_query_info,
4209
-        $original_query_param
4210
-    ) {
4211
-        $relation_obj = $this->related_settings_for($model_name);
4212
-        $model_relation_chain = EE_Model_Parser::extract_model_relation_chain($model_name, $original_query_param);
4213
-        // check if the relation is HABTM, because then we're essentially doing two joins
4214
-        // If so, join first to the JOIN table, and add its data types, and then continue as normal
4215
-        if ($relation_obj instanceof EE_HABTM_Relation) {
4216
-            $join_model_obj = $relation_obj->get_join_model();
4217
-            // replace the model specified with the join model for this relation chain, whi
4218
-            $relation_chain_to_join_model = EE_Model_Parser::replace_model_name_with_join_model_name_in_model_relation_chain(
4219
-                $model_name,
4220
-                $join_model_obj->get_this_model_name(),
4221
-                $model_relation_chain
4222
-            );
4223
-            $passed_in_query_info->merge(
4224
-                new EE_Model_Query_Info_Carrier(
4225
-                    array($relation_chain_to_join_model => $join_model_obj->get_this_model_name()),
4226
-                    $relation_obj->get_join_to_intermediate_model_statement($relation_chain_to_join_model)
4227
-                )
4228
-            );
4229
-        }
4230
-        // now just join to the other table pointed to by the relation object, and add its data types
4231
-        $passed_in_query_info->merge(
4232
-            new EE_Model_Query_Info_Carrier(
4233
-                array($model_relation_chain => $model_name),
4234
-                $relation_obj->get_join_statement($model_relation_chain)
4235
-            )
4236
-        );
4237
-    }
4238
-
4239
-
4240
-
4241
-    /**
4242
-     * Constructs SQL for where clause, like "WHERE Event.ID = 23 AND Transaction.amount > 100" etc.
4243
-     *
4244
-     * @param array $where_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
4245
-     * @return string of SQL
4246
-     * @throws EE_Error
4247
-     */
4248
-    private function _construct_where_clause($where_params)
4249
-    {
4250
-        $SQL = $this->_construct_condition_clause_recursive($where_params, ' AND ');
4251
-        if ($SQL) {
4252
-            return " WHERE " . $SQL;
4253
-        }
4254
-        return '';
4255
-    }
4256
-
4257
-
4258
-
4259
-    /**
4260
-     * Just like the _construct_where_clause, except prepends 'HAVING' instead of 'WHERE',
4261
-     * and should be passed HAVING parameters, not WHERE parameters
4262
-     *
4263
-     * @param array $having_params
4264
-     * @return string
4265
-     * @throws EE_Error
4266
-     */
4267
-    private function _construct_having_clause($having_params)
4268
-    {
4269
-        $SQL = $this->_construct_condition_clause_recursive($having_params, ' AND ');
4270
-        if ($SQL) {
4271
-            return " HAVING " . $SQL;
4272
-        }
4273
-        return '';
4274
-    }
4275
-
4276
-
4277
-    /**
4278
-     * Used for creating nested WHERE conditions. Eg "WHERE ! (Event.ID = 3 OR ( Event_Meta.meta_key = 'bob' AND
4279
-     * Event_Meta.meta_value = 'foo'))"
4280
-     *
4281
-     * @param array  $where_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
4282
-     * @param string $glue         joins each subclause together. Should really only be " AND " or " OR "...
4283
-     * @throws EE_Error
4284
-     * @return string of SQL
4285
-     */
4286
-    private function _construct_condition_clause_recursive($where_params, $glue = ' AND')
4287
-    {
4288
-        $where_clauses = array();
4289
-        foreach ($where_params as $query_param => $op_and_value_or_sub_condition) {
4290
-            $query_param = $this->_remove_stars_and_anything_after_from_condition_query_param_key($query_param);
4291
-            if (in_array($query_param, $this->_logic_query_param_keys, true)) {
4292
-                switch ($query_param) {
4293
-                    case 'not':
4294
-                    case 'NOT':
4295
-                        $where_clauses[] = "! ("
4296
-                                           . $this->_construct_condition_clause_recursive(
4297
-                                               $op_and_value_or_sub_condition,
4298
-                                               $glue
4299
-                                           )
4300
-                                           . ")";
4301
-                        break;
4302
-                    case 'and':
4303
-                    case 'AND':
4304
-                        $where_clauses[] = " ("
4305
-                                           . $this->_construct_condition_clause_recursive(
4306
-                                               $op_and_value_or_sub_condition,
4307
-                                               ' AND '
4308
-                                           )
4309
-                                           . ")";
4310
-                        break;
4311
-                    case 'or':
4312
-                    case 'OR':
4313
-                        $where_clauses[] = " ("
4314
-                                           . $this->_construct_condition_clause_recursive(
4315
-                                               $op_and_value_or_sub_condition,
4316
-                                               ' OR '
4317
-                                           )
4318
-                                           . ")";
4319
-                        break;
4320
-                }
4321
-            } else {
4322
-                $field_obj = $this->_deduce_field_from_query_param($query_param);
4323
-                // if it's not a normal field, maybe it's a custom selection?
4324
-                if (! $field_obj) {
4325
-                    if ($this->_custom_selections instanceof CustomSelects) {
4326
-                        $field_obj = $this->_custom_selections->getDataTypeForAlias($query_param);
4327
-                    } else {
4328
-                        throw new EE_Error(sprintf(esc_html__(
4329
-                            "%s is neither a valid model field name, nor a custom selection",
4330
-                            "event_espresso"
4331
-                        ), $query_param));
4332
-                    }
4333
-                }
4334
-                $op_and_value_sql = $this->_construct_op_and_value($op_and_value_or_sub_condition, $field_obj);
4335
-                $where_clauses[] = $this->_deduce_column_name_from_query_param($query_param) . SP . $op_and_value_sql;
4336
-            }
4337
-        }
4338
-        return $where_clauses ? implode($glue, $where_clauses) : '';
4339
-    }
4340
-
4341
-
4342
-
4343
-    /**
4344
-     * Takes the input parameter and extract the table name (alias) and column name
4345
-     *
4346
-     * @param string $query_param like Registration.Transaction.TXN_ID, Event.Datetime.start_time, or REG_ID
4347
-     * @throws EE_Error
4348
-     * @return string table alias and column name for SQL, eg "Transaction.TXN_ID"
4349
-     */
4350
-    private function _deduce_column_name_from_query_param($query_param)
4351
-    {
4352
-        $field = $this->_deduce_field_from_query_param($query_param);
4353
-        if ($field) {
4354
-            $table_alias_prefix = EE_Model_Parser::extract_table_alias_model_relation_chain_from_query_param(
4355
-                $field->get_model_name(),
4356
-                $query_param
4357
-            );
4358
-            return $table_alias_prefix . $field->get_qualified_column();
4359
-        }
4360
-        if (
4361
-            $this->_custom_selections instanceof CustomSelects
4362
-            && in_array($query_param, $this->_custom_selections->columnAliases(), true)
4363
-        ) {
4364
-            // maybe it's custom selection item?
4365
-            // if so, just use it as the "column name"
4366
-            return $query_param;
4367
-        }
4368
-        $custom_select_aliases = $this->_custom_selections instanceof CustomSelects
4369
-            ? implode(',', $this->_custom_selections->columnAliases())
4370
-            : '';
4371
-        throw new EE_Error(
4372
-            sprintf(
4373
-                esc_html__(
4374
-                    "%s is not a valid field on this model, nor a custom selection (%s)",
4375
-                    "event_espresso"
4376
-                ),
4377
-                $query_param,
4378
-                $custom_select_aliases
4379
-            )
4380
-        );
4381
-    }
4382
-
4383
-
4384
-
4385
-    /**
4386
-     * Removes the * and anything after it from the condition query param key. It is useful to add the * to condition
4387
-     * query param keys (eg, 'OR*', 'EVT_ID') in order for the array keys to still be unique, so that they don't get
4388
-     * overwritten Takes a string like 'Event.EVT_ID*', 'TXN_total**', 'OR*1st', and 'DTT_reg_start*foobar' to
4389
-     * 'Event.EVT_ID', 'TXN_total', 'OR', and 'DTT_reg_start', respectively.
4390
-     *
4391
-     * @param string $condition_query_param_key
4392
-     * @return string
4393
-     */
4394
-    private function _remove_stars_and_anything_after_from_condition_query_param_key($condition_query_param_key)
4395
-    {
4396
-        $pos_of_star = strpos($condition_query_param_key, '*');
4397
-        if ($pos_of_star === false) {
4398
-            return $condition_query_param_key;
4399
-        }
4400
-        $condition_query_param_sans_star = substr($condition_query_param_key, 0, $pos_of_star);
4401
-        return $condition_query_param_sans_star;
4402
-    }
4403
-
4404
-
4405
-
4406
-    /**
4407
-     * creates the SQL for the operator and the value in a WHERE clause, eg "< 23" or "LIKE '%monkey%'"
4408
-     *
4409
-     * @param                            mixed      array | string    $op_and_value
4410
-     * @param EE_Model_Field_Base|string $field_obj . If string, should be one of EEM_Base::_valid_wpdb_data_types
4411
-     * @throws EE_Error
4412
-     * @return string
4413
-     */
4414
-    private function _construct_op_and_value($op_and_value, $field_obj)
4415
-    {
4416
-        if (is_array($op_and_value)) {
4417
-            $operator = isset($op_and_value[0]) ? $this->_prepare_operator_for_sql($op_and_value[0]) : null;
4418
-            if (! $operator) {
4419
-                $php_array_like_string = array();
4420
-                foreach ($op_and_value as $key => $value) {
4421
-                    $php_array_like_string[] = "$key=>$value";
4422
-                }
4423
-                throw new EE_Error(
4424
-                    sprintf(
4425
-                        esc_html__(
4426
-                            "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))",
4427
-                            "event_espresso"
4428
-                        ),
4429
-                        implode(",", $php_array_like_string)
4430
-                    )
4431
-                );
4432
-            }
4433
-            $value = isset($op_and_value[1]) ? $op_and_value[1] : null;
4434
-        } else {
4435
-            $operator = '=';
4436
-            $value = $op_and_value;
4437
-        }
4438
-        // check to see if the value is actually another field
4439
-        if (is_array($op_and_value) && isset($op_and_value[2]) && $op_and_value[2] == true) {
4440
-            return $operator . SP . $this->_deduce_column_name_from_query_param($value);
4441
-        }
4442
-        if (in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4443
-            // in this case, the value should be an array, or at least a comma-separated list
4444
-            // it will need to handle a little differently
4445
-            $cleaned_value = $this->_construct_in_value($value, $field_obj);
4446
-            // note: $cleaned_value has already been run through $wpdb->prepare()
4447
-            return $operator . SP . $cleaned_value;
4448
-        }
4449
-        if (in_array($operator, $this->valid_between_style_operators()) && is_array($value)) {
4450
-            // the value should be an array with count of two.
4451
-            if (count($value) !== 2) {
4452
-                throw new EE_Error(
4453
-                    sprintf(
4454
-                        esc_html__(
4455
-                            "The '%s' operator must be used with an array of values and there must be exactly TWO values in that array.",
4456
-                            'event_espresso'
4457
-                        ),
4458
-                        "BETWEEN"
4459
-                    )
4460
-                );
4461
-            }
4462
-            $cleaned_value = $this->_construct_between_value($value, $field_obj);
4463
-            return $operator . SP . $cleaned_value;
4464
-        }
4465
-        if (in_array($operator, $this->valid_null_style_operators())) {
4466
-            if ($value !== null) {
4467
-                throw new EE_Error(
4468
-                    sprintf(
4469
-                        esc_html__(
4470
-                            "You attempted to give a value  (%s) while using a NULL-style operator (%s). That isn't valid",
4471
-                            "event_espresso"
4472
-                        ),
4473
-                        $value,
4474
-                        $operator
4475
-                    )
4476
-                );
4477
-            }
4478
-            return $operator;
4479
-        }
4480
-        if (in_array($operator, $this->valid_like_style_operators()) && ! is_array($value)) {
4481
-            // if the operator is 'LIKE', we want to allow percent signs (%) and not
4482
-            // remove other junk. So just treat it as a string.
4483
-            return $operator . SP . $this->_wpdb_prepare_using_field($value, '%s');
4484
-        }
4485
-        if (! in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4486
-            return $operator . SP . $this->_wpdb_prepare_using_field($value, $field_obj);
4487
-        }
4488
-        if (in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4489
-            throw new EE_Error(
4490
-                sprintf(
4491
-                    esc_html__(
4492
-                        "Operator '%s' must be used with an array of values, eg 'Registration.REG_ID' => array('%s',array(1,2,3))",
4493
-                        'event_espresso'
4494
-                    ),
4495
-                    $operator,
4496
-                    $operator
4497
-                )
4498
-            );
4499
-        }
4500
-        if (! in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4501
-            throw new EE_Error(
4502
-                sprintf(
4503
-                    esc_html__(
4504
-                        "Operator '%s' must be used with a single value, not an array. Eg 'Registration.REG_ID => array('%s',23))",
4505
-                        'event_espresso'
4506
-                    ),
4507
-                    $operator,
4508
-                    $operator
4509
-                )
4510
-            );
4511
-        }
4512
-        throw new EE_Error(
4513
-            sprintf(
4514
-                esc_html__(
4515
-                    "It appears you've provided some totally invalid query parameters. Operator and value were:'%s', which isn't right at all",
4516
-                    "event_espresso"
4517
-                ),
4518
-                http_build_query($op_and_value)
4519
-            )
4520
-        );
4521
-    }
4522
-
4523
-
4524
-
4525
-    /**
4526
-     * Creates the operands to be used in a BETWEEN query, eg "'2014-12-31 20:23:33' AND '2015-01-23 12:32:54'"
4527
-     *
4528
-     * @param array                      $values
4529
-     * @param EE_Model_Field_Base|string $field_obj if string, it should be the datatype to be used when querying, eg
4530
-     *                                              '%s'
4531
-     * @return string
4532
-     * @throws EE_Error
4533
-     */
4534
-    public function _construct_between_value($values, $field_obj)
4535
-    {
4536
-        $cleaned_values = array();
4537
-        foreach ($values as $value) {
4538
-            $cleaned_values[] = $this->_wpdb_prepare_using_field($value, $field_obj);
4539
-        }
4540
-        return $cleaned_values[0] . " AND " . $cleaned_values[1];
4541
-    }
4542
-
4543
-
4544
-    /**
4545
-     * Takes an array or a comma-separated list of $values and cleans them
4546
-     * according to $data_type using $wpdb->prepare, and then makes the list a
4547
-     * string surrounded by ( and ). Eg, _construct_in_value(array(1,2,3),'%d') would
4548
-     * return '(1,2,3)'; _construct_in_value("1,2,hack",'%d') would return '(1,2,1)' (assuming
4549
-     * I'm right that a string, when interpreted as a digit, becomes a 1. It might become a 0)
4550
-     *
4551
-     * @param mixed                      $values    array or comma-separated string
4552
-     * @param EE_Model_Field_Base|string $field_obj if string, it should be a wpdb data type like '%s', or '%d'
4553
-     * @return string of SQL to follow an 'IN' or 'NOT IN' operator
4554
-     * @throws EE_Error
4555
-     */
4556
-    public function _construct_in_value($values, $field_obj)
4557
-    {
4558
-        $prepped = [];
4559
-        // check if the value is a CSV list
4560
-        if (is_string($values)) {
4561
-            // in which case, turn it into an array
4562
-            $values = explode(',', $values);
4563
-        }
4564
-        // make sure we only have one of each value in the list
4565
-        $values = array_unique($values);
4566
-        foreach ($values as $value) {
4567
-            $prepped[] = $this->_wpdb_prepare_using_field($value, $field_obj);
4568
-        }
4569
-        // we would just LOVE to leave $cleaned_values as an empty array, and return the value as "()",
4570
-        // but unfortunately that's invalid SQL. So instead we return a string which we KNOW will evaluate to be the empty set
4571
-        // which is effectively equivalent to returning "()". We don't return "(0)" because that only works for auto-incrementing columns
4572
-        if (empty($prepped)) {
4573
-            $all_fields = $this->field_settings();
4574
-            $first_field    = reset($all_fields);
4575
-            $main_table = $this->_get_main_table();
4576
-            $prepped[]  = "SELECT {$first_field->get_table_column()} FROM {$main_table->get_table_name()} WHERE FALSE";
4577
-        }
4578
-        return '(' . implode(',', $prepped) . ')';
4579
-    }
4580
-
4581
-
4582
-
4583
-    /**
4584
-     * @param mixed                      $value
4585
-     * @param EE_Model_Field_Base|string $field_obj if string it should be a wpdb data type like '%d'
4586
-     * @throws EE_Error
4587
-     * @return false|null|string
4588
-     */
4589
-    private function _wpdb_prepare_using_field($value, $field_obj)
4590
-    {
4591
-        /** @type WPDB $wpdb */
4592
-        global $wpdb;
4593
-        if ($field_obj instanceof EE_Model_Field_Base) {
4594
-            return $wpdb->prepare(
4595
-                $field_obj->get_wpdb_data_type(),
4596
-                $this->_prepare_value_for_use_in_db($value, $field_obj)
4597
-            );
4598
-        } //$field_obj should really just be a data type
4599
-        if (! in_array($field_obj, $this->_valid_wpdb_data_types)) {
4600
-            throw new EE_Error(
4601
-                sprintf(
4602
-                    esc_html__("%s is not a valid wpdb datatype. Valid ones are %s", "event_espresso"),
4603
-                    $field_obj,
4604
-                    implode(",", $this->_valid_wpdb_data_types)
4605
-                )
4606
-            );
4607
-        }
4608
-        return $wpdb->prepare($field_obj, $value);
4609
-    }
4610
-
4611
-
4612
-
4613
-    /**
4614
-     * Takes the input parameter and finds the model field that it indicates.
4615
-     *
4616
-     * @param string $query_param_name like Registration.Transaction.TXN_ID, Event.Datetime.start_time, or REG_ID
4617
-     * @throws EE_Error
4618
-     * @return EE_Model_Field_Base
4619
-     */
4620
-    protected function _deduce_field_from_query_param($query_param_name)
4621
-    {
4622
-        // ok, now proceed with deducing which part is the model's name, and which is the field's name
4623
-        // which will help us find the database table and column
4624
-        $query_param_parts = explode(".", $query_param_name);
4625
-        if (empty($query_param_parts)) {
4626
-            throw new EE_Error(sprintf(esc_html__(
4627
-                "_extract_column_name is empty when trying to extract column and table name from %s",
4628
-                'event_espresso'
4629
-            ), $query_param_name));
4630
-        }
4631
-        $number_of_parts = count($query_param_parts);
4632
-        $last_query_param_part = $query_param_parts[ count($query_param_parts) - 1 ];
4633
-        if ($number_of_parts === 1) {
4634
-            $field_name = $last_query_param_part;
4635
-            $model_obj = $this;
4636
-        } else {// $number_of_parts >= 2
4637
-            // the last part is the column name, and there are only 2parts. therefore...
4638
-            $field_name = $last_query_param_part;
4639
-            $model_obj = $this->get_related_model_obj($query_param_parts[ $number_of_parts - 2 ]);
4640
-        }
4641
-        try {
4642
-            return $model_obj->field_settings_for($field_name);
4643
-        } catch (EE_Error $e) {
4644
-            return null;
4645
-        }
4646
-    }
4647
-
4648
-
4649
-
4650
-    /**
4651
-     * Given a field's name (ie, a key in $this->field_settings()), uses the EE_Model_Field object to get the table's
4652
-     * alias and column which corresponds to it
4653
-     *
4654
-     * @param string $field_name
4655
-     * @throws EE_Error
4656
-     * @return string
4657
-     */
4658
-    public function _get_qualified_column_for_field($field_name)
4659
-    {
4660
-        $all_fields = $this->field_settings();
4661
-        $field = isset($all_fields[ $field_name ]) ? $all_fields[ $field_name ] : false;
4662
-        if ($field) {
4663
-            return $field->get_qualified_column();
4664
-        }
4665
-        throw new EE_Error(
4666
-            sprintf(
4667
-                esc_html__(
4668
-                    "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.",
4669
-                    'event_espresso'
4670
-                ),
4671
-                $field_name,
4672
-                get_class($this)
4673
-            )
4674
-        );
4675
-    }
4676
-
4677
-
4678
-
4679
-    /**
4680
-     * similar to \EEM_Base::_get_qualified_column_for_field() but returns an array with data for ALL fields.
4681
-     * Example usage:
4682
-     * EEM_Ticket::instance()->get_all_wpdb_results(
4683
-     *      array(),
4684
-     *      ARRAY_A,
4685
-     *      EEM_Ticket::instance()->get_qualified_columns_for_all_fields()
4686
-     *  );
4687
-     * is equivalent to
4688
-     *  EEM_Ticket::instance()->get_all_wpdb_results( array(), ARRAY_A, '*' );
4689
-     * and
4690
-     *  EEM_Event::instance()->get_all_wpdb_results(
4691
-     *      array(
4692
-     *          array(
4693
-     *              'Datetime.Ticket.TKT_ID' => array( '<', 100 ),
4694
-     *          ),
4695
-     *          ARRAY_A,
4696
-     *          implode(
4697
-     *              ', ',
4698
-     *              array_merge(
4699
-     *                  EEM_Event::instance()->get_qualified_columns_for_all_fields( '', false ),
4700
-     *                  EEM_Ticket::instance()->get_qualified_columns_for_all_fields( 'Datetime', false )
4701
-     *              )
4702
-     *          )
4703
-     *      )
4704
-     *  );
4705
-     * selects rows from the database, selecting all the event and ticket columns, where the ticket ID is below 100
4706
-     *
4707
-     * @param string $model_relation_chain        the chain of models used to join between the model you want to query
4708
-     *                                            and the one whose fields you are selecting for example: when querying
4709
-     *                                            tickets model and selecting fields from the tickets model you would
4710
-     *                                            leave this parameter empty, because no models are needed to join
4711
-     *                                            between the queried model and the selected one. Likewise when
4712
-     *                                            querying the datetime model and selecting fields from the tickets
4713
-     *                                            model, it would also be left empty, because there is a direct
4714
-     *                                            relation from datetimes to tickets, so no model is needed to join
4715
-     *                                            them together. However, when querying from the event model and
4716
-     *                                            selecting fields from the ticket model, you should provide the string
4717
-     *                                            'Datetime', indicating that the event model must first join to the
4718
-     *                                            datetime model in order to find its relation to ticket model.
4719
-     *                                            Also, when querying from the venue model and selecting fields from
4720
-     *                                            the ticket model, you should provide the string 'Event.Datetime',
4721
-     *                                            indicating you need to join the venue model to the event model,
4722
-     *                                            to the datetime model, in order to find its relation to the ticket model.
4723
-     *                                            This string is used to deduce the prefix that gets added onto the
4724
-     *                                            models' tables qualified columns
4725
-     * @param bool   $return_string               if true, will return a string with qualified column names separated
4726
-     *                                            by ', ' if false, will simply return a numerically indexed array of
4727
-     *                                            qualified column names
4728
-     * @return array|string
4729
-     */
4730
-    public function get_qualified_columns_for_all_fields($model_relation_chain = '', $return_string = true)
4731
-    {
4732
-        $table_prefix = str_replace('.', '__', $model_relation_chain) . (empty($model_relation_chain) ? '' : '__');
4733
-        $qualified_columns = array();
4734
-        foreach ($this->field_settings() as $field_name => $field) {
4735
-            $qualified_columns[] = $table_prefix . $field->get_qualified_column();
4736
-        }
4737
-        return $return_string ? implode(', ', $qualified_columns) : $qualified_columns;
4738
-    }
4739
-
4740
-
4741
-
4742
-    /**
4743
-     * constructs the select use on special limit joins
4744
-     * NOTE: for now this has only been tested and will work when the  table alias is for the PRIMARY table. Although
4745
-     * its setup so the select query will be setup on and just doing the special select join off of the primary table
4746
-     * (as that is typically where the limits would be set).
4747
-     *
4748
-     * @param  string       $table_alias The table the select is being built for
4749
-     * @param  mixed|string $limit       The limit for this select
4750
-     * @return string                The final select join element for the query.
4751
-     */
4752
-    public function _construct_limit_join_select($table_alias, $limit)
4753
-    {
4754
-        $SQL = '';
4755
-        foreach ($this->_tables as $table_obj) {
4756
-            if ($table_obj instanceof EE_Primary_Table) {
4757
-                $SQL .= $table_alias === $table_obj->get_table_alias()
4758
-                    ? $table_obj->get_select_join_limit($limit)
4759
-                    : SP . $table_obj->get_table_name() . " AS " . $table_obj->get_table_alias() . SP;
4760
-            } elseif ($table_obj instanceof EE_Secondary_Table) {
4761
-                $SQL .= $table_alias === $table_obj->get_table_alias()
4762
-                    ? $table_obj->get_select_join_limit_join($limit)
4763
-                    : SP . $table_obj->get_join_sql($table_alias) . SP;
4764
-            }
4765
-        }
4766
-        return $SQL;
4767
-    }
4768
-
4769
-
4770
-
4771
-    /**
4772
-     * Constructs the internal join if there are multiple tables, or simply the table's name and alias
4773
-     * Eg "wp_post AS Event" or "wp_post AS Event INNER JOIN wp_postmeta Event_Meta ON Event.ID = Event_Meta.post_id"
4774
-     *
4775
-     * @return string SQL
4776
-     * @throws EE_Error
4777
-     */
4778
-    public function _construct_internal_join()
4779
-    {
4780
-        $SQL = $this->_get_main_table()->get_table_sql();
4781
-        $SQL .= $this->_construct_internal_join_to_table_with_alias($this->_get_main_table()->get_table_alias());
4782
-        return $SQL;
4783
-    }
4784
-
4785
-
4786
-
4787
-    /**
4788
-     * Constructs the SQL for joining all the tables on this model.
4789
-     * Normally $alias should be the primary table's alias, but in cases where
4790
-     * we have already joined to a secondary table (eg, the secondary table has a foreign key and is joined before the
4791
-     * primary table) then we should provide that secondary table's alias. Eg, with $alias being the primary table's
4792
-     * alias, this will construct SQL like:
4793
-     * " INNER JOIN wp_esp_secondary_table AS Secondary_Table ON Primary_Table.pk = Secondary_Table.fk".
4794
-     * With $alias being a secondary table's alias, this will construct SQL like:
4795
-     * " INNER JOIN wp_esp_primary_table AS Primary_Table ON Primary_Table.pk = Secondary_Table.fk".
4796
-     *
4797
-     * @param string $alias_prefixed table alias to join to (this table should already be in the FROM SQL clause)
4798
-     * @return string
4799
-     */
4800
-    public function _construct_internal_join_to_table_with_alias($alias_prefixed)
4801
-    {
4802
-        $SQL = '';
4803
-        $alias_sans_prefix = EE_Model_Parser::remove_table_alias_model_relation_chain_prefix($alias_prefixed);
4804
-        foreach ($this->_tables as $table_obj) {
4805
-            if ($table_obj instanceof EE_Secondary_Table) {// table is secondary table
4806
-                if ($alias_sans_prefix === $table_obj->get_table_alias()) {
4807
-                    // so we're joining to this table, meaning the table is already in
4808
-                    // the FROM statement, BUT the primary table isn't. So we want
4809
-                    // to add the inverse join sql
4810
-                    $SQL .= $table_obj->get_inverse_join_sql($alias_prefixed);
4811
-                } else {
4812
-                    // just add a regular JOIN to this table from the primary table
4813
-                    $SQL .= $table_obj->get_join_sql($alias_prefixed);
4814
-                }
4815
-            }//if it's a primary table, dont add any SQL. it should already be in the FROM statement
4816
-        }
4817
-        return $SQL;
4818
-    }
4819
-
4820
-
4821
-
4822
-    /**
4823
-     * Gets an array for storing all the data types on the next-to-be-executed-query.
4824
-     * This should be a growing array of keys being table-columns (eg 'EVT_ID' and 'Event.EVT_ID'), and values being
4825
-     * their data type (eg, '%s', '%d', etc)
4826
-     *
4827
-     * @return array
4828
-     */
4829
-    public function _get_data_types()
4830
-    {
4831
-        $data_types = array();
4832
-        foreach ($this->field_settings() as $field_obj) {
4833
-            // $data_types[$field_obj->get_table_column()] = $field_obj->get_wpdb_data_type();
4834
-            /** @var $field_obj EE_Model_Field_Base */
4835
-            $data_types[ $field_obj->get_qualified_column() ] = $field_obj->get_wpdb_data_type();
4836
-        }
4837
-        return $data_types;
4838
-    }
4839
-
4840
-
4841
-
4842
-    /**
4843
-     * Gets the model object given the relation's name / model's name (eg, 'Event', 'Registration',etc. Always singular)
4844
-     *
4845
-     * @param string $model_name
4846
-     * @throws EE_Error
4847
-     * @return EEM_Base
4848
-     */
4849
-    public function get_related_model_obj($model_name)
4850
-    {
4851
-        $model_classname = "EEM_" . $model_name;
4852
-        if (! class_exists($model_classname)) {
4853
-            throw new EE_Error(sprintf(esc_html__(
4854
-                "You specified a related model named %s in your query. No such model exists, if it did, it would have the classname %s",
4855
-                'event_espresso'
4856
-            ), $model_name, $model_classname));
4857
-        }
4858
-        return call_user_func($model_classname . "::instance");
4859
-    }
4860
-
4861
-
4862
-
4863
-    /**
4864
-     * Returns the array of EE_ModelRelations for this model.
4865
-     *
4866
-     * @return EE_Model_Relation_Base[]
4867
-     */
4868
-    public function relation_settings()
4869
-    {
4870
-        return $this->_model_relations;
4871
-    }
4872
-
4873
-
4874
-
4875
-    /**
4876
-     * Gets all related models that this model BELONGS TO. Handy to know sometimes
4877
-     * because without THOSE models, this model probably doesn't have much purpose.
4878
-     * (Eg, without an event, datetimes have little purpose.)
4879
-     *
4880
-     * @return EE_Belongs_To_Relation[]
4881
-     */
4882
-    public function belongs_to_relations()
4883
-    {
4884
-        $belongs_to_relations = array();
4885
-        foreach ($this->relation_settings() as $model_name => $relation_obj) {
4886
-            if ($relation_obj instanceof EE_Belongs_To_Relation) {
4887
-                $belongs_to_relations[ $model_name ] = $relation_obj;
4888
-            }
4889
-        }
4890
-        return $belongs_to_relations;
4891
-    }
4892
-
4893
-
4894
-
4895
-    /**
4896
-     * Returns the specified EE_Model_Relation, or throws an exception
4897
-     *
4898
-     * @param string $relation_name name of relation, key in $this->_relatedModels
4899
-     * @throws EE_Error
4900
-     * @return EE_Model_Relation_Base
4901
-     */
4902
-    public function related_settings_for($relation_name)
4903
-    {
4904
-        $relatedModels = $this->relation_settings();
4905
-        if (! array_key_exists($relation_name, $relatedModels)) {
4906
-            throw new EE_Error(
4907
-                sprintf(
4908
-                    esc_html__(
4909
-                        'Cannot get %s related to %s. There is no model relation of that type. There is, however, %s...',
4910
-                        'event_espresso'
4911
-                    ),
4912
-                    $relation_name,
4913
-                    $this->_get_class_name(),
4914
-                    implode(', ', array_keys($relatedModels))
4915
-                )
4916
-            );
4917
-        }
4918
-        return $relatedModels[ $relation_name ];
4919
-    }
4920
-
4921
-
4922
-
4923
-    /**
4924
-     * A convenience method for getting a specific field's settings, instead of getting all field settings for all
4925
-     * fields
4926
-     *
4927
-     * @param string $fieldName
4928
-     * @param boolean $include_db_only_fields
4929
-     * @throws EE_Error
4930
-     * @return EE_Model_Field_Base
4931
-     */
4932
-    public function field_settings_for($fieldName, $include_db_only_fields = true)
4933
-    {
4934
-        $fieldSettings = $this->field_settings($include_db_only_fields);
4935
-        if (! array_key_exists($fieldName, $fieldSettings)) {
4936
-            throw new EE_Error(sprintf(
4937
-                esc_html__("There is no field/column '%s' on '%s'", 'event_espresso'),
4938
-                $fieldName,
4939
-                get_class($this)
4940
-            ));
4941
-        }
4942
-        return $fieldSettings[ $fieldName ];
4943
-    }
4944
-
4945
-
4946
-
4947
-    /**
4948
-     * Checks if this field exists on this model
4949
-     *
4950
-     * @param string $fieldName a key in the model's _field_settings array
4951
-     * @return boolean
4952
-     */
4953
-    public function has_field($fieldName)
4954
-    {
4955
-        $fieldSettings = $this->field_settings(true);
4956
-        if (isset($fieldSettings[ $fieldName ])) {
4957
-            return true;
4958
-        }
4959
-        return false;
4960
-    }
4961
-
4962
-
4963
-
4964
-    /**
4965
-     * Returns whether or not this model has a relation to the specified model
4966
-     *
4967
-     * @param string $relation_name possibly one of the keys in the relation_settings array
4968
-     * @return boolean
4969
-     */
4970
-    public function has_relation($relation_name)
4971
-    {
4972
-        $relations = $this->relation_settings();
4973
-        if (isset($relations[ $relation_name ])) {
4974
-            return true;
4975
-        }
4976
-        return false;
4977
-    }
4978
-
4979
-
4980
-
4981
-    /**
4982
-     * gets the field object of type 'primary_key' from the fieldsSettings attribute.
4983
-     * Eg, on EE_Answer that would be ANS_ID field object
4984
-     *
4985
-     * @param $field_obj
4986
-     * @return boolean
4987
-     */
4988
-    public function is_primary_key_field($field_obj)
4989
-    {
4990
-        return $field_obj instanceof EE_Primary_Key_Field_Base ? true : false;
4991
-    }
4992
-
4993
-
4994
-
4995
-    /**
4996
-     * gets the field object of type 'primary_key' from the fieldsSettings attribute.
4997
-     * Eg, on EE_Answer that would be ANS_ID field object
4998
-     *
4999
-     * @return EE_Primary_Key_Field_Base
5000
-     * @throws EE_Error
5001
-     */
5002
-    public function get_primary_key_field()
5003
-    {
5004
-        if ($this->_primary_key_field === null) {
5005
-            foreach ($this->field_settings(true) as $field_obj) {
5006
-                if ($this->is_primary_key_field($field_obj)) {
5007
-                    $this->_primary_key_field = $field_obj;
5008
-                    break;
5009
-                }
5010
-            }
5011
-            if (! $this->_primary_key_field instanceof EE_Primary_Key_Field_Base) {
5012
-                throw new EE_Error(sprintf(
5013
-                    esc_html__("There is no Primary Key defined on model %s", 'event_espresso'),
5014
-                    get_class($this)
5015
-                ));
5016
-            }
5017
-        }
5018
-        return $this->_primary_key_field;
5019
-    }
5020
-
5021
-
5022
-
5023
-    /**
5024
-     * Returns whether or not not there is a primary key on this model.
5025
-     * Internally does some caching.
5026
-     *
5027
-     * @return boolean
5028
-     */
5029
-    public function has_primary_key_field()
5030
-    {
5031
-        if ($this->_has_primary_key_field === null) {
5032
-            try {
5033
-                $this->get_primary_key_field();
5034
-                $this->_has_primary_key_field = true;
5035
-            } catch (EE_Error $e) {
5036
-                $this->_has_primary_key_field = false;
5037
-            }
5038
-        }
5039
-        return $this->_has_primary_key_field;
5040
-    }
5041
-
5042
-
5043
-
5044
-    /**
5045
-     * Finds the first field of type $field_class_name.
5046
-     *
5047
-     * @param string $field_class_name class name of field that you want to find. Eg, EE_Datetime_Field,
5048
-     *                                 EE_Foreign_Key_Field, etc
5049
-     * @return EE_Model_Field_Base or null if none is found
5050
-     */
5051
-    public function get_a_field_of_type($field_class_name)
5052
-    {
5053
-        foreach ($this->field_settings() as $field) {
5054
-            if ($field instanceof $field_class_name) {
5055
-                return $field;
5056
-            }
5057
-        }
5058
-        return null;
5059
-    }
5060
-
5061
-
5062
-
5063
-    /**
5064
-     * Gets a foreign key field pointing to model.
5065
-     *
5066
-     * @param string $model_name eg Event, Registration, not EEM_Event
5067
-     * @return EE_Foreign_Key_Field_Base
5068
-     * @throws EE_Error
5069
-     */
5070
-    public function get_foreign_key_to($model_name)
5071
-    {
5072
-        if (! isset($this->_cache_foreign_key_to_fields[ $model_name ])) {
5073
-            foreach ($this->field_settings() as $field) {
5074
-                if (
5075
-                    $field instanceof EE_Foreign_Key_Field_Base
5076
-                    && in_array($model_name, $field->get_model_names_pointed_to())
5077
-                ) {
5078
-                    $this->_cache_foreign_key_to_fields[ $model_name ] = $field;
5079
-                    break;
5080
-                }
5081
-            }
5082
-            if (! isset($this->_cache_foreign_key_to_fields[ $model_name ])) {
5083
-                throw new EE_Error(sprintf(esc_html__(
5084
-                    "There is no foreign key field pointing to model %s on model %s",
5085
-                    'event_espresso'
5086
-                ), $model_name, get_class($this)));
5087
-            }
5088
-        }
5089
-        return $this->_cache_foreign_key_to_fields[ $model_name ];
5090
-    }
5091
-
5092
-
5093
-
5094
-    /**
5095
-     * Gets the table name (including $wpdb->prefix) for the table alias
5096
-     *
5097
-     * @param string $table_alias eg Event, Event_Meta, Registration, Transaction, but maybe
5098
-     *                            a table alias with a model chain prefix, like 'Venue__Event_Venue___Event_Meta'.
5099
-     *                            Either one works
5100
-     * @return string
5101
-     */
5102
-    public function get_table_for_alias($table_alias)
5103
-    {
5104
-        $table_alias_sans_model_relation_chain_prefix = EE_Model_Parser::remove_table_alias_model_relation_chain_prefix($table_alias);
5105
-        return $this->_tables[ $table_alias_sans_model_relation_chain_prefix ]->get_table_name();
5106
-    }
5107
-
5108
-
5109
-
5110
-    /**
5111
-     * Returns a flat array of all field son this model, instead of organizing them
5112
-     * by table_alias as they are in the constructor.
5113
-     *
5114
-     * @param bool $include_db_only_fields flag indicating whether or not to include the db-only fields
5115
-     * @return EE_Model_Field_Base[] where the keys are the field's name
5116
-     */
5117
-    public function field_settings($include_db_only_fields = false)
5118
-    {
5119
-        if ($include_db_only_fields) {
5120
-            if ($this->_cached_fields === null) {
5121
-                $this->_cached_fields = array();
5122
-                foreach ($this->_fields as $fields_corresponding_to_table) {
5123
-                    foreach ($fields_corresponding_to_table as $field_name => $field_obj) {
5124
-                        $this->_cached_fields[ $field_name ] = $field_obj;
5125
-                    }
5126
-                }
5127
-            }
5128
-            return $this->_cached_fields;
5129
-        }
5130
-        if ($this->_cached_fields_non_db_only === null) {
5131
-            $this->_cached_fields_non_db_only = array();
5132
-            foreach ($this->_fields as $fields_corresponding_to_table) {
5133
-                foreach ($fields_corresponding_to_table as $field_name => $field_obj) {
5134
-                    /** @var $field_obj EE_Model_Field_Base */
5135
-                    if (! $field_obj->is_db_only_field()) {
5136
-                        $this->_cached_fields_non_db_only[ $field_name ] = $field_obj;
5137
-                    }
5138
-                }
5139
-            }
5140
-        }
5141
-        return $this->_cached_fields_non_db_only;
5142
-    }
5143
-
5144
-
5145
-
5146
-    /**
5147
-     *        cycle though array of attendees and create objects out of each item
5148
-     *
5149
-     * @access        private
5150
-     * @param        array $rows of results of $wpdb->get_results($query,ARRAY_A)
5151
-     * @return \EE_Base_Class[] array keys are primary keys (if there is a primary key on the model. if not,
5152
-     *                           numerically indexed)
5153
-     * @throws EE_Error
5154
-     */
5155
-    protected function _create_objects($rows = array())
5156
-    {
5157
-        $array_of_objects = array();
5158
-        if (empty($rows)) {
5159
-            return array();
5160
-        }
5161
-        $count_if_model_has_no_primary_key = 0;
5162
-        $has_primary_key = $this->has_primary_key_field();
5163
-        $primary_key_field = $has_primary_key ? $this->get_primary_key_field() : null;
5164
-        foreach ((array) $rows as $row) {
5165
-            if (empty($row)) {
5166
-                // wp did its weird thing where it returns an array like array(0=>null), which is totally not helpful...
5167
-                return array();
5168
-            }
5169
-            // check if we've already set this object in the results array,
5170
-            // in which case there's no need to process it further (again)
5171
-            if ($has_primary_key) {
5172
-                $table_pk_value = $this->_get_column_value_with_table_alias_or_not(
5173
-                    $row,
5174
-                    $primary_key_field->get_qualified_column(),
5175
-                    $primary_key_field->get_table_column()
5176
-                );
5177
-                if ($table_pk_value && isset($array_of_objects[ $table_pk_value ])) {
5178
-                    continue;
5179
-                }
5180
-            }
5181
-            $classInstance = $this->instantiate_class_from_array_or_object($row);
5182
-            if (! $classInstance) {
5183
-                throw new EE_Error(
5184
-                    sprintf(
5185
-                        esc_html__('Could not create instance of class %s from row %s', 'event_espresso'),
5186
-                        $this->get_this_model_name(),
5187
-                        http_build_query($row)
5188
-                    )
5189
-                );
5190
-            }
5191
-            // set the timezone on the instantiated objects
5192
-            $classInstance->set_timezone($this->_timezone);
5193
-            // make sure if there is any timezone setting present that we set the timezone for the object
5194
-            $key = $has_primary_key ? $classInstance->ID() : $count_if_model_has_no_primary_key++;
5195
-            $array_of_objects[ $key ] = $classInstance;
5196
-            // also, for all the relations of type BelongsTo, see if we can cache
5197
-            // those related models
5198
-            // (we could do this for other relations too, but if there are conditions
5199
-            // that filtered out some fo the results, then we'd be caching an incomplete set
5200
-            // so it requires a little more thought than just caching them immediately...)
5201
-            foreach ($this->_model_relations as $modelName => $relation_obj) {
5202
-                if ($relation_obj instanceof EE_Belongs_To_Relation) {
5203
-                    // check if this model's INFO is present. If so, cache it on the model
5204
-                    $other_model = $relation_obj->get_other_model();
5205
-                    $other_model_obj_maybe = $other_model->instantiate_class_from_array_or_object($row);
5206
-                    // if we managed to make a model object from the results, cache it on the main model object
5207
-                    if ($other_model_obj_maybe) {
5208
-                        // set timezone on these other model objects if they are present
5209
-                        $other_model_obj_maybe->set_timezone($this->_timezone);
5210
-                        $classInstance->cache($modelName, $other_model_obj_maybe);
5211
-                    }
5212
-                }
5213
-            }
5214
-            // also, if this was a custom select query, let's see if there are any results for the custom select fields
5215
-            // and add them to the object as well.  We'll convert according to the set data_type if there's any set for
5216
-            // the field in the CustomSelects object
5217
-            if ($this->_custom_selections instanceof CustomSelects) {
5218
-                $classInstance->setCustomSelectsValues(
5219
-                    $this->getValuesForCustomSelectAliasesFromResults($row)
5220
-                );
5221
-            }
5222
-        }
5223
-        return $array_of_objects;
5224
-    }
5225
-
5226
-
5227
-    /**
5228
-     * This will parse a given row of results from the db and see if any keys in the results match an alias within the
5229
-     * current CustomSelects object. This will be used to build an array of values indexed by those keys.
5230
-     *
5231
-     * @param array $db_results_row
5232
-     * @return array
5233
-     */
5234
-    protected function getValuesForCustomSelectAliasesFromResults(array $db_results_row)
5235
-    {
5236
-        $results = array();
5237
-        if ($this->_custom_selections instanceof CustomSelects) {
5238
-            foreach ($this->_custom_selections->columnAliases() as $alias) {
5239
-                if (isset($db_results_row[ $alias ])) {
5240
-                    $results[ $alias ] = $this->convertValueToDataType(
5241
-                        $db_results_row[ $alias ],
5242
-                        $this->_custom_selections->getDataTypeForAlias($alias)
5243
-                    );
5244
-                }
5245
-            }
5246
-        }
5247
-        return $results;
5248
-    }
5249
-
5250
-
5251
-    /**
5252
-     * This will set the value for the given alias
5253
-     * @param string $value
5254
-     * @param string $datatype (one of %d, %s, %f)
5255
-     * @return int|string|float (int for %d, string for %s, float for %f)
5256
-     */
5257
-    protected function convertValueToDataType($value, $datatype)
5258
-    {
5259
-        switch ($datatype) {
5260
-            case '%f':
5261
-                return (float) $value;
5262
-            case '%d':
5263
-                return (int) $value;
5264
-            default:
5265
-                return (string) $value;
5266
-        }
5267
-    }
5268
-
5269
-
5270
-    /**
5271
-     * The purpose of this method is to allow us to create a model object that is not in the db that holds default
5272
-     * values. A typical example of where this is used is when creating a new item and the initial load of a form.  We
5273
-     * dont' necessarily want to test for if the object is present but just assume it is BUT load the defaults from the
5274
-     * object (as set in the model_field!).
5275
-     *
5276
-     * @return EE_Base_Class single EE_Base_Class object with default values for the properties.
5277
-     */
5278
-    public function create_default_object()
5279
-    {
5280
-        $this_model_fields_and_values = array();
5281
-        // setup the row using default values;
5282
-        foreach ($this->field_settings() as $field_name => $field_obj) {
5283
-            $this_model_fields_and_values[ $field_name ] = $field_obj->get_default_value();
5284
-        }
5285
-        $className = $this->_get_class_name();
5286
-        $classInstance = EE_Registry::instance()
5287
-                                    ->load_class($className, array($this_model_fields_and_values), false, false);
5288
-        return $classInstance;
5289
-    }
5290
-
5291
-
5292
-
5293
-    /**
5294
-     * @param mixed $cols_n_values either an array of where each key is the name of a field, and the value is its value
5295
-     *                             or an stdClass where each property is the name of a column,
5296
-     * @return EE_Base_Class
5297
-     * @throws EE_Error
5298
-     */
5299
-    public function instantiate_class_from_array_or_object($cols_n_values)
5300
-    {
5301
-        if (! is_array($cols_n_values) && is_object($cols_n_values)) {
5302
-            $cols_n_values = get_object_vars($cols_n_values);
5303
-        }
5304
-        $primary_key = null;
5305
-        // make sure the array only has keys that are fields/columns on this model
5306
-        $this_model_fields_n_values = $this->_deduce_fields_n_values_from_cols_n_values($cols_n_values);
5307
-        if ($this->has_primary_key_field() && isset($this_model_fields_n_values[ $this->primary_key_name() ])) {
5308
-            $primary_key = $this_model_fields_n_values[ $this->primary_key_name() ];
5309
-        }
5310
-        $className = $this->_get_class_name();
5311
-        // check we actually found results that we can use to build our model object
5312
-        // if not, return null
5313
-        if ($this->has_primary_key_field()) {
5314
-            if (empty($this_model_fields_n_values[ $this->primary_key_name() ])) {
5315
-                return null;
5316
-            }
5317
-        } elseif ($this->unique_indexes()) {
5318
-            $first_column = reset($this_model_fields_n_values);
5319
-            if (empty($first_column)) {
5320
-                return null;
5321
-            }
5322
-        }
5323
-        // if there is no primary key or the object doesn't already exist in the entity map, then create a new instance
5324
-        if ($primary_key) {
5325
-            $classInstance = $this->get_from_entity_map($primary_key);
5326
-            if (! $classInstance) {
5327
-                $classInstance = EE_Registry::instance()
5328
-                                            ->load_class(
5329
-                                                $className,
5330
-                                                array($this_model_fields_n_values, $this->_timezone),
5331
-                                                true,
5332
-                                                false
5333
-                                            );
5334
-                // add this new object to the entity map
5335
-                $classInstance = $this->add_to_entity_map($classInstance);
5336
-            }
5337
-        } else {
5338
-            $classInstance = EE_Registry::instance()
5339
-                                        ->load_class(
5340
-                                            $className,
5341
-                                            array($this_model_fields_n_values, $this->_timezone),
5342
-                                            true,
5343
-                                            false
5344
-                                        );
5345
-        }
5346
-        return $classInstance;
5347
-    }
5348
-
5349
-
5350
-
5351
-    /**
5352
-     * Gets the model object from the  entity map if it exists
5353
-     *
5354
-     * @param int|string $id the ID of the model object
5355
-     * @return EE_Base_Class
5356
-     */
5357
-    public function get_from_entity_map($id)
5358
-    {
5359
-        return isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])
5360
-            ? $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ] : null;
5361
-    }
5362
-
5363
-
5364
-
5365
-    /**
5366
-     * add_to_entity_map
5367
-     * Adds the object to the model's entity mappings
5368
-     *        Effectively tells the models "Hey, this model object is the most up-to-date representation of the data,
5369
-     *        and for the remainder of the request, it's even more up-to-date than what's in the database.
5370
-     *        So, if the database doesn't agree with what's in the entity mapper, ignore the database"
5371
-     *        If the database gets updated directly and you want the entity mapper to reflect that change,
5372
-     *        then this method should be called immediately after the update query
5373
-     * Note: The map is indexed by whatever the current blog id is set (via EEM_Base::$_model_query_blog_id).  This is
5374
-     * so on multisite, the entity map is specific to the query being done for a specific site.
5375
-     *
5376
-     * @param    EE_Base_Class $object
5377
-     * @throws EE_Error
5378
-     * @return \EE_Base_Class
5379
-     */
5380
-    public function add_to_entity_map(EE_Base_Class $object)
5381
-    {
5382
-        $className = $this->_get_class_name();
5383
-        if (! $object instanceof $className) {
5384
-            throw new EE_Error(sprintf(
5385
-                esc_html__("You tried adding a %s to a mapping of %ss", "event_espresso"),
5386
-                is_object($object) ? get_class($object) : $object,
5387
-                $className
5388
-            ));
5389
-        }
5390
-        /** @var $object EE_Base_Class */
5391
-        if (! $object->ID()) {
5392
-            throw new EE_Error(sprintf(esc_html__(
5393
-                "You tried storing a model object with NO ID in the %s entity mapper.",
5394
-                "event_espresso"
5395
-            ), get_class($this)));
5396
-        }
5397
-        // double check it's not already there
5398
-        $classInstance = $this->get_from_entity_map($object->ID());
5399
-        if ($classInstance) {
5400
-            return $classInstance;
5401
-        }
5402
-        $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $object->ID() ] = $object;
5403
-        return $object;
5404
-    }
5405
-
5406
-
5407
-
5408
-    /**
5409
-     * if a valid identifier is provided, then that entity is unset from the entity map,
5410
-     * if no identifier is provided, then the entire entity map is emptied
5411
-     *
5412
-     * @param int|string $id the ID of the model object
5413
-     * @return boolean
5414
-     */
5415
-    public function clear_entity_map($id = null)
5416
-    {
5417
-        if (empty($id)) {
5418
-            $this->_entity_map[ EEM_Base::$_model_query_blog_id ] = array();
5419
-            return true;
5420
-        }
5421
-        if (isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])) {
5422
-            unset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ]);
5423
-            return true;
5424
-        }
5425
-        return false;
5426
-    }
5427
-
5428
-
5429
-
5430
-    /**
5431
-     * Public wrapper for _deduce_fields_n_values_from_cols_n_values.
5432
-     * Given an array where keys are column (or column alias) names and values,
5433
-     * returns an array of their corresponding field names and database values
5434
-     *
5435
-     * @param array $cols_n_values
5436
-     * @return array
5437
-     */
5438
-    public function deduce_fields_n_values_from_cols_n_values($cols_n_values)
5439
-    {
5440
-        return $this->_deduce_fields_n_values_from_cols_n_values($cols_n_values);
5441
-    }
5442
-
5443
-
5444
-
5445
-    /**
5446
-     * _deduce_fields_n_values_from_cols_n_values
5447
-     * Given an array where keys are column (or column alias) names and values,
5448
-     * returns an array of their corresponding field names and database values
5449
-     *
5450
-     * @param string $cols_n_values
5451
-     * @return array
5452
-     */
5453
-    protected function _deduce_fields_n_values_from_cols_n_values($cols_n_values)
5454
-    {
5455
-        $this_model_fields_n_values = array();
5456
-        foreach ($this->get_tables() as $table_alias => $table_obj) {
5457
-            $table_pk_value = $this->_get_column_value_with_table_alias_or_not(
5458
-                $cols_n_values,
5459
-                $table_obj->get_fully_qualified_pk_column(),
5460
-                $table_obj->get_pk_column()
5461
-            );
5462
-            // there is a primary key on this table and its not set. Use defaults for all its columns
5463
-            if ($table_pk_value === null && $table_obj->get_pk_column()) {
5464
-                foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5465
-                    if (! $field_obj->is_db_only_field()) {
5466
-                        // prepare field as if its coming from db
5467
-                        $prepared_value = $field_obj->prepare_for_set($field_obj->get_default_value());
5468
-                        $this_model_fields_n_values[ $field_name ] = $field_obj->prepare_for_use_in_db($prepared_value);
5469
-                    }
5470
-                }
5471
-            } else {
5472
-                // the table's rows existed. Use their values
5473
-                foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5474
-                    if (! $field_obj->is_db_only_field()) {
5475
-                        $this_model_fields_n_values[ $field_name ] = $this->_get_column_value_with_table_alias_or_not(
5476
-                            $cols_n_values,
5477
-                            $field_obj->get_qualified_column(),
5478
-                            $field_obj->get_table_column()
5479
-                        );
5480
-                    }
5481
-                }
5482
-            }
5483
-        }
5484
-        return $this_model_fields_n_values;
5485
-    }
5486
-
5487
-
5488
-    /**
5489
-     * @param $cols_n_values
5490
-     * @param $qualified_column
5491
-     * @param $regular_column
5492
-     * @return null
5493
-     * @throws EE_Error
5494
-     * @throws ReflectionException
5495
-     */
5496
-    protected function _get_column_value_with_table_alias_or_not($cols_n_values, $qualified_column, $regular_column)
5497
-    {
5498
-        $value = null;
5499
-        // ask the field what it think it's table_name.column_name should be, and call it the "qualified column"
5500
-        // does the field on the model relate to this column retrieved from the db?
5501
-        // or is it a db-only field? (not relating to the model)
5502
-        if (isset($cols_n_values[ $qualified_column ])) {
5503
-            $value = $cols_n_values[ $qualified_column ];
5504
-        } elseif (isset($cols_n_values[ $regular_column ])) {
5505
-            $value = $cols_n_values[ $regular_column ];
5506
-        } elseif (! empty($this->foreign_key_aliases)) {
5507
-            // no PK?  ok check if there is a foreign key alias set for this table
5508
-            // then check if that alias exists in the incoming data
5509
-            // AND that the actual PK the $FK_alias represents matches the $qualified_column (full PK)
5510
-            foreach ($this->foreign_key_aliases as $FK_alias => $PK_column) {
5511
-                if ($PK_column === $qualified_column && isset($cols_n_values[ $FK_alias ])) {
5512
-                    $value = $cols_n_values[ $FK_alias ];
5513
-                    [$pk_class] = explode('.', $PK_column);
5514
-                    $pk_model_name = "EEM_{$pk_class}";
5515
-                    /** @var EEM_Base $pk_model */
5516
-                    $pk_model = EE_Registry::instance()->load_model($pk_model_name);
5517
-                    if ($pk_model instanceof EEM_Base) {
5518
-                        // make sure object is pulled from db and added to entity map
5519
-                        $pk_model->get_one_by_ID($value);
5520
-                    }
5521
-                    break;
5522
-                }
5523
-            }
5524
-        }
5525
-        return $value;
5526
-    }
5527
-
5528
-
5529
-    /**
5530
-     * refresh_entity_map_from_db
5531
-     * Makes sure the model object in the entity map at $id assumes the values
5532
-     * of the database (opposite of EE_base_Class::save())
5533
-     *
5534
-     * @param int|string $id
5535
-     * @return EE_Base_Class|EE_Soft_Delete_Base_Class|mixed|null
5536
-     * @throws EE_Error
5537
-     * @throws ReflectionException
5538
-     */
5539
-    public function refresh_entity_map_from_db($id)
5540
-    {
5541
-        $obj_in_map = $this->get_from_entity_map($id);
5542
-        if ($obj_in_map) {
5543
-            $wpdb_results = $this->_get_all_wpdb_results(
5544
-                array(array($this->get_primary_key_field()->get_name() => $id), 'limit' => 1)
5545
-            );
5546
-            if ($wpdb_results && is_array($wpdb_results)) {
5547
-                $one_row = reset($wpdb_results);
5548
-                foreach ($this->_deduce_fields_n_values_from_cols_n_values($one_row) as $field_name => $db_value) {
5549
-                    $obj_in_map->set_from_db($field_name, $db_value);
5550
-                }
5551
-                // clear the cache of related model objects
5552
-                foreach ($this->relation_settings() as $relation_name => $relation_obj) {
5553
-                    $obj_in_map->clear_cache($relation_name, null, true);
5554
-                }
5555
-            }
5556
-            $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ] = $obj_in_map;
5557
-            return $obj_in_map;
5558
-        }
5559
-        return $this->get_one_by_ID($id);
5560
-    }
5561
-
5562
-
5563
-
5564
-    /**
5565
-     * refresh_entity_map_with
5566
-     * Leaves the entry in the entity map alone, but updates it to match the provided
5567
-     * $replacing_model_obj (which we assume to be its equivalent but somehow NOT in the entity map).
5568
-     * This is useful if you have a model object you want to make authoritative over what's in the entity map currently.
5569
-     * Note: The old $replacing_model_obj should now be destroyed as it's now un-authoritative
5570
-     *
5571
-     * @param int|string    $id
5572
-     * @param EE_Base_Class $replacing_model_obj
5573
-     * @return \EE_Base_Class
5574
-     * @throws EE_Error
5575
-     */
5576
-    public function refresh_entity_map_with($id, $replacing_model_obj)
5577
-    {
5578
-        $obj_in_map = $this->get_from_entity_map($id);
5579
-        if ($obj_in_map) {
5580
-            if ($replacing_model_obj instanceof EE_Base_Class) {
5581
-                foreach ($replacing_model_obj->model_field_array() as $field_name => $value) {
5582
-                    $obj_in_map->set($field_name, $value);
5583
-                }
5584
-                // make the model object in the entity map's cache match the $replacing_model_obj
5585
-                foreach ($this->relation_settings() as $relation_name => $relation_obj) {
5586
-                    $obj_in_map->clear_cache($relation_name, null, true);
5587
-                    foreach ($replacing_model_obj->get_all_from_cache($relation_name) as $cache_id => $cached_obj) {
5588
-                        $obj_in_map->cache($relation_name, $cached_obj, $cache_id);
5589
-                    }
5590
-                }
5591
-            }
5592
-            return $obj_in_map;
5593
-        }
5594
-        $this->add_to_entity_map($replacing_model_obj);
5595
-        return $replacing_model_obj;
5596
-    }
5597
-
5598
-
5599
-
5600
-    /**
5601
-     * Gets the EE class that corresponds to this model. Eg, for EEM_Answer that
5602
-     * would be EE_Answer.To import that class, you'd just add ".class.php" to the name, like so
5603
-     * require_once($this->_getClassName().".class.php");
5604
-     *
5605
-     * @return string
5606
-     */
5607
-    private function _get_class_name()
5608
-    {
5609
-        return "EE_" . $this->get_this_model_name();
5610
-    }
5611
-
5612
-
5613
-
5614
-    /**
5615
-     * Get the name of the items this model represents, for the quantity specified. Eg,
5616
-     * if $quantity==1, on EEM_Event, it would 'Event' (internationalized), otherwise
5617
-     * it would be 'Events'.
5618
-     *
5619
-     * @param int|float|null $quantity
5620
-     * @return string
5621
-     */
5622
-    public function item_name($quantity = 1): string
5623
-    {
5624
-        $quantity = floor($quantity);
5625
-        return apply_filters(
5626
-            'FHEE__EEM_Base__item_name__plural_or_singular',
5627
-            $quantity > 1 ? $this->plural_item : $this->singular_item,
5628
-            $quantity,
5629
-            $this->plural_item,
5630
-            $this->singular_item
5631
-        );
5632
-    }
5633
-
5634
-
5635
-
5636
-    /**
5637
-     * Very handy general function to allow for plugins to extend any child of EE_TempBase.
5638
-     * If a method is called on a child of EE_TempBase that doesn't exist, this function is called
5639
-     * (http://www.garfieldtech.com/blog/php-magic-call) and passed the method's name and arguments. Instead of
5640
-     * requiring a plugin to extend the EE_TempBase (which works fine is there's only 1 plugin, but when will that
5641
-     * happen?) they can add a hook onto 'filters_hook_espresso__{className}__{methodName}' (eg,
5642
-     * filters_hook_espresso__EE_Answer__my_great_function) and accepts 2 arguments: the object on which the function
5643
-     * was called, and an array of the original arguments passed to the function. Whatever their callback function
5644
-     * returns will be returned by this function. Example: in functions.php (or in a plugin):
5645
-     * add_filter('FHEE__EE_Answer__my_callback','my_callback',10,3); function
5646
-     * my_callback($previousReturnValue,EE_TempBase $object,$argsArray){
5647
-     * $returnString= "you called my_callback! and passed args:".implode(",",$argsArray);
5648
-     *        return $previousReturnValue.$returnString;
5649
-     * }
5650
-     * require('EEM_Answer.model.php');
5651
-     * $answer=EEM_Answer::instance();
5652
-     * echo $answer->my_callback('monkeys',100);
5653
-     * //will output "you called my_callback! and passed args:monkeys,100"
5654
-     *
5655
-     * @param string $methodName name of method which was called on a child of EE_TempBase, but which
5656
-     * @param array  $args       array of original arguments passed to the function
5657
-     * @throws EE_Error
5658
-     * @return mixed whatever the plugin which calls add_filter decides
5659
-     */
5660
-    public function __call($methodName, $args)
5661
-    {
5662
-        $className = get_class($this);
5663
-        $tagName = "FHEE__{$className}__{$methodName}";
5664
-        if (! has_filter($tagName)) {
5665
-            throw new EE_Error(
5666
-                sprintf(
5667
-                    esc_html__(
5668
-                        '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 );',
5669
-                        'event_espresso'
5670
-                    ),
5671
-                    $methodName,
5672
-                    $className,
5673
-                    $tagName,
5674
-                    '<br />'
5675
-                )
5676
-            );
5677
-        }
5678
-        return apply_filters($tagName, null, $this, $args);
5679
-    }
5680
-
5681
-
5682
-
5683
-    /**
5684
-     * Ensures $base_class_obj_or_id is of the EE_Base_Class child that corresponds ot this model.
5685
-     * If not, assumes its an ID, and uses $this->get_one_by_ID() to get the EE_Base_Class.
5686
-     *
5687
-     * @param EE_Base_Class|string|int $base_class_obj_or_id either:
5688
-     *                                                       the EE_Base_Class object that corresponds to this Model,
5689
-     *                                                       the object's class name
5690
-     *                                                       or object's ID
5691
-     * @param boolean                  $ensure_is_in_db      if set, we will also verify this model object
5692
-     *                                                       exists in the database. If it does not, we add it
5693
-     * @throws EE_Error
5694
-     * @return EE_Base_Class
5695
-     */
5696
-    public function ensure_is_obj($base_class_obj_or_id, $ensure_is_in_db = false)
5697
-    {
5698
-        $className = $this->_get_class_name();
5699
-        if ($base_class_obj_or_id instanceof $className) {
5700
-            $model_object = $base_class_obj_or_id;
5701
-        } else {
5702
-            $primary_key_field = $this->get_primary_key_field();
5703
-            if (
5704
-                $primary_key_field instanceof EE_Primary_Key_Int_Field
5705
-                && (
5706
-                    is_int($base_class_obj_or_id)
5707
-                    || is_string($base_class_obj_or_id)
5708
-                )
5709
-            ) {
5710
-                // assume it's an ID.
5711
-                // either a proper integer or a string representing an integer (eg "101" instead of 101)
5712
-                $model_object = $this->get_one_by_ID($base_class_obj_or_id);
5713
-            } elseif (
5714
-                $primary_key_field instanceof EE_Primary_Key_String_Field
5715
-                && is_string($base_class_obj_or_id)
5716
-            ) {
5717
-                // assume its a string representation of the object
5718
-                $model_object = $this->get_one_by_ID($base_class_obj_or_id);
5719
-            } else {
5720
-                throw new EE_Error(
5721
-                    sprintf(
5722
-                        esc_html__(
5723
-                            "'%s' is neither an object of type %s, nor an ID! Its full value is '%s'",
5724
-                            'event_espresso'
5725
-                        ),
5726
-                        $base_class_obj_or_id,
5727
-                        $this->_get_class_name(),
5728
-                        print_r($base_class_obj_or_id, true)
5729
-                    )
5730
-                );
5731
-            }
5732
-        }
5733
-        if ($ensure_is_in_db && $model_object->ID() !== null) {
5734
-            $model_object->save();
5735
-        }
5736
-        return $model_object;
5737
-    }
5738
-
5739
-
5740
-
5741
-    /**
5742
-     * Similar to ensure_is_obj(), this method makes sure $base_class_obj_or_id
5743
-     * is a value of the this model's primary key. If it's an EE_Base_Class child,
5744
-     * returns it ID.
5745
-     *
5746
-     * @param EE_Base_Class|int|string $base_class_obj_or_id
5747
-     * @return int|string depending on the type of this model object's ID
5748
-     * @throws EE_Error
5749
-     */
5750
-    public function ensure_is_ID($base_class_obj_or_id)
5751
-    {
5752
-        $className = $this->_get_class_name();
5753
-        if ($base_class_obj_or_id instanceof $className) {
5754
-            /** @var $base_class_obj_or_id EE_Base_Class */
5755
-            $id = $base_class_obj_or_id->ID();
5756
-        } elseif (is_int($base_class_obj_or_id)) {
5757
-            // assume it's an ID
5758
-            $id = $base_class_obj_or_id;
5759
-        } elseif (is_string($base_class_obj_or_id)) {
5760
-            // assume its a string representation of the object
5761
-            $id = $base_class_obj_or_id;
5762
-        } else {
5763
-            throw new EE_Error(sprintf(
5764
-                esc_html__(
5765
-                    "'%s' is neither an object of type %s, nor an ID! Its full value is '%s'",
5766
-                    'event_espresso'
5767
-                ),
5768
-                $base_class_obj_or_id,
5769
-                $this->_get_class_name(),
5770
-                print_r($base_class_obj_or_id, true)
5771
-            ));
5772
-        }
5773
-        return $id;
5774
-    }
5775
-
5776
-
5777
-
5778
-    /**
5779
-     * Sets whether the values passed to the model (eg, values in WHERE, values in INSERT, UPDATE, etc)
5780
-     * have already been ran through the appropriate model field's prepare_for_use_in_db method. IE, they have
5781
-     * been sanitized and converted into the appropriate domain.
5782
-     * Usually the only place you'll want to change the default (which is to assume values have NOT been sanitized by
5783
-     * the model object/model field) is when making a method call from WITHIN a model object, which has direct access
5784
-     * to its sanitized values. Note: after changing this setting, you should set it back to its previous value (using
5785
-     * get_assumption_concerning_values_already_prepared_by_model_object()) eg.
5786
-     * $EVT = EEM_Event::instance(); $old_setting =
5787
-     * $EVT->get_assumption_concerning_values_already_prepared_by_model_object();
5788
-     * $EVT->assume_values_already_prepared_by_model_object(true);
5789
-     * $EVT->update(array('foo'=>'bar'),array(array('foo'=>'monkey')));
5790
-     * $EVT->assume_values_already_prepared_by_model_object($old_setting);
5791
-     *
5792
-     * @param int $values_already_prepared like one of the constants on EEM_Base
5793
-     * @return void
5794
-     */
5795
-    public function assume_values_already_prepared_by_model_object(
5796
-        $values_already_prepared = self::not_prepared_by_model_object
5797
-    ) {
5798
-        $this->_values_already_prepared_by_model_object = $values_already_prepared;
5799
-    }
5800
-
5801
-
5802
-
5803
-    /**
5804
-     * Read comments for assume_values_already_prepared_by_model_object()
5805
-     *
5806
-     * @return int
5807
-     */
5808
-    public function get_assumption_concerning_values_already_prepared_by_model_object()
5809
-    {
5810
-        return $this->_values_already_prepared_by_model_object;
5811
-    }
5812
-
5813
-
5814
-
5815
-    /**
5816
-     * Gets all the indexes on this model
5817
-     *
5818
-     * @return EE_Index[]
5819
-     */
5820
-    public function indexes()
5821
-    {
5822
-        return $this->_indexes;
5823
-    }
5824
-
5825
-
5826
-
5827
-    /**
5828
-     * Gets all the Unique Indexes on this model
5829
-     *
5830
-     * @return EE_Unique_Index[]
5831
-     */
5832
-    public function unique_indexes()
5833
-    {
5834
-        $unique_indexes = array();
5835
-        foreach ($this->_indexes as $name => $index) {
5836
-            if ($index instanceof EE_Unique_Index) {
5837
-                $unique_indexes [ $name ] = $index;
5838
-            }
5839
-        }
5840
-        return $unique_indexes;
5841
-    }
5842
-
5843
-
5844
-
5845
-    /**
5846
-     * Gets all the fields which, when combined, make the primary key.
5847
-     * This is usually just an array with 1 element (the primary key), but in cases
5848
-     * where there is no primary key, it's a combination of fields as defined
5849
-     * on a primary index
5850
-     *
5851
-     * @return EE_Model_Field_Base[] indexed by the field's name
5852
-     * @throws EE_Error
5853
-     */
5854
-    public function get_combined_primary_key_fields()
5855
-    {
5856
-        foreach ($this->indexes() as $index) {
5857
-            if ($index instanceof EE_Primary_Key_Index) {
5858
-                return $index->fields();
5859
-            }
5860
-        }
5861
-        return array($this->primary_key_name() => $this->get_primary_key_field());
5862
-    }
5863
-
5864
-
5865
-
5866
-    /**
5867
-     * Used to build a primary key string (when the model has no primary key),
5868
-     * which can be used a unique string to identify this model object.
5869
-     *
5870
-     * @param array $fields_n_values keys are field names, values are their values.
5871
-     *                               Note: if you have results from `EEM_Base::get_all_wpdb_results()`, you need to
5872
-     *                               run it through `EEM_Base::deduce_fields_n_values_from_cols_n_values()`
5873
-     *                               before passing it to this function (that will convert it from columns-n-values
5874
-     *                               to field-names-n-values).
5875
-     * @return string
5876
-     * @throws EE_Error
5877
-     */
5878
-    public function get_index_primary_key_string($fields_n_values)
5879
-    {
5880
-        $cols_n_values_for_primary_key_index = array_intersect_key(
5881
-            $fields_n_values,
5882
-            $this->get_combined_primary_key_fields()
5883
-        );
5884
-        return http_build_query($cols_n_values_for_primary_key_index);
5885
-    }
5886
-
5887
-
5888
-
5889
-    /**
5890
-     * Gets the field values from the primary key string
5891
-     *
5892
-     * @see EEM_Base::get_combined_primary_key_fields() and EEM_Base::get_index_primary_key_string()
5893
-     * @param string $index_primary_key_string
5894
-     * @return null|array
5895
-     * @throws EE_Error
5896
-     */
5897
-    public function parse_index_primary_key_string($index_primary_key_string)
5898
-    {
5899
-        $key_fields = $this->get_combined_primary_key_fields();
5900
-        // check all of them are in the $id
5901
-        $key_vals_in_combined_pk = array();
5902
-        parse_str($index_primary_key_string, $key_vals_in_combined_pk);
5903
-        foreach ($key_fields as $key_field_name => $field_obj) {
5904
-            if (! isset($key_vals_in_combined_pk[ $key_field_name ])) {
5905
-                return null;
5906
-            }
5907
-        }
5908
-        return $key_vals_in_combined_pk;
5909
-    }
5910
-
5911
-
5912
-
5913
-    /**
5914
-     * verifies that an array of key-value pairs for model fields has a key
5915
-     * for each field comprising the primary key index
5916
-     *
5917
-     * @param array $key_vals
5918
-     * @return boolean
5919
-     * @throws EE_Error
5920
-     */
5921
-    public function has_all_combined_primary_key_fields($key_vals)
5922
-    {
5923
-        $keys_it_should_have = array_keys($this->get_combined_primary_key_fields());
5924
-        foreach ($keys_it_should_have as $key) {
5925
-            if (! isset($key_vals[ $key ])) {
5926
-                return false;
5927
-            }
5928
-        }
5929
-        return true;
5930
-    }
5931
-
5932
-
5933
-
5934
-    /**
5935
-     * Finds all model objects in the DB that appear to be a copy of $model_object_or_attributes_array.
5936
-     * We consider something to be a copy if all the attributes match (except the ID, of course).
5937
-     *
5938
-     * @param array|EE_Base_Class $model_object_or_attributes_array If its an array, it's field-value pairs
5939
-     * @param array               $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
5940
-     * @throws EE_Error
5941
-     * @return \EE_Base_Class[] Array keys are object IDs (if there is a primary key on the model. if not, numerically
5942
-     *                                                              indexed)
5943
-     */
5944
-    public function get_all_copies($model_object_or_attributes_array, $query_params = array())
5945
-    {
5946
-        if ($model_object_or_attributes_array instanceof EE_Base_Class) {
5947
-            $attributes_array = $model_object_or_attributes_array->model_field_array();
5948
-        } elseif (is_array($model_object_or_attributes_array)) {
5949
-            $attributes_array = $model_object_or_attributes_array;
5950
-        } else {
5951
-            throw new EE_Error(sprintf(esc_html__(
5952
-                "get_all_copies should be provided with either a model object or an array of field-value-pairs, but was given %s",
5953
-                "event_espresso"
5954
-            ), $model_object_or_attributes_array));
5955
-        }
5956
-        // even copies obviously won't have the same ID, so remove the primary key
5957
-        // from the WHERE conditions for finding copies (if there is a primary key, of course)
5958
-        if ($this->has_primary_key_field() && isset($attributes_array[ $this->primary_key_name() ])) {
5959
-            unset($attributes_array[ $this->primary_key_name() ]);
5960
-        }
5961
-        if (isset($query_params[0])) {
5962
-            $query_params[0] = array_merge($attributes_array, $query_params);
5963
-        } else {
5964
-            $query_params[0] = $attributes_array;
5965
-        }
5966
-        return $this->get_all($query_params);
5967
-    }
5968
-
5969
-
5970
-
5971
-    /**
5972
-     * Gets the first copy we find. See get_all_copies for more details
5973
-     *
5974
-     * @param       mixed EE_Base_Class | array        $model_object_or_attributes_array
5975
-     * @param array $query_params
5976
-     * @return EE_Base_Class
5977
-     * @throws EE_Error
5978
-     */
5979
-    public function get_one_copy($model_object_or_attributes_array, $query_params = array())
5980
-    {
5981
-        if (! is_array($query_params)) {
5982
-            EE_Error::doing_it_wrong(
5983
-                'EEM_Base::get_one_copy',
5984
-                sprintf(
5985
-                    esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
5986
-                    gettype($query_params)
5987
-                ),
5988
-                '4.6.0'
5989
-            );
5990
-            $query_params = array();
5991
-        }
5992
-        $query_params['limit'] = 1;
5993
-        $copies = $this->get_all_copies($model_object_or_attributes_array, $query_params);
5994
-        if (is_array($copies)) {
5995
-            return array_shift($copies);
5996
-        }
5997
-        return null;
5998
-    }
5999
-
6000
-
6001
-
6002
-    /**
6003
-     * Updates the item with the specified id. Ignores default query parameters because
6004
-     * we have specified the ID, and its assumed we KNOW what we're doing
6005
-     *
6006
-     * @param array      $fields_n_values keys are field names, values are their new values
6007
-     * @param int|string $id              the value of the primary key to update
6008
-     * @return int number of rows updated
6009
-     * @throws EE_Error
6010
-     */
6011
-    public function update_by_ID($fields_n_values, $id)
6012
-    {
6013
-        $query_params = array(
6014
-            0                          => array($this->get_primary_key_field()->get_name() => $id),
6015
-            'default_where_conditions' => EEM_Base::default_where_conditions_others_only,
6016
-        );
6017
-        return $this->update($fields_n_values, $query_params);
6018
-    }
6019
-
6020
-
6021
-
6022
-    /**
6023
-     * Changes an operator which was supplied to the models into one usable in SQL
6024
-     *
6025
-     * @param string $operator_supplied
6026
-     * @return string an operator which can be used in SQL
6027
-     * @throws EE_Error
6028
-     */
6029
-    private function _prepare_operator_for_sql($operator_supplied)
6030
-    {
6031
-        $sql_operator = isset($this->_valid_operators[ $operator_supplied ]) ? $this->_valid_operators[ $operator_supplied ]
6032
-            : null;
6033
-        if ($sql_operator) {
6034
-            return $sql_operator;
6035
-        }
6036
-        throw new EE_Error(
6037
-            sprintf(
6038
-                esc_html__(
6039
-                    "The operator '%s' is not in the list of valid operators: %s",
6040
-                    "event_espresso"
6041
-                ),
6042
-                $operator_supplied,
6043
-                implode(",", array_keys($this->_valid_operators))
6044
-            )
6045
-        );
6046
-    }
6047
-
6048
-
6049
-
6050
-    /**
6051
-     * Gets the valid operators
6052
-     * @return array keys are accepted strings, values are the SQL they are converted to
6053
-     */
6054
-    public function valid_operators()
6055
-    {
6056
-        return $this->_valid_operators;
6057
-    }
6058
-
6059
-
6060
-
6061
-    /**
6062
-     * Gets the between-style operators (take 2 arguments).
6063
-     * @return array keys are accepted strings, values are the SQL they are converted to
6064
-     */
6065
-    public function valid_between_style_operators()
6066
-    {
6067
-        return array_intersect(
6068
-            $this->valid_operators(),
6069
-            $this->_between_style_operators
6070
-        );
6071
-    }
6072
-
6073
-    /**
6074
-     * Gets the "like"-style operators (take a single argument, but it may contain wildcards)
6075
-     * @return array keys are accepted strings, values are the SQL they are converted to
6076
-     */
6077
-    public function valid_like_style_operators()
6078
-    {
6079
-        return array_intersect(
6080
-            $this->valid_operators(),
6081
-            $this->_like_style_operators
6082
-        );
6083
-    }
6084
-
6085
-    /**
6086
-     * Gets the "in"-style operators
6087
-     * @return array keys are accepted strings, values are the SQL they are converted to
6088
-     */
6089
-    public function valid_in_style_operators()
6090
-    {
6091
-        return array_intersect(
6092
-            $this->valid_operators(),
6093
-            $this->_in_style_operators
6094
-        );
6095
-    }
6096
-
6097
-    /**
6098
-     * Gets the "null"-style operators (accept no arguments)
6099
-     * @return array keys are accepted strings, values are the SQL they are converted to
6100
-     */
6101
-    public function valid_null_style_operators()
6102
-    {
6103
-        return array_intersect(
6104
-            $this->valid_operators(),
6105
-            $this->_null_style_operators
6106
-        );
6107
-    }
6108
-
6109
-    /**
6110
-     * Gets an array where keys are the primary keys and values are their 'names'
6111
-     * (as determined by the model object's name() function, which is often overridden)
6112
-     *
6113
-     * @param array $query_params like get_all's
6114
-     * @return string[]
6115
-     * @throws EE_Error
6116
-     */
6117
-    public function get_all_names($query_params = array())
6118
-    {
6119
-        $objs = $this->get_all($query_params);
6120
-        $names = array();
6121
-        foreach ($objs as $obj) {
6122
-            $names[ $obj->ID() ] = $obj->name();
6123
-        }
6124
-        return $names;
6125
-    }
6126
-
6127
-
6128
-
6129
-    /**
6130
-     * Gets an array of primary keys from the model objects. If you acquired the model objects
6131
-     * using EEM_Base::get_all() you don't need to call this (and probably shouldn't because
6132
-     * this is duplicated effort and reduces efficiency) you would be better to use
6133
-     * array_keys() on $model_objects.
6134
-     *
6135
-     * @param \EE_Base_Class[] $model_objects
6136
-     * @param boolean          $filter_out_empty_ids if a model object has an ID of '' or 0, don't bother including it
6137
-     *                                               in the returned array
6138
-     * @return array
6139
-     * @throws EE_Error
6140
-     */
6141
-    public function get_IDs($model_objects, $filter_out_empty_ids = false)
6142
-    {
6143
-        if (! $this->has_primary_key_field()) {
6144
-            if (WP_DEBUG) {
6145
-                EE_Error::add_error(
6146
-                    esc_html__('Trying to get IDs from a model than has no primary key', 'event_espresso'),
6147
-                    __FILE__,
6148
-                    __FUNCTION__,
6149
-                    __LINE__
6150
-                );
6151
-            }
6152
-        }
6153
-        $IDs = array();
6154
-        foreach ($model_objects as $model_object) {
6155
-            $id = $model_object->ID();
6156
-            if (! $id) {
6157
-                if ($filter_out_empty_ids) {
6158
-                    continue;
6159
-                }
6160
-                if (WP_DEBUG) {
6161
-                    EE_Error::add_error(
6162
-                        esc_html__(
6163
-                            'Called %1$s on a model object that has no ID and so probably hasn\'t been saved to the database',
6164
-                            'event_espresso'
6165
-                        ),
6166
-                        __FILE__,
6167
-                        __FUNCTION__,
6168
-                        __LINE__
6169
-                    );
6170
-                }
6171
-            }
6172
-            $IDs[] = $id;
6173
-        }
6174
-        return $IDs;
6175
-    }
6176
-
6177
-
6178
-
6179
-    /**
6180
-     * Returns the string used in capabilities relating to this model. If there
6181
-     * are no capabilities that relate to this model returns false
6182
-     *
6183
-     * @return string|false
6184
-     */
6185
-    public function cap_slug()
6186
-    {
6187
-        return apply_filters('FHEE__EEM_Base__cap_slug', $this->_caps_slug, $this);
6188
-    }
6189
-
6190
-
6191
-
6192
-    /**
6193
-     * Returns the capability-restrictions array (@see EEM_Base::_cap_restrictions).
6194
-     * If $context is provided (which should be set to one of EEM_Base::valid_cap_contexts())
6195
-     * only returns the cap restrictions array in that context (ie, the array
6196
-     * at that key)
6197
-     *
6198
-     * @param string $context
6199
-     * @return EE_Default_Where_Conditions[] indexed by associated capability
6200
-     * @throws EE_Error
6201
-     */
6202
-    public function cap_restrictions($context = EEM_Base::caps_read)
6203
-    {
6204
-        EEM_Base::verify_is_valid_cap_context($context);
6205
-        // check if we ought to run the restriction generator first
6206
-        if (
6207
-            isset($this->_cap_restriction_generators[ $context ])
6208
-            && $this->_cap_restriction_generators[ $context ] instanceof EE_Restriction_Generator_Base
6209
-            && ! $this->_cap_restriction_generators[ $context ]->has_generated_cap_restrictions()
6210
-        ) {
6211
-            $this->_cap_restrictions[ $context ] = array_merge(
6212
-                $this->_cap_restrictions[ $context ],
6213
-                $this->_cap_restriction_generators[ $context ]->generate_restrictions()
6214
-            );
6215
-        }
6216
-        // and make sure we've finalized the construction of each restriction
6217
-        foreach ($this->_cap_restrictions[ $context ] as $where_conditions_obj) {
6218
-            if ($where_conditions_obj instanceof EE_Default_Where_Conditions) {
6219
-                $where_conditions_obj->_finalize_construct($this);
6220
-            }
6221
-        }
6222
-        return $this->_cap_restrictions[ $context ];
6223
-    }
6224
-
6225
-
6226
-
6227
-    /**
6228
-     * Indicating whether or not this model thinks its a wp core model
6229
-     *
6230
-     * @return boolean
6231
-     */
6232
-    public function is_wp_core_model()
6233
-    {
6234
-        return $this->_wp_core_model;
6235
-    }
6236
-
6237
-
6238
-
6239
-    /**
6240
-     * Gets all the caps that are missing which impose a restriction on
6241
-     * queries made in this context
6242
-     *
6243
-     * @param string $context one of EEM_Base::caps_ constants
6244
-     * @return EE_Default_Where_Conditions[] indexed by capability name
6245
-     * @throws EE_Error
6246
-     */
6247
-    public function caps_missing($context = EEM_Base::caps_read)
6248
-    {
6249
-        $missing_caps = array();
6250
-        $cap_restrictions = $this->cap_restrictions($context);
6251
-        foreach ($cap_restrictions as $cap => $restriction_if_no_cap) {
6252
-            if (
6253
-                ! EE_Capabilities::instance()
6254
-                                 ->current_user_can($cap, $this->get_this_model_name() . '_model_applying_caps')
6255
-            ) {
6256
-                $missing_caps[ $cap ] = $restriction_if_no_cap;
6257
-            }
6258
-        }
6259
-        return $missing_caps;
6260
-    }
6261
-
6262
-
6263
-
6264
-    /**
6265
-     * Gets the mapping from capability contexts to action strings used in capability names
6266
-     *
6267
-     * @return array keys are one of EEM_Base::valid_cap_contexts(), and values are usually
6268
-     * one of 'read', 'edit', or 'delete'
6269
-     */
6270
-    public function cap_contexts_to_cap_action_map()
6271
-    {
6272
-        return apply_filters(
6273
-            'FHEE__EEM_Base__cap_contexts_to_cap_action_map',
6274
-            $this->_cap_contexts_to_cap_action_map,
6275
-            $this
6276
-        );
6277
-    }
6278
-
6279
-
6280
-
6281
-    /**
6282
-     * Gets the action string for the specified capability context
6283
-     *
6284
-     * @param string $context
6285
-     * @return string one of EEM_Base::cap_contexts_to_cap_action_map() values
6286
-     * @throws EE_Error
6287
-     */
6288
-    public function cap_action_for_context($context)
6289
-    {
6290
-        $mapping = $this->cap_contexts_to_cap_action_map();
6291
-        if (isset($mapping[ $context ])) {
6292
-            return $mapping[ $context ];
6293
-        }
6294
-        if ($action = apply_filters('FHEE__EEM_Base__cap_action_for_context', null, $this, $mapping, $context)) {
6295
-            return $action;
6296
-        }
6297
-        throw new EE_Error(
6298
-            sprintf(
6299
-                esc_html__('Cannot find capability restrictions for context "%1$s", allowed values are:%2$s', 'event_espresso'),
6300
-                $context,
6301
-                implode(',', array_keys($this->cap_contexts_to_cap_action_map()))
6302
-            )
6303
-        );
6304
-    }
6305
-
6306
-
6307
-
6308
-    /**
6309
-     * Returns all the capability contexts which are valid when querying models
6310
-     *
6311
-     * @return array
6312
-     */
6313
-    public static function valid_cap_contexts()
6314
-    {
6315
-        return apply_filters('FHEE__EEM_Base__valid_cap_contexts', array(
6316
-            self::caps_read,
6317
-            self::caps_read_admin,
6318
-            self::caps_edit,
6319
-            self::caps_delete,
6320
-        ));
6321
-    }
6322
-
6323
-
6324
-
6325
-    /**
6326
-     * Returns all valid options for 'default_where_conditions'
6327
-     *
6328
-     * @return array
6329
-     */
6330
-    public static function valid_default_where_conditions()
6331
-    {
6332
-        return array(
6333
-            EEM_Base::default_where_conditions_all,
6334
-            EEM_Base::default_where_conditions_this_only,
6335
-            EEM_Base::default_where_conditions_others_only,
6336
-            EEM_Base::default_where_conditions_minimum_all,
6337
-            EEM_Base::default_where_conditions_minimum_others,
6338
-            EEM_Base::default_where_conditions_none
6339
-        );
6340
-    }
6341
-
6342
-    // public static function default_where_conditions_full
6343
-    /**
6344
-     * Verifies $context is one of EEM_Base::valid_cap_contexts(), if not it throws an exception
6345
-     *
6346
-     * @param string $context
6347
-     * @return bool
6348
-     * @throws EE_Error
6349
-     */
6350
-    public static function verify_is_valid_cap_context($context)
6351
-    {
6352
-        $valid_cap_contexts = EEM_Base::valid_cap_contexts();
6353
-        if (in_array($context, $valid_cap_contexts)) {
6354
-            return true;
6355
-        }
6356
-        throw new EE_Error(
6357
-            sprintf(
6358
-                esc_html__(
6359
-                    'Context "%1$s" passed into model "%2$s" is not a valid context. They are: %3$s',
6360
-                    'event_espresso'
6361
-                ),
6362
-                $context,
6363
-                'EEM_Base',
6364
-                implode(',', $valid_cap_contexts)
6365
-            )
6366
-        );
6367
-    }
6368
-
6369
-
6370
-
6371
-    /**
6372
-     * Clears all the models field caches. This is only useful when a sub-class
6373
-     * might have added a field or something and these caches might be invalidated
6374
-     */
6375
-    protected function _invalidate_field_caches()
6376
-    {
6377
-        $this->_cache_foreign_key_to_fields = array();
6378
-        $this->_cached_fields = null;
6379
-        $this->_cached_fields_non_db_only = null;
6380
-    }
6381
-
6382
-
6383
-
6384
-    /**
6385
-     * Gets the list of all the where query param keys that relate to logic instead of field names
6386
-     * (eg "and", "or", "not").
6387
-     *
6388
-     * @return array
6389
-     */
6390
-    public function logic_query_param_keys()
6391
-    {
6392
-        return $this->_logic_query_param_keys;
6393
-    }
6394
-
6395
-
6396
-
6397
-    /**
6398
-     * Determines whether or not the where query param array key is for a logic query param.
6399
-     * Eg 'OR', 'not*', and 'and*because-i-say-so' should all return true, whereas
6400
-     * 'ATT_fname', 'EVT_name*not-you-or-me', and 'ORG_name' should return false
6401
-     *
6402
-     * @param $query_param_key
6403
-     * @return bool
6404
-     */
6405
-    public function is_logic_query_param_key($query_param_key)
6406
-    {
6407
-        foreach ($this->logic_query_param_keys() as $logic_query_param_key) {
6408
-            if (
6409
-                $query_param_key === $logic_query_param_key
6410
-                || strpos($query_param_key, $logic_query_param_key . '*') === 0
6411
-            ) {
6412
-                return true;
6413
-            }
6414
-        }
6415
-        return false;
6416
-    }
6417
-
6418
-    /**
6419
-     * Returns true if this model has a password field on it (regardless of whether that password field has any content)
6420
-     * @since 4.9.74.p
6421
-     * @return boolean
6422
-     */
6423
-    public function hasPassword()
6424
-    {
6425
-        // if we don't yet know if there's a password field, find out and remember it for next time.
6426
-        if ($this->has_password_field === null) {
6427
-            $password_field = $this->getPasswordField();
6428
-            $this->has_password_field = $password_field instanceof EE_Password_Field ? true : false;
6429
-        }
6430
-        return $this->has_password_field;
6431
-    }
6432
-
6433
-    /**
6434
-     * Returns the password field on this model, if there is one
6435
-     * @since 4.9.74.p
6436
-     * @return EE_Password_Field|null
6437
-     */
6438
-    public function getPasswordField()
6439
-    {
6440
-        // if we definetely already know there is a password field or not (because has_password_field is true or false)
6441
-        // there's no need to search for it. If we don't know yet, then find out
6442
-        if ($this->has_password_field === null && $this->password_field === null) {
6443
-            $this->password_field = $this->get_a_field_of_type('EE_Password_Field');
6444
-        }
6445
-        // don't bother setting has_password_field because that's hasPassword()'s job.
6446
-        return $this->password_field;
6447
-    }
6448
-
6449
-
6450
-    /**
6451
-     * Returns the list of field (as EE_Model_Field_Bases) that are protected by the password
6452
-     * @since 4.9.74.p
6453
-     * @return EE_Model_Field_Base[]
6454
-     * @throws EE_Error
6455
-     */
6456
-    public function getPasswordProtectedFields()
6457
-    {
6458
-        $password_field = $this->getPasswordField();
6459
-        $fields = array();
6460
-        if ($password_field instanceof EE_Password_Field) {
6461
-            $field_names = $password_field->protectedFields();
6462
-            foreach ($field_names as $field_name) {
6463
-                $fields[ $field_name ] = $this->field_settings_for($field_name);
6464
-            }
6465
-        }
6466
-        return $fields;
6467
-    }
6468
-
6469
-
6470
-    /**
6471
-     * Checks if the current user can perform the requested action on this model
6472
-     * @since 4.9.74.p
6473
-     * @param string $cap_to_check one of the array keys from _cap_contexts_to_cap_action_map
6474
-     * @param EE_Base_Class|array $model_obj_or_fields_n_values
6475
-     * @return bool
6476
-     * @throws EE_Error
6477
-     * @throws InvalidArgumentException
6478
-     * @throws InvalidDataTypeException
6479
-     * @throws InvalidInterfaceException
6480
-     * @throws ReflectionException
6481
-     * @throws UnexpectedEntityException
6482
-     */
6483
-    public function currentUserCan($cap_to_check, $model_obj_or_fields_n_values)
6484
-    {
6485
-        if ($model_obj_or_fields_n_values instanceof EE_Base_Class) {
6486
-            $model_obj_or_fields_n_values = $model_obj_or_fields_n_values->model_field_array();
6487
-        }
6488
-        if (!is_array($model_obj_or_fields_n_values)) {
6489
-            throw new UnexpectedEntityException(
6490
-                $model_obj_or_fields_n_values,
6491
-                'EE_Base_Class',
6492
-                sprintf(
6493
-                    esc_html__('%1$s must be passed an `EE_Base_Class or an array of fields names with their values. You passed in something different.', 'event_espresso'),
6494
-                    __FUNCTION__
6495
-                )
6496
-            );
6497
-        }
6498
-        return $this->exists(
6499
-            $this->alter_query_params_to_restrict_by_ID(
6500
-                $this->get_index_primary_key_string($model_obj_or_fields_n_values),
6501
-                array(
6502
-                    'default_where_conditions' => 'none',
6503
-                    'caps'                     => $cap_to_check,
6504
-                )
6505
-            )
6506
-        );
6507
-    }
6508
-
6509
-    /**
6510
-     * Returns the query param where conditions key to the password affecting this model.
6511
-     * Eg on EEM_Event this would just be "password", on EEM_Datetime this would be "Event.password", etc.
6512
-     * @since 4.9.74.p
6513
-     * @return null|string
6514
-     * @throws EE_Error
6515
-     * @throws InvalidArgumentException
6516
-     * @throws InvalidDataTypeException
6517
-     * @throws InvalidInterfaceException
6518
-     * @throws ModelConfigurationException
6519
-     * @throws ReflectionException
6520
-     */
6521
-    public function modelChainAndPassword()
6522
-    {
6523
-        if ($this->model_chain_to_password === null) {
6524
-            throw new ModelConfigurationException(
6525
-                $this,
6526
-                esc_html_x(
6527
-                // @codingStandardsIgnoreStart
6528
-                    'Cannot exclude protected data because the model has not specified which model has the password.',
6529
-                    // @codingStandardsIgnoreEnd
6530
-                    '1: model name',
6531
-                    'event_espresso'
6532
-                )
6533
-            );
6534
-        }
6535
-        if ($this->model_chain_to_password === '') {
6536
-            $model_with_password = $this;
6537
-        } else {
6538
-            if ($pos_of_period = strrpos($this->model_chain_to_password, '.')) {
6539
-                $last_model_in_chain = substr($this->model_chain_to_password, $pos_of_period + 1);
6540
-            } else {
6541
-                $last_model_in_chain = $this->model_chain_to_password;
6542
-            }
6543
-            $model_with_password = EE_Registry::instance()->load_model($last_model_in_chain);
6544
-        }
6545
-
6546
-        $password_field = $model_with_password->getPasswordField();
6547
-        if ($password_field instanceof EE_Password_Field) {
6548
-            $password_field_name = $password_field->get_name();
6549
-        } else {
6550
-            throw new ModelConfigurationException(
6551
-                $this,
6552
-                sprintf(
6553
-                    esc_html_x(
6554
-                        '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"',
6555
-                        '1: model name, 2: special string',
6556
-                        'event_espresso'
6557
-                    ),
6558
-                    $model_with_password->get_this_model_name(),
6559
-                    $this->model_chain_to_password
6560
-                )
6561
-            );
6562
-        }
6563
-        return ($this->model_chain_to_password ? $this->model_chain_to_password . '.' : '') . $password_field_name;
6564
-    }
6565
-
6566
-    /**
6567
-     * Returns true if there is a password on a related model which restricts access to some of this model's rows,
6568
-     * or if this model itself has a password affecting access to some of its other fields.
6569
-     * @since 4.9.74.p
6570
-     * @return boolean
6571
-     */
6572
-    public function restrictedByRelatedModelPassword()
6573
-    {
6574
-        return $this->model_chain_to_password !== null;
6575
-    }
3864
+		}
3865
+		return $null_friendly_where_conditions;
3866
+	}
3867
+
3868
+
3869
+
3870
+	/**
3871
+	 * Uses the _default_where_conditions_strategy set during __construct() to get
3872
+	 * default where conditions on all get_all, update, and delete queries done by this model.
3873
+	 * Use the same syntax as client code. Eg on the Event model, use array('Event.EVT_post_type'=>'esp_event'),
3874
+	 * NOT array('Event_CPT.post_type'=>'esp_event').
3875
+	 *
3876
+	 * @param string $model_relation_path eg, path from Event to Payment is "Registration.Transaction.Payment."
3877
+	 * @return array @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3878
+	 */
3879
+	private function _get_default_where_conditions($model_relation_path = '')
3880
+	{
3881
+		if ($this->_ignore_where_strategy) {
3882
+			return array();
3883
+		}
3884
+		return $this->_default_where_conditions_strategy->get_default_where_conditions($model_relation_path);
3885
+	}
3886
+
3887
+
3888
+
3889
+	/**
3890
+	 * Uses the _minimum_where_conditions_strategy set during __construct() to get
3891
+	 * minimum where conditions on all get_all, update, and delete queries done by this model.
3892
+	 * Use the same syntax as client code. Eg on the Event model, use array('Event.EVT_post_type'=>'esp_event'),
3893
+	 * NOT array('Event_CPT.post_type'=>'esp_event').
3894
+	 * Similar to _get_default_where_conditions
3895
+	 *
3896
+	 * @param string $model_relation_path eg, path from Event to Payment is "Registration.Transaction.Payment."
3897
+	 * @return array @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3898
+	 */
3899
+	protected function _get_minimum_where_conditions($model_relation_path = '')
3900
+	{
3901
+		if ($this->_ignore_where_strategy) {
3902
+			return array();
3903
+		}
3904
+		return $this->_minimum_where_conditions_strategy->get_default_where_conditions($model_relation_path);
3905
+	}
3906
+
3907
+
3908
+
3909
+	/**
3910
+	 * Creates the string of SQL for the select part of a select query, everything behind SELECT and before FROM.
3911
+	 * Eg, "Event.post_id, Event.post_name,Event_Detail.EVT_ID..."
3912
+	 *
3913
+	 * @param EE_Model_Query_Info_Carrier $model_query_info
3914
+	 * @return string
3915
+	 * @throws EE_Error
3916
+	 */
3917
+	private function _construct_default_select_sql(EE_Model_Query_Info_Carrier $model_query_info)
3918
+	{
3919
+		$selects = $this->_get_columns_to_select_for_this_model();
3920
+		foreach (
3921
+			$model_query_info->get_model_names_included() as $model_relation_chain => $name_of_other_model_included
3922
+		) {
3923
+			$other_model_included = $this->get_related_model_obj($name_of_other_model_included);
3924
+			$other_model_selects = $other_model_included->_get_columns_to_select_for_this_model($model_relation_chain);
3925
+			foreach ($other_model_selects as $key => $value) {
3926
+				$selects[] = $value;
3927
+			}
3928
+		}
3929
+		return implode(", ", $selects);
3930
+	}
3931
+
3932
+
3933
+
3934
+	/**
3935
+	 * Gets an array of columns to select for this model, which are necessary for it to create its objects.
3936
+	 * So that's going to be the columns for all the fields on the model
3937
+	 *
3938
+	 * @param string $model_relation_chain like 'Question.Question_Group.Event'
3939
+	 * @return array numerically indexed, values are columns to select and rename, eg "Event.ID AS 'Event.ID'"
3940
+	 */
3941
+	public function _get_columns_to_select_for_this_model($model_relation_chain = '')
3942
+	{
3943
+		$fields = $this->field_settings();
3944
+		$selects = array();
3945
+		$table_alias_with_model_relation_chain_prefix = EE_Model_Parser::extract_table_alias_model_relation_chain_prefix(
3946
+			$model_relation_chain,
3947
+			$this->get_this_model_name()
3948
+		);
3949
+		foreach ($fields as $field_obj) {
3950
+			$selects[] = $table_alias_with_model_relation_chain_prefix
3951
+						 . $field_obj->get_table_alias()
3952
+						 . "."
3953
+						 . $field_obj->get_table_column()
3954
+						 . " AS '"
3955
+						 . $table_alias_with_model_relation_chain_prefix
3956
+						 . $field_obj->get_table_alias()
3957
+						 . "."
3958
+						 . $field_obj->get_table_column()
3959
+						 . "'";
3960
+		}
3961
+		// make sure we are also getting the PKs of each table
3962
+		$tables = $this->get_tables();
3963
+		if (count($tables) > 1) {
3964
+			foreach ($tables as $table_obj) {
3965
+				$qualified_pk_column = $table_alias_with_model_relation_chain_prefix
3966
+									   . $table_obj->get_fully_qualified_pk_column();
3967
+				if (! in_array($qualified_pk_column, $selects)) {
3968
+					$selects[] = "$qualified_pk_column AS '$qualified_pk_column'";
3969
+				}
3970
+			}
3971
+		}
3972
+		return $selects;
3973
+	}
3974
+
3975
+
3976
+
3977
+	/**
3978
+	 * Given a $query_param like 'Registration.Transaction.TXN_ID', pops off 'Registration.',
3979
+	 * gets the join statement for it; gets the data types for it; and passes the remaining 'Transaction.TXN_ID'
3980
+	 * onto its related Transaction object to do the same. Returns an EE_Join_And_Data_Types object which contains the
3981
+	 * SQL for joining, and the data types
3982
+	 *
3983
+	 * @param null|string                 $original_query_param
3984
+	 * @param string                      $query_param          like Registration.Transaction.TXN_ID
3985
+	 * @param EE_Model_Query_Info_Carrier $passed_in_query_info
3986
+	 * @param    string                   $query_param_type     like Registration.Transaction.TXN_ID
3987
+	 *                                                          or 'PAY_ID'. Otherwise, we don't expect there to be a
3988
+	 *                                                          column name. We only want model names, eg 'Event.Venue'
3989
+	 *                                                          or 'Registration's
3990
+	 * @param string                      $original_query_param what it originally was (eg
3991
+	 *                                                          Registration.Transaction.TXN_ID). If null, we assume it
3992
+	 *                                                          matches $query_param
3993
+	 * @throws EE_Error
3994
+	 * @return void only modifies the EEM_Related_Model_Info_Carrier passed into it
3995
+	 */
3996
+	private function _extract_related_model_info_from_query_param(
3997
+		$query_param,
3998
+		EE_Model_Query_Info_Carrier $passed_in_query_info,
3999
+		$query_param_type,
4000
+		$original_query_param = null
4001
+	) {
4002
+		if ($original_query_param === null) {
4003
+			$original_query_param = $query_param;
4004
+		}
4005
+		$query_param = $this->_remove_stars_and_anything_after_from_condition_query_param_key($query_param);
4006
+		// check to see if we have a field on this model
4007
+		$this_model_fields = $this->field_settings(true);
4008
+		if (array_key_exists($query_param, $this_model_fields)) {
4009
+			$field_is_allowed = in_array(
4010
+				$query_param_type,
4011
+				[0, 'where', 'having', 'order_by', 'group_by', 'order', 'custom_selects'],
4012
+				true
4013
+			);
4014
+			if ($field_is_allowed) {
4015
+				return;
4016
+			}
4017
+			throw new EE_Error(
4018
+				sprintf(
4019
+					esc_html__(
4020
+						"Using a field name (%s) on model %s is not allowed on this query param type '%s'. Original query param was %s",
4021
+						"event_espresso"
4022
+					),
4023
+					$query_param,
4024
+					get_class($this),
4025
+					$query_param_type,
4026
+					$original_query_param
4027
+				)
4028
+			);
4029
+		}
4030
+		// check if this is a special logic query param
4031
+		if (in_array($query_param, $this->_logic_query_param_keys, true)) {
4032
+			$operator_is_allowed = in_array($query_param_type, ['where', 'having', 0, 'custom_selects'], true);
4033
+			if ($operator_is_allowed) {
4034
+				return;
4035
+			}
4036
+			throw new EE_Error(
4037
+				sprintf(
4038
+					esc_html__(
4039
+						'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',
4040
+						'event_espresso'
4041
+					),
4042
+					implode('", "', $this->_logic_query_param_keys),
4043
+					$query_param,
4044
+					get_class($this),
4045
+					'<br />',
4046
+					"\t"
4047
+					. ' $passed_in_query_info = <pre>'
4048
+					. print_r($passed_in_query_info, true)
4049
+					. '</pre>'
4050
+					. "\n\t"
4051
+					. ' $query_param_type = '
4052
+					. $query_param_type
4053
+					. "\n\t"
4054
+					. ' $original_query_param = '
4055
+					. $original_query_param
4056
+				)
4057
+			);
4058
+		}
4059
+		// check if it's a custom selection
4060
+		if (
4061
+			$this->_custom_selections instanceof CustomSelects
4062
+			&& in_array($query_param, $this->_custom_selections->columnAliases(), true)
4063
+		) {
4064
+			return;
4065
+		}
4066
+		// check if has a model name at the beginning
4067
+		// and
4068
+		// check if it's a field on a related model
4069
+		if (
4070
+			$this->extractJoinModelFromQueryParams(
4071
+				$passed_in_query_info,
4072
+				$query_param,
4073
+				$original_query_param,
4074
+				$query_param_type
4075
+			)
4076
+		) {
4077
+			return;
4078
+		}
4079
+
4080
+		// ok so $query_param didn't start with a model name
4081
+		// and we previously confirmed it wasn't a logic query param or field on the current model
4082
+		// it's wack, that's what it is
4083
+		throw new EE_Error(
4084
+			sprintf(
4085
+				esc_html__(
4086
+					"There is no model named '%s' related to %s. Query param type is %s and original query param is %s",
4087
+					"event_espresso"
4088
+				),
4089
+				$query_param,
4090
+				get_class($this),
4091
+				$query_param_type,
4092
+				$original_query_param
4093
+			)
4094
+		);
4095
+	}
4096
+
4097
+
4098
+	/**
4099
+	 * Extracts any possible join model information from the provided possible_join_string.
4100
+	 * This method will read the provided $possible_join_string value and determine if there are any possible model join
4101
+	 * parts that should be added to the query.
4102
+	 *
4103
+	 * @param EE_Model_Query_Info_Carrier $query_info_carrier
4104
+	 * @param string                      $possible_join_string  Such as Registration.REG_ID, or Registration
4105
+	 * @param null|string                 $original_query_param
4106
+	 * @param string                      $query_parameter_type  The type for the source of the $possible_join_string
4107
+	 *                                                           ('where', 'order_by', 'group_by', 'custom_selects' etc.)
4108
+	 * @return bool  returns true if a join was added and false if not.
4109
+	 * @throws EE_Error
4110
+	 */
4111
+	private function extractJoinModelFromQueryParams(
4112
+		EE_Model_Query_Info_Carrier $query_info_carrier,
4113
+		$possible_join_string,
4114
+		$original_query_param,
4115
+		$query_parameter_type
4116
+	) {
4117
+		foreach ($this->_model_relations as $valid_related_model_name => $relation_obj) {
4118
+			if (strpos($possible_join_string, $valid_related_model_name . ".") === 0) {
4119
+				$this->_add_join_to_model($valid_related_model_name, $query_info_carrier, $original_query_param);
4120
+				$possible_join_string = substr($possible_join_string, strlen($valid_related_model_name . "."));
4121
+				if ($possible_join_string === '') {
4122
+					// nothing left to $query_param
4123
+					// we should actually end in a field name, not a model like this!
4124
+					throw new EE_Error(
4125
+						sprintf(
4126
+							esc_html__(
4127
+								"Query param '%s' (of type %s on model %s) shouldn't end on a period (.) ",
4128
+								"event_espresso"
4129
+							),
4130
+							$possible_join_string,
4131
+							$query_parameter_type,
4132
+							get_class($this),
4133
+							$valid_related_model_name
4134
+						)
4135
+					);
4136
+				}
4137
+				$related_model_obj = $this->get_related_model_obj($valid_related_model_name);
4138
+				$related_model_obj->_extract_related_model_info_from_query_param(
4139
+					$possible_join_string,
4140
+					$query_info_carrier,
4141
+					$query_parameter_type,
4142
+					$original_query_param
4143
+				);
4144
+				return true;
4145
+			}
4146
+			if ($possible_join_string === $valid_related_model_name) {
4147
+				$this->_add_join_to_model(
4148
+					$valid_related_model_name,
4149
+					$query_info_carrier,
4150
+					$original_query_param
4151
+				);
4152
+				return true;
4153
+			}
4154
+		}
4155
+		return false;
4156
+	}
4157
+
4158
+
4159
+	/**
4160
+	 * Extracts related models from Custom Selects and sets up any joins for those related models.
4161
+	 * @param EE_Model_Query_Info_Carrier $query_info_carrier
4162
+	 * @throws EE_Error
4163
+	 */
4164
+	private function extractRelatedModelsFromCustomSelects(EE_Model_Query_Info_Carrier $query_info_carrier)
4165
+	{
4166
+		if (
4167
+			$this->_custom_selections instanceof CustomSelects
4168
+			&& (
4169
+				$this->_custom_selections->type() === CustomSelects::TYPE_STRUCTURED
4170
+				|| $this->_custom_selections->type() == CustomSelects::TYPE_COMPLEX
4171
+			)
4172
+		) {
4173
+			$original_selects = $this->_custom_selections->originalSelects();
4174
+			foreach ($original_selects as $alias => $select_configuration) {
4175
+				$this->extractJoinModelFromQueryParams(
4176
+					$query_info_carrier,
4177
+					$select_configuration[0],
4178
+					$select_configuration[0],
4179
+					'custom_selects'
4180
+				);
4181
+			}
4182
+		}
4183
+	}
4184
+
4185
+
4186
+
4187
+	/**
4188
+	 * Privately used by _extract_related_model_info_from_query_param to add a join to $model_name
4189
+	 * and store it on $passed_in_query_info
4190
+	 *
4191
+	 * @param string                      $model_name
4192
+	 * @param EE_Model_Query_Info_Carrier $passed_in_query_info
4193
+	 * @param string                      $original_query_param used to extract the relation chain between the queried
4194
+	 *                                                          model and $model_name. Eg, if we are querying Event,
4195
+	 *                                                          and are adding a join to 'Payment' with the original
4196
+	 *                                                          query param key
4197
+	 *                                                          'Registration.Transaction.Payment.PAY_amount', we want
4198
+	 *                                                          to extract 'Registration.Transaction.Payment', in case
4199
+	 *                                                          Payment wants to add default query params so that it
4200
+	 *                                                          will know what models to prepend onto its default query
4201
+	 *                                                          params or in case it wants to rename tables (in case
4202
+	 *                                                          there are multiple joins to the same table)
4203
+	 * @return void
4204
+	 * @throws EE_Error
4205
+	 */
4206
+	private function _add_join_to_model(
4207
+		$model_name,
4208
+		EE_Model_Query_Info_Carrier $passed_in_query_info,
4209
+		$original_query_param
4210
+	) {
4211
+		$relation_obj = $this->related_settings_for($model_name);
4212
+		$model_relation_chain = EE_Model_Parser::extract_model_relation_chain($model_name, $original_query_param);
4213
+		// check if the relation is HABTM, because then we're essentially doing two joins
4214
+		// If so, join first to the JOIN table, and add its data types, and then continue as normal
4215
+		if ($relation_obj instanceof EE_HABTM_Relation) {
4216
+			$join_model_obj = $relation_obj->get_join_model();
4217
+			// replace the model specified with the join model for this relation chain, whi
4218
+			$relation_chain_to_join_model = EE_Model_Parser::replace_model_name_with_join_model_name_in_model_relation_chain(
4219
+				$model_name,
4220
+				$join_model_obj->get_this_model_name(),
4221
+				$model_relation_chain
4222
+			);
4223
+			$passed_in_query_info->merge(
4224
+				new EE_Model_Query_Info_Carrier(
4225
+					array($relation_chain_to_join_model => $join_model_obj->get_this_model_name()),
4226
+					$relation_obj->get_join_to_intermediate_model_statement($relation_chain_to_join_model)
4227
+				)
4228
+			);
4229
+		}
4230
+		// now just join to the other table pointed to by the relation object, and add its data types
4231
+		$passed_in_query_info->merge(
4232
+			new EE_Model_Query_Info_Carrier(
4233
+				array($model_relation_chain => $model_name),
4234
+				$relation_obj->get_join_statement($model_relation_chain)
4235
+			)
4236
+		);
4237
+	}
4238
+
4239
+
4240
+
4241
+	/**
4242
+	 * Constructs SQL for where clause, like "WHERE Event.ID = 23 AND Transaction.amount > 100" etc.
4243
+	 *
4244
+	 * @param array $where_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
4245
+	 * @return string of SQL
4246
+	 * @throws EE_Error
4247
+	 */
4248
+	private function _construct_where_clause($where_params)
4249
+	{
4250
+		$SQL = $this->_construct_condition_clause_recursive($where_params, ' AND ');
4251
+		if ($SQL) {
4252
+			return " WHERE " . $SQL;
4253
+		}
4254
+		return '';
4255
+	}
4256
+
4257
+
4258
+
4259
+	/**
4260
+	 * Just like the _construct_where_clause, except prepends 'HAVING' instead of 'WHERE',
4261
+	 * and should be passed HAVING parameters, not WHERE parameters
4262
+	 *
4263
+	 * @param array $having_params
4264
+	 * @return string
4265
+	 * @throws EE_Error
4266
+	 */
4267
+	private function _construct_having_clause($having_params)
4268
+	{
4269
+		$SQL = $this->_construct_condition_clause_recursive($having_params, ' AND ');
4270
+		if ($SQL) {
4271
+			return " HAVING " . $SQL;
4272
+		}
4273
+		return '';
4274
+	}
4275
+
4276
+
4277
+	/**
4278
+	 * Used for creating nested WHERE conditions. Eg "WHERE ! (Event.ID = 3 OR ( Event_Meta.meta_key = 'bob' AND
4279
+	 * Event_Meta.meta_value = 'foo'))"
4280
+	 *
4281
+	 * @param array  $where_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
4282
+	 * @param string $glue         joins each subclause together. Should really only be " AND " or " OR "...
4283
+	 * @throws EE_Error
4284
+	 * @return string of SQL
4285
+	 */
4286
+	private function _construct_condition_clause_recursive($where_params, $glue = ' AND')
4287
+	{
4288
+		$where_clauses = array();
4289
+		foreach ($where_params as $query_param => $op_and_value_or_sub_condition) {
4290
+			$query_param = $this->_remove_stars_and_anything_after_from_condition_query_param_key($query_param);
4291
+			if (in_array($query_param, $this->_logic_query_param_keys, true)) {
4292
+				switch ($query_param) {
4293
+					case 'not':
4294
+					case 'NOT':
4295
+						$where_clauses[] = "! ("
4296
+										   . $this->_construct_condition_clause_recursive(
4297
+											   $op_and_value_or_sub_condition,
4298
+											   $glue
4299
+										   )
4300
+										   . ")";
4301
+						break;
4302
+					case 'and':
4303
+					case 'AND':
4304
+						$where_clauses[] = " ("
4305
+										   . $this->_construct_condition_clause_recursive(
4306
+											   $op_and_value_or_sub_condition,
4307
+											   ' AND '
4308
+										   )
4309
+										   . ")";
4310
+						break;
4311
+					case 'or':
4312
+					case 'OR':
4313
+						$where_clauses[] = " ("
4314
+										   . $this->_construct_condition_clause_recursive(
4315
+											   $op_and_value_or_sub_condition,
4316
+											   ' OR '
4317
+										   )
4318
+										   . ")";
4319
+						break;
4320
+				}
4321
+			} else {
4322
+				$field_obj = $this->_deduce_field_from_query_param($query_param);
4323
+				// if it's not a normal field, maybe it's a custom selection?
4324
+				if (! $field_obj) {
4325
+					if ($this->_custom_selections instanceof CustomSelects) {
4326
+						$field_obj = $this->_custom_selections->getDataTypeForAlias($query_param);
4327
+					} else {
4328
+						throw new EE_Error(sprintf(esc_html__(
4329
+							"%s is neither a valid model field name, nor a custom selection",
4330
+							"event_espresso"
4331
+						), $query_param));
4332
+					}
4333
+				}
4334
+				$op_and_value_sql = $this->_construct_op_and_value($op_and_value_or_sub_condition, $field_obj);
4335
+				$where_clauses[] = $this->_deduce_column_name_from_query_param($query_param) . SP . $op_and_value_sql;
4336
+			}
4337
+		}
4338
+		return $where_clauses ? implode($glue, $where_clauses) : '';
4339
+	}
4340
+
4341
+
4342
+
4343
+	/**
4344
+	 * Takes the input parameter and extract the table name (alias) and column name
4345
+	 *
4346
+	 * @param string $query_param like Registration.Transaction.TXN_ID, Event.Datetime.start_time, or REG_ID
4347
+	 * @throws EE_Error
4348
+	 * @return string table alias and column name for SQL, eg "Transaction.TXN_ID"
4349
+	 */
4350
+	private function _deduce_column_name_from_query_param($query_param)
4351
+	{
4352
+		$field = $this->_deduce_field_from_query_param($query_param);
4353
+		if ($field) {
4354
+			$table_alias_prefix = EE_Model_Parser::extract_table_alias_model_relation_chain_from_query_param(
4355
+				$field->get_model_name(),
4356
+				$query_param
4357
+			);
4358
+			return $table_alias_prefix . $field->get_qualified_column();
4359
+		}
4360
+		if (
4361
+			$this->_custom_selections instanceof CustomSelects
4362
+			&& in_array($query_param, $this->_custom_selections->columnAliases(), true)
4363
+		) {
4364
+			// maybe it's custom selection item?
4365
+			// if so, just use it as the "column name"
4366
+			return $query_param;
4367
+		}
4368
+		$custom_select_aliases = $this->_custom_selections instanceof CustomSelects
4369
+			? implode(',', $this->_custom_selections->columnAliases())
4370
+			: '';
4371
+		throw new EE_Error(
4372
+			sprintf(
4373
+				esc_html__(
4374
+					"%s is not a valid field on this model, nor a custom selection (%s)",
4375
+					"event_espresso"
4376
+				),
4377
+				$query_param,
4378
+				$custom_select_aliases
4379
+			)
4380
+		);
4381
+	}
4382
+
4383
+
4384
+
4385
+	/**
4386
+	 * Removes the * and anything after it from the condition query param key. It is useful to add the * to condition
4387
+	 * query param keys (eg, 'OR*', 'EVT_ID') in order for the array keys to still be unique, so that they don't get
4388
+	 * overwritten Takes a string like 'Event.EVT_ID*', 'TXN_total**', 'OR*1st', and 'DTT_reg_start*foobar' to
4389
+	 * 'Event.EVT_ID', 'TXN_total', 'OR', and 'DTT_reg_start', respectively.
4390
+	 *
4391
+	 * @param string $condition_query_param_key
4392
+	 * @return string
4393
+	 */
4394
+	private function _remove_stars_and_anything_after_from_condition_query_param_key($condition_query_param_key)
4395
+	{
4396
+		$pos_of_star = strpos($condition_query_param_key, '*');
4397
+		if ($pos_of_star === false) {
4398
+			return $condition_query_param_key;
4399
+		}
4400
+		$condition_query_param_sans_star = substr($condition_query_param_key, 0, $pos_of_star);
4401
+		return $condition_query_param_sans_star;
4402
+	}
4403
+
4404
+
4405
+
4406
+	/**
4407
+	 * creates the SQL for the operator and the value in a WHERE clause, eg "< 23" or "LIKE '%monkey%'"
4408
+	 *
4409
+	 * @param                            mixed      array | string    $op_and_value
4410
+	 * @param EE_Model_Field_Base|string $field_obj . If string, should be one of EEM_Base::_valid_wpdb_data_types
4411
+	 * @throws EE_Error
4412
+	 * @return string
4413
+	 */
4414
+	private function _construct_op_and_value($op_and_value, $field_obj)
4415
+	{
4416
+		if (is_array($op_and_value)) {
4417
+			$operator = isset($op_and_value[0]) ? $this->_prepare_operator_for_sql($op_and_value[0]) : null;
4418
+			if (! $operator) {
4419
+				$php_array_like_string = array();
4420
+				foreach ($op_and_value as $key => $value) {
4421
+					$php_array_like_string[] = "$key=>$value";
4422
+				}
4423
+				throw new EE_Error(
4424
+					sprintf(
4425
+						esc_html__(
4426
+							"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))",
4427
+							"event_espresso"
4428
+						),
4429
+						implode(",", $php_array_like_string)
4430
+					)
4431
+				);
4432
+			}
4433
+			$value = isset($op_and_value[1]) ? $op_and_value[1] : null;
4434
+		} else {
4435
+			$operator = '=';
4436
+			$value = $op_and_value;
4437
+		}
4438
+		// check to see if the value is actually another field
4439
+		if (is_array($op_and_value) && isset($op_and_value[2]) && $op_and_value[2] == true) {
4440
+			return $operator . SP . $this->_deduce_column_name_from_query_param($value);
4441
+		}
4442
+		if (in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4443
+			// in this case, the value should be an array, or at least a comma-separated list
4444
+			// it will need to handle a little differently
4445
+			$cleaned_value = $this->_construct_in_value($value, $field_obj);
4446
+			// note: $cleaned_value has already been run through $wpdb->prepare()
4447
+			return $operator . SP . $cleaned_value;
4448
+		}
4449
+		if (in_array($operator, $this->valid_between_style_operators()) && is_array($value)) {
4450
+			// the value should be an array with count of two.
4451
+			if (count($value) !== 2) {
4452
+				throw new EE_Error(
4453
+					sprintf(
4454
+						esc_html__(
4455
+							"The '%s' operator must be used with an array of values and there must be exactly TWO values in that array.",
4456
+							'event_espresso'
4457
+						),
4458
+						"BETWEEN"
4459
+					)
4460
+				);
4461
+			}
4462
+			$cleaned_value = $this->_construct_between_value($value, $field_obj);
4463
+			return $operator . SP . $cleaned_value;
4464
+		}
4465
+		if (in_array($operator, $this->valid_null_style_operators())) {
4466
+			if ($value !== null) {
4467
+				throw new EE_Error(
4468
+					sprintf(
4469
+						esc_html__(
4470
+							"You attempted to give a value  (%s) while using a NULL-style operator (%s). That isn't valid",
4471
+							"event_espresso"
4472
+						),
4473
+						$value,
4474
+						$operator
4475
+					)
4476
+				);
4477
+			}
4478
+			return $operator;
4479
+		}
4480
+		if (in_array($operator, $this->valid_like_style_operators()) && ! is_array($value)) {
4481
+			// if the operator is 'LIKE', we want to allow percent signs (%) and not
4482
+			// remove other junk. So just treat it as a string.
4483
+			return $operator . SP . $this->_wpdb_prepare_using_field($value, '%s');
4484
+		}
4485
+		if (! in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4486
+			return $operator . SP . $this->_wpdb_prepare_using_field($value, $field_obj);
4487
+		}
4488
+		if (in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4489
+			throw new EE_Error(
4490
+				sprintf(
4491
+					esc_html__(
4492
+						"Operator '%s' must be used with an array of values, eg 'Registration.REG_ID' => array('%s',array(1,2,3))",
4493
+						'event_espresso'
4494
+					),
4495
+					$operator,
4496
+					$operator
4497
+				)
4498
+			);
4499
+		}
4500
+		if (! in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4501
+			throw new EE_Error(
4502
+				sprintf(
4503
+					esc_html__(
4504
+						"Operator '%s' must be used with a single value, not an array. Eg 'Registration.REG_ID => array('%s',23))",
4505
+						'event_espresso'
4506
+					),
4507
+					$operator,
4508
+					$operator
4509
+				)
4510
+			);
4511
+		}
4512
+		throw new EE_Error(
4513
+			sprintf(
4514
+				esc_html__(
4515
+					"It appears you've provided some totally invalid query parameters. Operator and value were:'%s', which isn't right at all",
4516
+					"event_espresso"
4517
+				),
4518
+				http_build_query($op_and_value)
4519
+			)
4520
+		);
4521
+	}
4522
+
4523
+
4524
+
4525
+	/**
4526
+	 * Creates the operands to be used in a BETWEEN query, eg "'2014-12-31 20:23:33' AND '2015-01-23 12:32:54'"
4527
+	 *
4528
+	 * @param array                      $values
4529
+	 * @param EE_Model_Field_Base|string $field_obj if string, it should be the datatype to be used when querying, eg
4530
+	 *                                              '%s'
4531
+	 * @return string
4532
+	 * @throws EE_Error
4533
+	 */
4534
+	public function _construct_between_value($values, $field_obj)
4535
+	{
4536
+		$cleaned_values = array();
4537
+		foreach ($values as $value) {
4538
+			$cleaned_values[] = $this->_wpdb_prepare_using_field($value, $field_obj);
4539
+		}
4540
+		return $cleaned_values[0] . " AND " . $cleaned_values[1];
4541
+	}
4542
+
4543
+
4544
+	/**
4545
+	 * Takes an array or a comma-separated list of $values and cleans them
4546
+	 * according to $data_type using $wpdb->prepare, and then makes the list a
4547
+	 * string surrounded by ( and ). Eg, _construct_in_value(array(1,2,3),'%d') would
4548
+	 * return '(1,2,3)'; _construct_in_value("1,2,hack",'%d') would return '(1,2,1)' (assuming
4549
+	 * I'm right that a string, when interpreted as a digit, becomes a 1. It might become a 0)
4550
+	 *
4551
+	 * @param mixed                      $values    array or comma-separated string
4552
+	 * @param EE_Model_Field_Base|string $field_obj if string, it should be a wpdb data type like '%s', or '%d'
4553
+	 * @return string of SQL to follow an 'IN' or 'NOT IN' operator
4554
+	 * @throws EE_Error
4555
+	 */
4556
+	public function _construct_in_value($values, $field_obj)
4557
+	{
4558
+		$prepped = [];
4559
+		// check if the value is a CSV list
4560
+		if (is_string($values)) {
4561
+			// in which case, turn it into an array
4562
+			$values = explode(',', $values);
4563
+		}
4564
+		// make sure we only have one of each value in the list
4565
+		$values = array_unique($values);
4566
+		foreach ($values as $value) {
4567
+			$prepped[] = $this->_wpdb_prepare_using_field($value, $field_obj);
4568
+		}
4569
+		// we would just LOVE to leave $cleaned_values as an empty array, and return the value as "()",
4570
+		// but unfortunately that's invalid SQL. So instead we return a string which we KNOW will evaluate to be the empty set
4571
+		// which is effectively equivalent to returning "()". We don't return "(0)" because that only works for auto-incrementing columns
4572
+		if (empty($prepped)) {
4573
+			$all_fields = $this->field_settings();
4574
+			$first_field    = reset($all_fields);
4575
+			$main_table = $this->_get_main_table();
4576
+			$prepped[]  = "SELECT {$first_field->get_table_column()} FROM {$main_table->get_table_name()} WHERE FALSE";
4577
+		}
4578
+		return '(' . implode(',', $prepped) . ')';
4579
+	}
4580
+
4581
+
4582
+
4583
+	/**
4584
+	 * @param mixed                      $value
4585
+	 * @param EE_Model_Field_Base|string $field_obj if string it should be a wpdb data type like '%d'
4586
+	 * @throws EE_Error
4587
+	 * @return false|null|string
4588
+	 */
4589
+	private function _wpdb_prepare_using_field($value, $field_obj)
4590
+	{
4591
+		/** @type WPDB $wpdb */
4592
+		global $wpdb;
4593
+		if ($field_obj instanceof EE_Model_Field_Base) {
4594
+			return $wpdb->prepare(
4595
+				$field_obj->get_wpdb_data_type(),
4596
+				$this->_prepare_value_for_use_in_db($value, $field_obj)
4597
+			);
4598
+		} //$field_obj should really just be a data type
4599
+		if (! in_array($field_obj, $this->_valid_wpdb_data_types)) {
4600
+			throw new EE_Error(
4601
+				sprintf(
4602
+					esc_html__("%s is not a valid wpdb datatype. Valid ones are %s", "event_espresso"),
4603
+					$field_obj,
4604
+					implode(",", $this->_valid_wpdb_data_types)
4605
+				)
4606
+			);
4607
+		}
4608
+		return $wpdb->prepare($field_obj, $value);
4609
+	}
4610
+
4611
+
4612
+
4613
+	/**
4614
+	 * Takes the input parameter and finds the model field that it indicates.
4615
+	 *
4616
+	 * @param string $query_param_name like Registration.Transaction.TXN_ID, Event.Datetime.start_time, or REG_ID
4617
+	 * @throws EE_Error
4618
+	 * @return EE_Model_Field_Base
4619
+	 */
4620
+	protected function _deduce_field_from_query_param($query_param_name)
4621
+	{
4622
+		// ok, now proceed with deducing which part is the model's name, and which is the field's name
4623
+		// which will help us find the database table and column
4624
+		$query_param_parts = explode(".", $query_param_name);
4625
+		if (empty($query_param_parts)) {
4626
+			throw new EE_Error(sprintf(esc_html__(
4627
+				"_extract_column_name is empty when trying to extract column and table name from %s",
4628
+				'event_espresso'
4629
+			), $query_param_name));
4630
+		}
4631
+		$number_of_parts = count($query_param_parts);
4632
+		$last_query_param_part = $query_param_parts[ count($query_param_parts) - 1 ];
4633
+		if ($number_of_parts === 1) {
4634
+			$field_name = $last_query_param_part;
4635
+			$model_obj = $this;
4636
+		} else {// $number_of_parts >= 2
4637
+			// the last part is the column name, and there are only 2parts. therefore...
4638
+			$field_name = $last_query_param_part;
4639
+			$model_obj = $this->get_related_model_obj($query_param_parts[ $number_of_parts - 2 ]);
4640
+		}
4641
+		try {
4642
+			return $model_obj->field_settings_for($field_name);
4643
+		} catch (EE_Error $e) {
4644
+			return null;
4645
+		}
4646
+	}
4647
+
4648
+
4649
+
4650
+	/**
4651
+	 * Given a field's name (ie, a key in $this->field_settings()), uses the EE_Model_Field object to get the table's
4652
+	 * alias and column which corresponds to it
4653
+	 *
4654
+	 * @param string $field_name
4655
+	 * @throws EE_Error
4656
+	 * @return string
4657
+	 */
4658
+	public function _get_qualified_column_for_field($field_name)
4659
+	{
4660
+		$all_fields = $this->field_settings();
4661
+		$field = isset($all_fields[ $field_name ]) ? $all_fields[ $field_name ] : false;
4662
+		if ($field) {
4663
+			return $field->get_qualified_column();
4664
+		}
4665
+		throw new EE_Error(
4666
+			sprintf(
4667
+				esc_html__(
4668
+					"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.",
4669
+					'event_espresso'
4670
+				),
4671
+				$field_name,
4672
+				get_class($this)
4673
+			)
4674
+		);
4675
+	}
4676
+
4677
+
4678
+
4679
+	/**
4680
+	 * similar to \EEM_Base::_get_qualified_column_for_field() but returns an array with data for ALL fields.
4681
+	 * Example usage:
4682
+	 * EEM_Ticket::instance()->get_all_wpdb_results(
4683
+	 *      array(),
4684
+	 *      ARRAY_A,
4685
+	 *      EEM_Ticket::instance()->get_qualified_columns_for_all_fields()
4686
+	 *  );
4687
+	 * is equivalent to
4688
+	 *  EEM_Ticket::instance()->get_all_wpdb_results( array(), ARRAY_A, '*' );
4689
+	 * and
4690
+	 *  EEM_Event::instance()->get_all_wpdb_results(
4691
+	 *      array(
4692
+	 *          array(
4693
+	 *              'Datetime.Ticket.TKT_ID' => array( '<', 100 ),
4694
+	 *          ),
4695
+	 *          ARRAY_A,
4696
+	 *          implode(
4697
+	 *              ', ',
4698
+	 *              array_merge(
4699
+	 *                  EEM_Event::instance()->get_qualified_columns_for_all_fields( '', false ),
4700
+	 *                  EEM_Ticket::instance()->get_qualified_columns_for_all_fields( 'Datetime', false )
4701
+	 *              )
4702
+	 *          )
4703
+	 *      )
4704
+	 *  );
4705
+	 * selects rows from the database, selecting all the event and ticket columns, where the ticket ID is below 100
4706
+	 *
4707
+	 * @param string $model_relation_chain        the chain of models used to join between the model you want to query
4708
+	 *                                            and the one whose fields you are selecting for example: when querying
4709
+	 *                                            tickets model and selecting fields from the tickets model you would
4710
+	 *                                            leave this parameter empty, because no models are needed to join
4711
+	 *                                            between the queried model and the selected one. Likewise when
4712
+	 *                                            querying the datetime model and selecting fields from the tickets
4713
+	 *                                            model, it would also be left empty, because there is a direct
4714
+	 *                                            relation from datetimes to tickets, so no model is needed to join
4715
+	 *                                            them together. However, when querying from the event model and
4716
+	 *                                            selecting fields from the ticket model, you should provide the string
4717
+	 *                                            'Datetime', indicating that the event model must first join to the
4718
+	 *                                            datetime model in order to find its relation to ticket model.
4719
+	 *                                            Also, when querying from the venue model and selecting fields from
4720
+	 *                                            the ticket model, you should provide the string 'Event.Datetime',
4721
+	 *                                            indicating you need to join the venue model to the event model,
4722
+	 *                                            to the datetime model, in order to find its relation to the ticket model.
4723
+	 *                                            This string is used to deduce the prefix that gets added onto the
4724
+	 *                                            models' tables qualified columns
4725
+	 * @param bool   $return_string               if true, will return a string with qualified column names separated
4726
+	 *                                            by ', ' if false, will simply return a numerically indexed array of
4727
+	 *                                            qualified column names
4728
+	 * @return array|string
4729
+	 */
4730
+	public function get_qualified_columns_for_all_fields($model_relation_chain = '', $return_string = true)
4731
+	{
4732
+		$table_prefix = str_replace('.', '__', $model_relation_chain) . (empty($model_relation_chain) ? '' : '__');
4733
+		$qualified_columns = array();
4734
+		foreach ($this->field_settings() as $field_name => $field) {
4735
+			$qualified_columns[] = $table_prefix . $field->get_qualified_column();
4736
+		}
4737
+		return $return_string ? implode(', ', $qualified_columns) : $qualified_columns;
4738
+	}
4739
+
4740
+
4741
+
4742
+	/**
4743
+	 * constructs the select use on special limit joins
4744
+	 * NOTE: for now this has only been tested and will work when the  table alias is for the PRIMARY table. Although
4745
+	 * its setup so the select query will be setup on and just doing the special select join off of the primary table
4746
+	 * (as that is typically where the limits would be set).
4747
+	 *
4748
+	 * @param  string       $table_alias The table the select is being built for
4749
+	 * @param  mixed|string $limit       The limit for this select
4750
+	 * @return string                The final select join element for the query.
4751
+	 */
4752
+	public function _construct_limit_join_select($table_alias, $limit)
4753
+	{
4754
+		$SQL = '';
4755
+		foreach ($this->_tables as $table_obj) {
4756
+			if ($table_obj instanceof EE_Primary_Table) {
4757
+				$SQL .= $table_alias === $table_obj->get_table_alias()
4758
+					? $table_obj->get_select_join_limit($limit)
4759
+					: SP . $table_obj->get_table_name() . " AS " . $table_obj->get_table_alias() . SP;
4760
+			} elseif ($table_obj instanceof EE_Secondary_Table) {
4761
+				$SQL .= $table_alias === $table_obj->get_table_alias()
4762
+					? $table_obj->get_select_join_limit_join($limit)
4763
+					: SP . $table_obj->get_join_sql($table_alias) . SP;
4764
+			}
4765
+		}
4766
+		return $SQL;
4767
+	}
4768
+
4769
+
4770
+
4771
+	/**
4772
+	 * Constructs the internal join if there are multiple tables, or simply the table's name and alias
4773
+	 * Eg "wp_post AS Event" or "wp_post AS Event INNER JOIN wp_postmeta Event_Meta ON Event.ID = Event_Meta.post_id"
4774
+	 *
4775
+	 * @return string SQL
4776
+	 * @throws EE_Error
4777
+	 */
4778
+	public function _construct_internal_join()
4779
+	{
4780
+		$SQL = $this->_get_main_table()->get_table_sql();
4781
+		$SQL .= $this->_construct_internal_join_to_table_with_alias($this->_get_main_table()->get_table_alias());
4782
+		return $SQL;
4783
+	}
4784
+
4785
+
4786
+
4787
+	/**
4788
+	 * Constructs the SQL for joining all the tables on this model.
4789
+	 * Normally $alias should be the primary table's alias, but in cases where
4790
+	 * we have already joined to a secondary table (eg, the secondary table has a foreign key and is joined before the
4791
+	 * primary table) then we should provide that secondary table's alias. Eg, with $alias being the primary table's
4792
+	 * alias, this will construct SQL like:
4793
+	 * " INNER JOIN wp_esp_secondary_table AS Secondary_Table ON Primary_Table.pk = Secondary_Table.fk".
4794
+	 * With $alias being a secondary table's alias, this will construct SQL like:
4795
+	 * " INNER JOIN wp_esp_primary_table AS Primary_Table ON Primary_Table.pk = Secondary_Table.fk".
4796
+	 *
4797
+	 * @param string $alias_prefixed table alias to join to (this table should already be in the FROM SQL clause)
4798
+	 * @return string
4799
+	 */
4800
+	public function _construct_internal_join_to_table_with_alias($alias_prefixed)
4801
+	{
4802
+		$SQL = '';
4803
+		$alias_sans_prefix = EE_Model_Parser::remove_table_alias_model_relation_chain_prefix($alias_prefixed);
4804
+		foreach ($this->_tables as $table_obj) {
4805
+			if ($table_obj instanceof EE_Secondary_Table) {// table is secondary table
4806
+				if ($alias_sans_prefix === $table_obj->get_table_alias()) {
4807
+					// so we're joining to this table, meaning the table is already in
4808
+					// the FROM statement, BUT the primary table isn't. So we want
4809
+					// to add the inverse join sql
4810
+					$SQL .= $table_obj->get_inverse_join_sql($alias_prefixed);
4811
+				} else {
4812
+					// just add a regular JOIN to this table from the primary table
4813
+					$SQL .= $table_obj->get_join_sql($alias_prefixed);
4814
+				}
4815
+			}//if it's a primary table, dont add any SQL. it should already be in the FROM statement
4816
+		}
4817
+		return $SQL;
4818
+	}
4819
+
4820
+
4821
+
4822
+	/**
4823
+	 * Gets an array for storing all the data types on the next-to-be-executed-query.
4824
+	 * This should be a growing array of keys being table-columns (eg 'EVT_ID' and 'Event.EVT_ID'), and values being
4825
+	 * their data type (eg, '%s', '%d', etc)
4826
+	 *
4827
+	 * @return array
4828
+	 */
4829
+	public function _get_data_types()
4830
+	{
4831
+		$data_types = array();
4832
+		foreach ($this->field_settings() as $field_obj) {
4833
+			// $data_types[$field_obj->get_table_column()] = $field_obj->get_wpdb_data_type();
4834
+			/** @var $field_obj EE_Model_Field_Base */
4835
+			$data_types[ $field_obj->get_qualified_column() ] = $field_obj->get_wpdb_data_type();
4836
+		}
4837
+		return $data_types;
4838
+	}
4839
+
4840
+
4841
+
4842
+	/**
4843
+	 * Gets the model object given the relation's name / model's name (eg, 'Event', 'Registration',etc. Always singular)
4844
+	 *
4845
+	 * @param string $model_name
4846
+	 * @throws EE_Error
4847
+	 * @return EEM_Base
4848
+	 */
4849
+	public function get_related_model_obj($model_name)
4850
+	{
4851
+		$model_classname = "EEM_" . $model_name;
4852
+		if (! class_exists($model_classname)) {
4853
+			throw new EE_Error(sprintf(esc_html__(
4854
+				"You specified a related model named %s in your query. No such model exists, if it did, it would have the classname %s",
4855
+				'event_espresso'
4856
+			), $model_name, $model_classname));
4857
+		}
4858
+		return call_user_func($model_classname . "::instance");
4859
+	}
4860
+
4861
+
4862
+
4863
+	/**
4864
+	 * Returns the array of EE_ModelRelations for this model.
4865
+	 *
4866
+	 * @return EE_Model_Relation_Base[]
4867
+	 */
4868
+	public function relation_settings()
4869
+	{
4870
+		return $this->_model_relations;
4871
+	}
4872
+
4873
+
4874
+
4875
+	/**
4876
+	 * Gets all related models that this model BELONGS TO. Handy to know sometimes
4877
+	 * because without THOSE models, this model probably doesn't have much purpose.
4878
+	 * (Eg, without an event, datetimes have little purpose.)
4879
+	 *
4880
+	 * @return EE_Belongs_To_Relation[]
4881
+	 */
4882
+	public function belongs_to_relations()
4883
+	{
4884
+		$belongs_to_relations = array();
4885
+		foreach ($this->relation_settings() as $model_name => $relation_obj) {
4886
+			if ($relation_obj instanceof EE_Belongs_To_Relation) {
4887
+				$belongs_to_relations[ $model_name ] = $relation_obj;
4888
+			}
4889
+		}
4890
+		return $belongs_to_relations;
4891
+	}
4892
+
4893
+
4894
+
4895
+	/**
4896
+	 * Returns the specified EE_Model_Relation, or throws an exception
4897
+	 *
4898
+	 * @param string $relation_name name of relation, key in $this->_relatedModels
4899
+	 * @throws EE_Error
4900
+	 * @return EE_Model_Relation_Base
4901
+	 */
4902
+	public function related_settings_for($relation_name)
4903
+	{
4904
+		$relatedModels = $this->relation_settings();
4905
+		if (! array_key_exists($relation_name, $relatedModels)) {
4906
+			throw new EE_Error(
4907
+				sprintf(
4908
+					esc_html__(
4909
+						'Cannot get %s related to %s. There is no model relation of that type. There is, however, %s...',
4910
+						'event_espresso'
4911
+					),
4912
+					$relation_name,
4913
+					$this->_get_class_name(),
4914
+					implode(', ', array_keys($relatedModels))
4915
+				)
4916
+			);
4917
+		}
4918
+		return $relatedModels[ $relation_name ];
4919
+	}
4920
+
4921
+
4922
+
4923
+	/**
4924
+	 * A convenience method for getting a specific field's settings, instead of getting all field settings for all
4925
+	 * fields
4926
+	 *
4927
+	 * @param string $fieldName
4928
+	 * @param boolean $include_db_only_fields
4929
+	 * @throws EE_Error
4930
+	 * @return EE_Model_Field_Base
4931
+	 */
4932
+	public function field_settings_for($fieldName, $include_db_only_fields = true)
4933
+	{
4934
+		$fieldSettings = $this->field_settings($include_db_only_fields);
4935
+		if (! array_key_exists($fieldName, $fieldSettings)) {
4936
+			throw new EE_Error(sprintf(
4937
+				esc_html__("There is no field/column '%s' on '%s'", 'event_espresso'),
4938
+				$fieldName,
4939
+				get_class($this)
4940
+			));
4941
+		}
4942
+		return $fieldSettings[ $fieldName ];
4943
+	}
4944
+
4945
+
4946
+
4947
+	/**
4948
+	 * Checks if this field exists on this model
4949
+	 *
4950
+	 * @param string $fieldName a key in the model's _field_settings array
4951
+	 * @return boolean
4952
+	 */
4953
+	public function has_field($fieldName)
4954
+	{
4955
+		$fieldSettings = $this->field_settings(true);
4956
+		if (isset($fieldSettings[ $fieldName ])) {
4957
+			return true;
4958
+		}
4959
+		return false;
4960
+	}
4961
+
4962
+
4963
+
4964
+	/**
4965
+	 * Returns whether or not this model has a relation to the specified model
4966
+	 *
4967
+	 * @param string $relation_name possibly one of the keys in the relation_settings array
4968
+	 * @return boolean
4969
+	 */
4970
+	public function has_relation($relation_name)
4971
+	{
4972
+		$relations = $this->relation_settings();
4973
+		if (isset($relations[ $relation_name ])) {
4974
+			return true;
4975
+		}
4976
+		return false;
4977
+	}
4978
+
4979
+
4980
+
4981
+	/**
4982
+	 * gets the field object of type 'primary_key' from the fieldsSettings attribute.
4983
+	 * Eg, on EE_Answer that would be ANS_ID field object
4984
+	 *
4985
+	 * @param $field_obj
4986
+	 * @return boolean
4987
+	 */
4988
+	public function is_primary_key_field($field_obj)
4989
+	{
4990
+		return $field_obj instanceof EE_Primary_Key_Field_Base ? true : false;
4991
+	}
4992
+
4993
+
4994
+
4995
+	/**
4996
+	 * gets the field object of type 'primary_key' from the fieldsSettings attribute.
4997
+	 * Eg, on EE_Answer that would be ANS_ID field object
4998
+	 *
4999
+	 * @return EE_Primary_Key_Field_Base
5000
+	 * @throws EE_Error
5001
+	 */
5002
+	public function get_primary_key_field()
5003
+	{
5004
+		if ($this->_primary_key_field === null) {
5005
+			foreach ($this->field_settings(true) as $field_obj) {
5006
+				if ($this->is_primary_key_field($field_obj)) {
5007
+					$this->_primary_key_field = $field_obj;
5008
+					break;
5009
+				}
5010
+			}
5011
+			if (! $this->_primary_key_field instanceof EE_Primary_Key_Field_Base) {
5012
+				throw new EE_Error(sprintf(
5013
+					esc_html__("There is no Primary Key defined on model %s", 'event_espresso'),
5014
+					get_class($this)
5015
+				));
5016
+			}
5017
+		}
5018
+		return $this->_primary_key_field;
5019
+	}
5020
+
5021
+
5022
+
5023
+	/**
5024
+	 * Returns whether or not not there is a primary key on this model.
5025
+	 * Internally does some caching.
5026
+	 *
5027
+	 * @return boolean
5028
+	 */
5029
+	public function has_primary_key_field()
5030
+	{
5031
+		if ($this->_has_primary_key_field === null) {
5032
+			try {
5033
+				$this->get_primary_key_field();
5034
+				$this->_has_primary_key_field = true;
5035
+			} catch (EE_Error $e) {
5036
+				$this->_has_primary_key_field = false;
5037
+			}
5038
+		}
5039
+		return $this->_has_primary_key_field;
5040
+	}
5041
+
5042
+
5043
+
5044
+	/**
5045
+	 * Finds the first field of type $field_class_name.
5046
+	 *
5047
+	 * @param string $field_class_name class name of field that you want to find. Eg, EE_Datetime_Field,
5048
+	 *                                 EE_Foreign_Key_Field, etc
5049
+	 * @return EE_Model_Field_Base or null if none is found
5050
+	 */
5051
+	public function get_a_field_of_type($field_class_name)
5052
+	{
5053
+		foreach ($this->field_settings() as $field) {
5054
+			if ($field instanceof $field_class_name) {
5055
+				return $field;
5056
+			}
5057
+		}
5058
+		return null;
5059
+	}
5060
+
5061
+
5062
+
5063
+	/**
5064
+	 * Gets a foreign key field pointing to model.
5065
+	 *
5066
+	 * @param string $model_name eg Event, Registration, not EEM_Event
5067
+	 * @return EE_Foreign_Key_Field_Base
5068
+	 * @throws EE_Error
5069
+	 */
5070
+	public function get_foreign_key_to($model_name)
5071
+	{
5072
+		if (! isset($this->_cache_foreign_key_to_fields[ $model_name ])) {
5073
+			foreach ($this->field_settings() as $field) {
5074
+				if (
5075
+					$field instanceof EE_Foreign_Key_Field_Base
5076
+					&& in_array($model_name, $field->get_model_names_pointed_to())
5077
+				) {
5078
+					$this->_cache_foreign_key_to_fields[ $model_name ] = $field;
5079
+					break;
5080
+				}
5081
+			}
5082
+			if (! isset($this->_cache_foreign_key_to_fields[ $model_name ])) {
5083
+				throw new EE_Error(sprintf(esc_html__(
5084
+					"There is no foreign key field pointing to model %s on model %s",
5085
+					'event_espresso'
5086
+				), $model_name, get_class($this)));
5087
+			}
5088
+		}
5089
+		return $this->_cache_foreign_key_to_fields[ $model_name ];
5090
+	}
5091
+
5092
+
5093
+
5094
+	/**
5095
+	 * Gets the table name (including $wpdb->prefix) for the table alias
5096
+	 *
5097
+	 * @param string $table_alias eg Event, Event_Meta, Registration, Transaction, but maybe
5098
+	 *                            a table alias with a model chain prefix, like 'Venue__Event_Venue___Event_Meta'.
5099
+	 *                            Either one works
5100
+	 * @return string
5101
+	 */
5102
+	public function get_table_for_alias($table_alias)
5103
+	{
5104
+		$table_alias_sans_model_relation_chain_prefix = EE_Model_Parser::remove_table_alias_model_relation_chain_prefix($table_alias);
5105
+		return $this->_tables[ $table_alias_sans_model_relation_chain_prefix ]->get_table_name();
5106
+	}
5107
+
5108
+
5109
+
5110
+	/**
5111
+	 * Returns a flat array of all field son this model, instead of organizing them
5112
+	 * by table_alias as they are in the constructor.
5113
+	 *
5114
+	 * @param bool $include_db_only_fields flag indicating whether or not to include the db-only fields
5115
+	 * @return EE_Model_Field_Base[] where the keys are the field's name
5116
+	 */
5117
+	public function field_settings($include_db_only_fields = false)
5118
+	{
5119
+		if ($include_db_only_fields) {
5120
+			if ($this->_cached_fields === null) {
5121
+				$this->_cached_fields = array();
5122
+				foreach ($this->_fields as $fields_corresponding_to_table) {
5123
+					foreach ($fields_corresponding_to_table as $field_name => $field_obj) {
5124
+						$this->_cached_fields[ $field_name ] = $field_obj;
5125
+					}
5126
+				}
5127
+			}
5128
+			return $this->_cached_fields;
5129
+		}
5130
+		if ($this->_cached_fields_non_db_only === null) {
5131
+			$this->_cached_fields_non_db_only = array();
5132
+			foreach ($this->_fields as $fields_corresponding_to_table) {
5133
+				foreach ($fields_corresponding_to_table as $field_name => $field_obj) {
5134
+					/** @var $field_obj EE_Model_Field_Base */
5135
+					if (! $field_obj->is_db_only_field()) {
5136
+						$this->_cached_fields_non_db_only[ $field_name ] = $field_obj;
5137
+					}
5138
+				}
5139
+			}
5140
+		}
5141
+		return $this->_cached_fields_non_db_only;
5142
+	}
5143
+
5144
+
5145
+
5146
+	/**
5147
+	 *        cycle though array of attendees and create objects out of each item
5148
+	 *
5149
+	 * @access        private
5150
+	 * @param        array $rows of results of $wpdb->get_results($query,ARRAY_A)
5151
+	 * @return \EE_Base_Class[] array keys are primary keys (if there is a primary key on the model. if not,
5152
+	 *                           numerically indexed)
5153
+	 * @throws EE_Error
5154
+	 */
5155
+	protected function _create_objects($rows = array())
5156
+	{
5157
+		$array_of_objects = array();
5158
+		if (empty($rows)) {
5159
+			return array();
5160
+		}
5161
+		$count_if_model_has_no_primary_key = 0;
5162
+		$has_primary_key = $this->has_primary_key_field();
5163
+		$primary_key_field = $has_primary_key ? $this->get_primary_key_field() : null;
5164
+		foreach ((array) $rows as $row) {
5165
+			if (empty($row)) {
5166
+				// wp did its weird thing where it returns an array like array(0=>null), which is totally not helpful...
5167
+				return array();
5168
+			}
5169
+			// check if we've already set this object in the results array,
5170
+			// in which case there's no need to process it further (again)
5171
+			if ($has_primary_key) {
5172
+				$table_pk_value = $this->_get_column_value_with_table_alias_or_not(
5173
+					$row,
5174
+					$primary_key_field->get_qualified_column(),
5175
+					$primary_key_field->get_table_column()
5176
+				);
5177
+				if ($table_pk_value && isset($array_of_objects[ $table_pk_value ])) {
5178
+					continue;
5179
+				}
5180
+			}
5181
+			$classInstance = $this->instantiate_class_from_array_or_object($row);
5182
+			if (! $classInstance) {
5183
+				throw new EE_Error(
5184
+					sprintf(
5185
+						esc_html__('Could not create instance of class %s from row %s', 'event_espresso'),
5186
+						$this->get_this_model_name(),
5187
+						http_build_query($row)
5188
+					)
5189
+				);
5190
+			}
5191
+			// set the timezone on the instantiated objects
5192
+			$classInstance->set_timezone($this->_timezone);
5193
+			// make sure if there is any timezone setting present that we set the timezone for the object
5194
+			$key = $has_primary_key ? $classInstance->ID() : $count_if_model_has_no_primary_key++;
5195
+			$array_of_objects[ $key ] = $classInstance;
5196
+			// also, for all the relations of type BelongsTo, see if we can cache
5197
+			// those related models
5198
+			// (we could do this for other relations too, but if there are conditions
5199
+			// that filtered out some fo the results, then we'd be caching an incomplete set
5200
+			// so it requires a little more thought than just caching them immediately...)
5201
+			foreach ($this->_model_relations as $modelName => $relation_obj) {
5202
+				if ($relation_obj instanceof EE_Belongs_To_Relation) {
5203
+					// check if this model's INFO is present. If so, cache it on the model
5204
+					$other_model = $relation_obj->get_other_model();
5205
+					$other_model_obj_maybe = $other_model->instantiate_class_from_array_or_object($row);
5206
+					// if we managed to make a model object from the results, cache it on the main model object
5207
+					if ($other_model_obj_maybe) {
5208
+						// set timezone on these other model objects if they are present
5209
+						$other_model_obj_maybe->set_timezone($this->_timezone);
5210
+						$classInstance->cache($modelName, $other_model_obj_maybe);
5211
+					}
5212
+				}
5213
+			}
5214
+			// also, if this was a custom select query, let's see if there are any results for the custom select fields
5215
+			// and add them to the object as well.  We'll convert according to the set data_type if there's any set for
5216
+			// the field in the CustomSelects object
5217
+			if ($this->_custom_selections instanceof CustomSelects) {
5218
+				$classInstance->setCustomSelectsValues(
5219
+					$this->getValuesForCustomSelectAliasesFromResults($row)
5220
+				);
5221
+			}
5222
+		}
5223
+		return $array_of_objects;
5224
+	}
5225
+
5226
+
5227
+	/**
5228
+	 * This will parse a given row of results from the db and see if any keys in the results match an alias within the
5229
+	 * current CustomSelects object. This will be used to build an array of values indexed by those keys.
5230
+	 *
5231
+	 * @param array $db_results_row
5232
+	 * @return array
5233
+	 */
5234
+	protected function getValuesForCustomSelectAliasesFromResults(array $db_results_row)
5235
+	{
5236
+		$results = array();
5237
+		if ($this->_custom_selections instanceof CustomSelects) {
5238
+			foreach ($this->_custom_selections->columnAliases() as $alias) {
5239
+				if (isset($db_results_row[ $alias ])) {
5240
+					$results[ $alias ] = $this->convertValueToDataType(
5241
+						$db_results_row[ $alias ],
5242
+						$this->_custom_selections->getDataTypeForAlias($alias)
5243
+					);
5244
+				}
5245
+			}
5246
+		}
5247
+		return $results;
5248
+	}
5249
+
5250
+
5251
+	/**
5252
+	 * This will set the value for the given alias
5253
+	 * @param string $value
5254
+	 * @param string $datatype (one of %d, %s, %f)
5255
+	 * @return int|string|float (int for %d, string for %s, float for %f)
5256
+	 */
5257
+	protected function convertValueToDataType($value, $datatype)
5258
+	{
5259
+		switch ($datatype) {
5260
+			case '%f':
5261
+				return (float) $value;
5262
+			case '%d':
5263
+				return (int) $value;
5264
+			default:
5265
+				return (string) $value;
5266
+		}
5267
+	}
5268
+
5269
+
5270
+	/**
5271
+	 * The purpose of this method is to allow us to create a model object that is not in the db that holds default
5272
+	 * values. A typical example of where this is used is when creating a new item and the initial load of a form.  We
5273
+	 * dont' necessarily want to test for if the object is present but just assume it is BUT load the defaults from the
5274
+	 * object (as set in the model_field!).
5275
+	 *
5276
+	 * @return EE_Base_Class single EE_Base_Class object with default values for the properties.
5277
+	 */
5278
+	public function create_default_object()
5279
+	{
5280
+		$this_model_fields_and_values = array();
5281
+		// setup the row using default values;
5282
+		foreach ($this->field_settings() as $field_name => $field_obj) {
5283
+			$this_model_fields_and_values[ $field_name ] = $field_obj->get_default_value();
5284
+		}
5285
+		$className = $this->_get_class_name();
5286
+		$classInstance = EE_Registry::instance()
5287
+									->load_class($className, array($this_model_fields_and_values), false, false);
5288
+		return $classInstance;
5289
+	}
5290
+
5291
+
5292
+
5293
+	/**
5294
+	 * @param mixed $cols_n_values either an array of where each key is the name of a field, and the value is its value
5295
+	 *                             or an stdClass where each property is the name of a column,
5296
+	 * @return EE_Base_Class
5297
+	 * @throws EE_Error
5298
+	 */
5299
+	public function instantiate_class_from_array_or_object($cols_n_values)
5300
+	{
5301
+		if (! is_array($cols_n_values) && is_object($cols_n_values)) {
5302
+			$cols_n_values = get_object_vars($cols_n_values);
5303
+		}
5304
+		$primary_key = null;
5305
+		// make sure the array only has keys that are fields/columns on this model
5306
+		$this_model_fields_n_values = $this->_deduce_fields_n_values_from_cols_n_values($cols_n_values);
5307
+		if ($this->has_primary_key_field() && isset($this_model_fields_n_values[ $this->primary_key_name() ])) {
5308
+			$primary_key = $this_model_fields_n_values[ $this->primary_key_name() ];
5309
+		}
5310
+		$className = $this->_get_class_name();
5311
+		// check we actually found results that we can use to build our model object
5312
+		// if not, return null
5313
+		if ($this->has_primary_key_field()) {
5314
+			if (empty($this_model_fields_n_values[ $this->primary_key_name() ])) {
5315
+				return null;
5316
+			}
5317
+		} elseif ($this->unique_indexes()) {
5318
+			$first_column = reset($this_model_fields_n_values);
5319
+			if (empty($first_column)) {
5320
+				return null;
5321
+			}
5322
+		}
5323
+		// if there is no primary key or the object doesn't already exist in the entity map, then create a new instance
5324
+		if ($primary_key) {
5325
+			$classInstance = $this->get_from_entity_map($primary_key);
5326
+			if (! $classInstance) {
5327
+				$classInstance = EE_Registry::instance()
5328
+											->load_class(
5329
+												$className,
5330
+												array($this_model_fields_n_values, $this->_timezone),
5331
+												true,
5332
+												false
5333
+											);
5334
+				// add this new object to the entity map
5335
+				$classInstance = $this->add_to_entity_map($classInstance);
5336
+			}
5337
+		} else {
5338
+			$classInstance = EE_Registry::instance()
5339
+										->load_class(
5340
+											$className,
5341
+											array($this_model_fields_n_values, $this->_timezone),
5342
+											true,
5343
+											false
5344
+										);
5345
+		}
5346
+		return $classInstance;
5347
+	}
5348
+
5349
+
5350
+
5351
+	/**
5352
+	 * Gets the model object from the  entity map if it exists
5353
+	 *
5354
+	 * @param int|string $id the ID of the model object
5355
+	 * @return EE_Base_Class
5356
+	 */
5357
+	public function get_from_entity_map($id)
5358
+	{
5359
+		return isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])
5360
+			? $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ] : null;
5361
+	}
5362
+
5363
+
5364
+
5365
+	/**
5366
+	 * add_to_entity_map
5367
+	 * Adds the object to the model's entity mappings
5368
+	 *        Effectively tells the models "Hey, this model object is the most up-to-date representation of the data,
5369
+	 *        and for the remainder of the request, it's even more up-to-date than what's in the database.
5370
+	 *        So, if the database doesn't agree with what's in the entity mapper, ignore the database"
5371
+	 *        If the database gets updated directly and you want the entity mapper to reflect that change,
5372
+	 *        then this method should be called immediately after the update query
5373
+	 * Note: The map is indexed by whatever the current blog id is set (via EEM_Base::$_model_query_blog_id).  This is
5374
+	 * so on multisite, the entity map is specific to the query being done for a specific site.
5375
+	 *
5376
+	 * @param    EE_Base_Class $object
5377
+	 * @throws EE_Error
5378
+	 * @return \EE_Base_Class
5379
+	 */
5380
+	public function add_to_entity_map(EE_Base_Class $object)
5381
+	{
5382
+		$className = $this->_get_class_name();
5383
+		if (! $object instanceof $className) {
5384
+			throw new EE_Error(sprintf(
5385
+				esc_html__("You tried adding a %s to a mapping of %ss", "event_espresso"),
5386
+				is_object($object) ? get_class($object) : $object,
5387
+				$className
5388
+			));
5389
+		}
5390
+		/** @var $object EE_Base_Class */
5391
+		if (! $object->ID()) {
5392
+			throw new EE_Error(sprintf(esc_html__(
5393
+				"You tried storing a model object with NO ID in the %s entity mapper.",
5394
+				"event_espresso"
5395
+			), get_class($this)));
5396
+		}
5397
+		// double check it's not already there
5398
+		$classInstance = $this->get_from_entity_map($object->ID());
5399
+		if ($classInstance) {
5400
+			return $classInstance;
5401
+		}
5402
+		$this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $object->ID() ] = $object;
5403
+		return $object;
5404
+	}
5405
+
5406
+
5407
+
5408
+	/**
5409
+	 * if a valid identifier is provided, then that entity is unset from the entity map,
5410
+	 * if no identifier is provided, then the entire entity map is emptied
5411
+	 *
5412
+	 * @param int|string $id the ID of the model object
5413
+	 * @return boolean
5414
+	 */
5415
+	public function clear_entity_map($id = null)
5416
+	{
5417
+		if (empty($id)) {
5418
+			$this->_entity_map[ EEM_Base::$_model_query_blog_id ] = array();
5419
+			return true;
5420
+		}
5421
+		if (isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])) {
5422
+			unset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ]);
5423
+			return true;
5424
+		}
5425
+		return false;
5426
+	}
5427
+
5428
+
5429
+
5430
+	/**
5431
+	 * Public wrapper for _deduce_fields_n_values_from_cols_n_values.
5432
+	 * Given an array where keys are column (or column alias) names and values,
5433
+	 * returns an array of their corresponding field names and database values
5434
+	 *
5435
+	 * @param array $cols_n_values
5436
+	 * @return array
5437
+	 */
5438
+	public function deduce_fields_n_values_from_cols_n_values($cols_n_values)
5439
+	{
5440
+		return $this->_deduce_fields_n_values_from_cols_n_values($cols_n_values);
5441
+	}
5442
+
5443
+
5444
+
5445
+	/**
5446
+	 * _deduce_fields_n_values_from_cols_n_values
5447
+	 * Given an array where keys are column (or column alias) names and values,
5448
+	 * returns an array of their corresponding field names and database values
5449
+	 *
5450
+	 * @param string $cols_n_values
5451
+	 * @return array
5452
+	 */
5453
+	protected function _deduce_fields_n_values_from_cols_n_values($cols_n_values)
5454
+	{
5455
+		$this_model_fields_n_values = array();
5456
+		foreach ($this->get_tables() as $table_alias => $table_obj) {
5457
+			$table_pk_value = $this->_get_column_value_with_table_alias_or_not(
5458
+				$cols_n_values,
5459
+				$table_obj->get_fully_qualified_pk_column(),
5460
+				$table_obj->get_pk_column()
5461
+			);
5462
+			// there is a primary key on this table and its not set. Use defaults for all its columns
5463
+			if ($table_pk_value === null && $table_obj->get_pk_column()) {
5464
+				foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5465
+					if (! $field_obj->is_db_only_field()) {
5466
+						// prepare field as if its coming from db
5467
+						$prepared_value = $field_obj->prepare_for_set($field_obj->get_default_value());
5468
+						$this_model_fields_n_values[ $field_name ] = $field_obj->prepare_for_use_in_db($prepared_value);
5469
+					}
5470
+				}
5471
+			} else {
5472
+				// the table's rows existed. Use their values
5473
+				foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5474
+					if (! $field_obj->is_db_only_field()) {
5475
+						$this_model_fields_n_values[ $field_name ] = $this->_get_column_value_with_table_alias_or_not(
5476
+							$cols_n_values,
5477
+							$field_obj->get_qualified_column(),
5478
+							$field_obj->get_table_column()
5479
+						);
5480
+					}
5481
+				}
5482
+			}
5483
+		}
5484
+		return $this_model_fields_n_values;
5485
+	}
5486
+
5487
+
5488
+	/**
5489
+	 * @param $cols_n_values
5490
+	 * @param $qualified_column
5491
+	 * @param $regular_column
5492
+	 * @return null
5493
+	 * @throws EE_Error
5494
+	 * @throws ReflectionException
5495
+	 */
5496
+	protected function _get_column_value_with_table_alias_or_not($cols_n_values, $qualified_column, $regular_column)
5497
+	{
5498
+		$value = null;
5499
+		// ask the field what it think it's table_name.column_name should be, and call it the "qualified column"
5500
+		// does the field on the model relate to this column retrieved from the db?
5501
+		// or is it a db-only field? (not relating to the model)
5502
+		if (isset($cols_n_values[ $qualified_column ])) {
5503
+			$value = $cols_n_values[ $qualified_column ];
5504
+		} elseif (isset($cols_n_values[ $regular_column ])) {
5505
+			$value = $cols_n_values[ $regular_column ];
5506
+		} elseif (! empty($this->foreign_key_aliases)) {
5507
+			// no PK?  ok check if there is a foreign key alias set for this table
5508
+			// then check if that alias exists in the incoming data
5509
+			// AND that the actual PK the $FK_alias represents matches the $qualified_column (full PK)
5510
+			foreach ($this->foreign_key_aliases as $FK_alias => $PK_column) {
5511
+				if ($PK_column === $qualified_column && isset($cols_n_values[ $FK_alias ])) {
5512
+					$value = $cols_n_values[ $FK_alias ];
5513
+					[$pk_class] = explode('.', $PK_column);
5514
+					$pk_model_name = "EEM_{$pk_class}";
5515
+					/** @var EEM_Base $pk_model */
5516
+					$pk_model = EE_Registry::instance()->load_model($pk_model_name);
5517
+					if ($pk_model instanceof EEM_Base) {
5518
+						// make sure object is pulled from db and added to entity map
5519
+						$pk_model->get_one_by_ID($value);
5520
+					}
5521
+					break;
5522
+				}
5523
+			}
5524
+		}
5525
+		return $value;
5526
+	}
5527
+
5528
+
5529
+	/**
5530
+	 * refresh_entity_map_from_db
5531
+	 * Makes sure the model object in the entity map at $id assumes the values
5532
+	 * of the database (opposite of EE_base_Class::save())
5533
+	 *
5534
+	 * @param int|string $id
5535
+	 * @return EE_Base_Class|EE_Soft_Delete_Base_Class|mixed|null
5536
+	 * @throws EE_Error
5537
+	 * @throws ReflectionException
5538
+	 */
5539
+	public function refresh_entity_map_from_db($id)
5540
+	{
5541
+		$obj_in_map = $this->get_from_entity_map($id);
5542
+		if ($obj_in_map) {
5543
+			$wpdb_results = $this->_get_all_wpdb_results(
5544
+				array(array($this->get_primary_key_field()->get_name() => $id), 'limit' => 1)
5545
+			);
5546
+			if ($wpdb_results && is_array($wpdb_results)) {
5547
+				$one_row = reset($wpdb_results);
5548
+				foreach ($this->_deduce_fields_n_values_from_cols_n_values($one_row) as $field_name => $db_value) {
5549
+					$obj_in_map->set_from_db($field_name, $db_value);
5550
+				}
5551
+				// clear the cache of related model objects
5552
+				foreach ($this->relation_settings() as $relation_name => $relation_obj) {
5553
+					$obj_in_map->clear_cache($relation_name, null, true);
5554
+				}
5555
+			}
5556
+			$this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ] = $obj_in_map;
5557
+			return $obj_in_map;
5558
+		}
5559
+		return $this->get_one_by_ID($id);
5560
+	}
5561
+
5562
+
5563
+
5564
+	/**
5565
+	 * refresh_entity_map_with
5566
+	 * Leaves the entry in the entity map alone, but updates it to match the provided
5567
+	 * $replacing_model_obj (which we assume to be its equivalent but somehow NOT in the entity map).
5568
+	 * This is useful if you have a model object you want to make authoritative over what's in the entity map currently.
5569
+	 * Note: The old $replacing_model_obj should now be destroyed as it's now un-authoritative
5570
+	 *
5571
+	 * @param int|string    $id
5572
+	 * @param EE_Base_Class $replacing_model_obj
5573
+	 * @return \EE_Base_Class
5574
+	 * @throws EE_Error
5575
+	 */
5576
+	public function refresh_entity_map_with($id, $replacing_model_obj)
5577
+	{
5578
+		$obj_in_map = $this->get_from_entity_map($id);
5579
+		if ($obj_in_map) {
5580
+			if ($replacing_model_obj instanceof EE_Base_Class) {
5581
+				foreach ($replacing_model_obj->model_field_array() as $field_name => $value) {
5582
+					$obj_in_map->set($field_name, $value);
5583
+				}
5584
+				// make the model object in the entity map's cache match the $replacing_model_obj
5585
+				foreach ($this->relation_settings() as $relation_name => $relation_obj) {
5586
+					$obj_in_map->clear_cache($relation_name, null, true);
5587
+					foreach ($replacing_model_obj->get_all_from_cache($relation_name) as $cache_id => $cached_obj) {
5588
+						$obj_in_map->cache($relation_name, $cached_obj, $cache_id);
5589
+					}
5590
+				}
5591
+			}
5592
+			return $obj_in_map;
5593
+		}
5594
+		$this->add_to_entity_map($replacing_model_obj);
5595
+		return $replacing_model_obj;
5596
+	}
5597
+
5598
+
5599
+
5600
+	/**
5601
+	 * Gets the EE class that corresponds to this model. Eg, for EEM_Answer that
5602
+	 * would be EE_Answer.To import that class, you'd just add ".class.php" to the name, like so
5603
+	 * require_once($this->_getClassName().".class.php");
5604
+	 *
5605
+	 * @return string
5606
+	 */
5607
+	private function _get_class_name()
5608
+	{
5609
+		return "EE_" . $this->get_this_model_name();
5610
+	}
5611
+
5612
+
5613
+
5614
+	/**
5615
+	 * Get the name of the items this model represents, for the quantity specified. Eg,
5616
+	 * if $quantity==1, on EEM_Event, it would 'Event' (internationalized), otherwise
5617
+	 * it would be 'Events'.
5618
+	 *
5619
+	 * @param int|float|null $quantity
5620
+	 * @return string
5621
+	 */
5622
+	public function item_name($quantity = 1): string
5623
+	{
5624
+		$quantity = floor($quantity);
5625
+		return apply_filters(
5626
+			'FHEE__EEM_Base__item_name__plural_or_singular',
5627
+			$quantity > 1 ? $this->plural_item : $this->singular_item,
5628
+			$quantity,
5629
+			$this->plural_item,
5630
+			$this->singular_item
5631
+		);
5632
+	}
5633
+
5634
+
5635
+
5636
+	/**
5637
+	 * Very handy general function to allow for plugins to extend any child of EE_TempBase.
5638
+	 * If a method is called on a child of EE_TempBase that doesn't exist, this function is called
5639
+	 * (http://www.garfieldtech.com/blog/php-magic-call) and passed the method's name and arguments. Instead of
5640
+	 * requiring a plugin to extend the EE_TempBase (which works fine is there's only 1 plugin, but when will that
5641
+	 * happen?) they can add a hook onto 'filters_hook_espresso__{className}__{methodName}' (eg,
5642
+	 * filters_hook_espresso__EE_Answer__my_great_function) and accepts 2 arguments: the object on which the function
5643
+	 * was called, and an array of the original arguments passed to the function. Whatever their callback function
5644
+	 * returns will be returned by this function. Example: in functions.php (or in a plugin):
5645
+	 * add_filter('FHEE__EE_Answer__my_callback','my_callback',10,3); function
5646
+	 * my_callback($previousReturnValue,EE_TempBase $object,$argsArray){
5647
+	 * $returnString= "you called my_callback! and passed args:".implode(",",$argsArray);
5648
+	 *        return $previousReturnValue.$returnString;
5649
+	 * }
5650
+	 * require('EEM_Answer.model.php');
5651
+	 * $answer=EEM_Answer::instance();
5652
+	 * echo $answer->my_callback('monkeys',100);
5653
+	 * //will output "you called my_callback! and passed args:monkeys,100"
5654
+	 *
5655
+	 * @param string $methodName name of method which was called on a child of EE_TempBase, but which
5656
+	 * @param array  $args       array of original arguments passed to the function
5657
+	 * @throws EE_Error
5658
+	 * @return mixed whatever the plugin which calls add_filter decides
5659
+	 */
5660
+	public function __call($methodName, $args)
5661
+	{
5662
+		$className = get_class($this);
5663
+		$tagName = "FHEE__{$className}__{$methodName}";
5664
+		if (! has_filter($tagName)) {
5665
+			throw new EE_Error(
5666
+				sprintf(
5667
+					esc_html__(
5668
+						'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 );',
5669
+						'event_espresso'
5670
+					),
5671
+					$methodName,
5672
+					$className,
5673
+					$tagName,
5674
+					'<br />'
5675
+				)
5676
+			);
5677
+		}
5678
+		return apply_filters($tagName, null, $this, $args);
5679
+	}
5680
+
5681
+
5682
+
5683
+	/**
5684
+	 * Ensures $base_class_obj_or_id is of the EE_Base_Class child that corresponds ot this model.
5685
+	 * If not, assumes its an ID, and uses $this->get_one_by_ID() to get the EE_Base_Class.
5686
+	 *
5687
+	 * @param EE_Base_Class|string|int $base_class_obj_or_id either:
5688
+	 *                                                       the EE_Base_Class object that corresponds to this Model,
5689
+	 *                                                       the object's class name
5690
+	 *                                                       or object's ID
5691
+	 * @param boolean                  $ensure_is_in_db      if set, we will also verify this model object
5692
+	 *                                                       exists in the database. If it does not, we add it
5693
+	 * @throws EE_Error
5694
+	 * @return EE_Base_Class
5695
+	 */
5696
+	public function ensure_is_obj($base_class_obj_or_id, $ensure_is_in_db = false)
5697
+	{
5698
+		$className = $this->_get_class_name();
5699
+		if ($base_class_obj_or_id instanceof $className) {
5700
+			$model_object = $base_class_obj_or_id;
5701
+		} else {
5702
+			$primary_key_field = $this->get_primary_key_field();
5703
+			if (
5704
+				$primary_key_field instanceof EE_Primary_Key_Int_Field
5705
+				&& (
5706
+					is_int($base_class_obj_or_id)
5707
+					|| is_string($base_class_obj_or_id)
5708
+				)
5709
+			) {
5710
+				// assume it's an ID.
5711
+				// either a proper integer or a string representing an integer (eg "101" instead of 101)
5712
+				$model_object = $this->get_one_by_ID($base_class_obj_or_id);
5713
+			} elseif (
5714
+				$primary_key_field instanceof EE_Primary_Key_String_Field
5715
+				&& is_string($base_class_obj_or_id)
5716
+			) {
5717
+				// assume its a string representation of the object
5718
+				$model_object = $this->get_one_by_ID($base_class_obj_or_id);
5719
+			} else {
5720
+				throw new EE_Error(
5721
+					sprintf(
5722
+						esc_html__(
5723
+							"'%s' is neither an object of type %s, nor an ID! Its full value is '%s'",
5724
+							'event_espresso'
5725
+						),
5726
+						$base_class_obj_or_id,
5727
+						$this->_get_class_name(),
5728
+						print_r($base_class_obj_or_id, true)
5729
+					)
5730
+				);
5731
+			}
5732
+		}
5733
+		if ($ensure_is_in_db && $model_object->ID() !== null) {
5734
+			$model_object->save();
5735
+		}
5736
+		return $model_object;
5737
+	}
5738
+
5739
+
5740
+
5741
+	/**
5742
+	 * Similar to ensure_is_obj(), this method makes sure $base_class_obj_or_id
5743
+	 * is a value of the this model's primary key. If it's an EE_Base_Class child,
5744
+	 * returns it ID.
5745
+	 *
5746
+	 * @param EE_Base_Class|int|string $base_class_obj_or_id
5747
+	 * @return int|string depending on the type of this model object's ID
5748
+	 * @throws EE_Error
5749
+	 */
5750
+	public function ensure_is_ID($base_class_obj_or_id)
5751
+	{
5752
+		$className = $this->_get_class_name();
5753
+		if ($base_class_obj_or_id instanceof $className) {
5754
+			/** @var $base_class_obj_or_id EE_Base_Class */
5755
+			$id = $base_class_obj_or_id->ID();
5756
+		} elseif (is_int($base_class_obj_or_id)) {
5757
+			// assume it's an ID
5758
+			$id = $base_class_obj_or_id;
5759
+		} elseif (is_string($base_class_obj_or_id)) {
5760
+			// assume its a string representation of the object
5761
+			$id = $base_class_obj_or_id;
5762
+		} else {
5763
+			throw new EE_Error(sprintf(
5764
+				esc_html__(
5765
+					"'%s' is neither an object of type %s, nor an ID! Its full value is '%s'",
5766
+					'event_espresso'
5767
+				),
5768
+				$base_class_obj_or_id,
5769
+				$this->_get_class_name(),
5770
+				print_r($base_class_obj_or_id, true)
5771
+			));
5772
+		}
5773
+		return $id;
5774
+	}
5775
+
5776
+
5777
+
5778
+	/**
5779
+	 * Sets whether the values passed to the model (eg, values in WHERE, values in INSERT, UPDATE, etc)
5780
+	 * have already been ran through the appropriate model field's prepare_for_use_in_db method. IE, they have
5781
+	 * been sanitized and converted into the appropriate domain.
5782
+	 * Usually the only place you'll want to change the default (which is to assume values have NOT been sanitized by
5783
+	 * the model object/model field) is when making a method call from WITHIN a model object, which has direct access
5784
+	 * to its sanitized values. Note: after changing this setting, you should set it back to its previous value (using
5785
+	 * get_assumption_concerning_values_already_prepared_by_model_object()) eg.
5786
+	 * $EVT = EEM_Event::instance(); $old_setting =
5787
+	 * $EVT->get_assumption_concerning_values_already_prepared_by_model_object();
5788
+	 * $EVT->assume_values_already_prepared_by_model_object(true);
5789
+	 * $EVT->update(array('foo'=>'bar'),array(array('foo'=>'monkey')));
5790
+	 * $EVT->assume_values_already_prepared_by_model_object($old_setting);
5791
+	 *
5792
+	 * @param int $values_already_prepared like one of the constants on EEM_Base
5793
+	 * @return void
5794
+	 */
5795
+	public function assume_values_already_prepared_by_model_object(
5796
+		$values_already_prepared = self::not_prepared_by_model_object
5797
+	) {
5798
+		$this->_values_already_prepared_by_model_object = $values_already_prepared;
5799
+	}
5800
+
5801
+
5802
+
5803
+	/**
5804
+	 * Read comments for assume_values_already_prepared_by_model_object()
5805
+	 *
5806
+	 * @return int
5807
+	 */
5808
+	public function get_assumption_concerning_values_already_prepared_by_model_object()
5809
+	{
5810
+		return $this->_values_already_prepared_by_model_object;
5811
+	}
5812
+
5813
+
5814
+
5815
+	/**
5816
+	 * Gets all the indexes on this model
5817
+	 *
5818
+	 * @return EE_Index[]
5819
+	 */
5820
+	public function indexes()
5821
+	{
5822
+		return $this->_indexes;
5823
+	}
5824
+
5825
+
5826
+
5827
+	/**
5828
+	 * Gets all the Unique Indexes on this model
5829
+	 *
5830
+	 * @return EE_Unique_Index[]
5831
+	 */
5832
+	public function unique_indexes()
5833
+	{
5834
+		$unique_indexes = array();
5835
+		foreach ($this->_indexes as $name => $index) {
5836
+			if ($index instanceof EE_Unique_Index) {
5837
+				$unique_indexes [ $name ] = $index;
5838
+			}
5839
+		}
5840
+		return $unique_indexes;
5841
+	}
5842
+
5843
+
5844
+
5845
+	/**
5846
+	 * Gets all the fields which, when combined, make the primary key.
5847
+	 * This is usually just an array with 1 element (the primary key), but in cases
5848
+	 * where there is no primary key, it's a combination of fields as defined
5849
+	 * on a primary index
5850
+	 *
5851
+	 * @return EE_Model_Field_Base[] indexed by the field's name
5852
+	 * @throws EE_Error
5853
+	 */
5854
+	public function get_combined_primary_key_fields()
5855
+	{
5856
+		foreach ($this->indexes() as $index) {
5857
+			if ($index instanceof EE_Primary_Key_Index) {
5858
+				return $index->fields();
5859
+			}
5860
+		}
5861
+		return array($this->primary_key_name() => $this->get_primary_key_field());
5862
+	}
5863
+
5864
+
5865
+
5866
+	/**
5867
+	 * Used to build a primary key string (when the model has no primary key),
5868
+	 * which can be used a unique string to identify this model object.
5869
+	 *
5870
+	 * @param array $fields_n_values keys are field names, values are their values.
5871
+	 *                               Note: if you have results from `EEM_Base::get_all_wpdb_results()`, you need to
5872
+	 *                               run it through `EEM_Base::deduce_fields_n_values_from_cols_n_values()`
5873
+	 *                               before passing it to this function (that will convert it from columns-n-values
5874
+	 *                               to field-names-n-values).
5875
+	 * @return string
5876
+	 * @throws EE_Error
5877
+	 */
5878
+	public function get_index_primary_key_string($fields_n_values)
5879
+	{
5880
+		$cols_n_values_for_primary_key_index = array_intersect_key(
5881
+			$fields_n_values,
5882
+			$this->get_combined_primary_key_fields()
5883
+		);
5884
+		return http_build_query($cols_n_values_for_primary_key_index);
5885
+	}
5886
+
5887
+
5888
+
5889
+	/**
5890
+	 * Gets the field values from the primary key string
5891
+	 *
5892
+	 * @see EEM_Base::get_combined_primary_key_fields() and EEM_Base::get_index_primary_key_string()
5893
+	 * @param string $index_primary_key_string
5894
+	 * @return null|array
5895
+	 * @throws EE_Error
5896
+	 */
5897
+	public function parse_index_primary_key_string($index_primary_key_string)
5898
+	{
5899
+		$key_fields = $this->get_combined_primary_key_fields();
5900
+		// check all of them are in the $id
5901
+		$key_vals_in_combined_pk = array();
5902
+		parse_str($index_primary_key_string, $key_vals_in_combined_pk);
5903
+		foreach ($key_fields as $key_field_name => $field_obj) {
5904
+			if (! isset($key_vals_in_combined_pk[ $key_field_name ])) {
5905
+				return null;
5906
+			}
5907
+		}
5908
+		return $key_vals_in_combined_pk;
5909
+	}
5910
+
5911
+
5912
+
5913
+	/**
5914
+	 * verifies that an array of key-value pairs for model fields has a key
5915
+	 * for each field comprising the primary key index
5916
+	 *
5917
+	 * @param array $key_vals
5918
+	 * @return boolean
5919
+	 * @throws EE_Error
5920
+	 */
5921
+	public function has_all_combined_primary_key_fields($key_vals)
5922
+	{
5923
+		$keys_it_should_have = array_keys($this->get_combined_primary_key_fields());
5924
+		foreach ($keys_it_should_have as $key) {
5925
+			if (! isset($key_vals[ $key ])) {
5926
+				return false;
5927
+			}
5928
+		}
5929
+		return true;
5930
+	}
5931
+
5932
+
5933
+
5934
+	/**
5935
+	 * Finds all model objects in the DB that appear to be a copy of $model_object_or_attributes_array.
5936
+	 * We consider something to be a copy if all the attributes match (except the ID, of course).
5937
+	 *
5938
+	 * @param array|EE_Base_Class $model_object_or_attributes_array If its an array, it's field-value pairs
5939
+	 * @param array               $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
5940
+	 * @throws EE_Error
5941
+	 * @return \EE_Base_Class[] Array keys are object IDs (if there is a primary key on the model. if not, numerically
5942
+	 *                                                              indexed)
5943
+	 */
5944
+	public function get_all_copies($model_object_or_attributes_array, $query_params = array())
5945
+	{
5946
+		if ($model_object_or_attributes_array instanceof EE_Base_Class) {
5947
+			$attributes_array = $model_object_or_attributes_array->model_field_array();
5948
+		} elseif (is_array($model_object_or_attributes_array)) {
5949
+			$attributes_array = $model_object_or_attributes_array;
5950
+		} else {
5951
+			throw new EE_Error(sprintf(esc_html__(
5952
+				"get_all_copies should be provided with either a model object or an array of field-value-pairs, but was given %s",
5953
+				"event_espresso"
5954
+			), $model_object_or_attributes_array));
5955
+		}
5956
+		// even copies obviously won't have the same ID, so remove the primary key
5957
+		// from the WHERE conditions for finding copies (if there is a primary key, of course)
5958
+		if ($this->has_primary_key_field() && isset($attributes_array[ $this->primary_key_name() ])) {
5959
+			unset($attributes_array[ $this->primary_key_name() ]);
5960
+		}
5961
+		if (isset($query_params[0])) {
5962
+			$query_params[0] = array_merge($attributes_array, $query_params);
5963
+		} else {
5964
+			$query_params[0] = $attributes_array;
5965
+		}
5966
+		return $this->get_all($query_params);
5967
+	}
5968
+
5969
+
5970
+
5971
+	/**
5972
+	 * Gets the first copy we find. See get_all_copies for more details
5973
+	 *
5974
+	 * @param       mixed EE_Base_Class | array        $model_object_or_attributes_array
5975
+	 * @param array $query_params
5976
+	 * @return EE_Base_Class
5977
+	 * @throws EE_Error
5978
+	 */
5979
+	public function get_one_copy($model_object_or_attributes_array, $query_params = array())
5980
+	{
5981
+		if (! is_array($query_params)) {
5982
+			EE_Error::doing_it_wrong(
5983
+				'EEM_Base::get_one_copy',
5984
+				sprintf(
5985
+					esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
5986
+					gettype($query_params)
5987
+				),
5988
+				'4.6.0'
5989
+			);
5990
+			$query_params = array();
5991
+		}
5992
+		$query_params['limit'] = 1;
5993
+		$copies = $this->get_all_copies($model_object_or_attributes_array, $query_params);
5994
+		if (is_array($copies)) {
5995
+			return array_shift($copies);
5996
+		}
5997
+		return null;
5998
+	}
5999
+
6000
+
6001
+
6002
+	/**
6003
+	 * Updates the item with the specified id. Ignores default query parameters because
6004
+	 * we have specified the ID, and its assumed we KNOW what we're doing
6005
+	 *
6006
+	 * @param array      $fields_n_values keys are field names, values are their new values
6007
+	 * @param int|string $id              the value of the primary key to update
6008
+	 * @return int number of rows updated
6009
+	 * @throws EE_Error
6010
+	 */
6011
+	public function update_by_ID($fields_n_values, $id)
6012
+	{
6013
+		$query_params = array(
6014
+			0                          => array($this->get_primary_key_field()->get_name() => $id),
6015
+			'default_where_conditions' => EEM_Base::default_where_conditions_others_only,
6016
+		);
6017
+		return $this->update($fields_n_values, $query_params);
6018
+	}
6019
+
6020
+
6021
+
6022
+	/**
6023
+	 * Changes an operator which was supplied to the models into one usable in SQL
6024
+	 *
6025
+	 * @param string $operator_supplied
6026
+	 * @return string an operator which can be used in SQL
6027
+	 * @throws EE_Error
6028
+	 */
6029
+	private function _prepare_operator_for_sql($operator_supplied)
6030
+	{
6031
+		$sql_operator = isset($this->_valid_operators[ $operator_supplied ]) ? $this->_valid_operators[ $operator_supplied ]
6032
+			: null;
6033
+		if ($sql_operator) {
6034
+			return $sql_operator;
6035
+		}
6036
+		throw new EE_Error(
6037
+			sprintf(
6038
+				esc_html__(
6039
+					"The operator '%s' is not in the list of valid operators: %s",
6040
+					"event_espresso"
6041
+				),
6042
+				$operator_supplied,
6043
+				implode(",", array_keys($this->_valid_operators))
6044
+			)
6045
+		);
6046
+	}
6047
+
6048
+
6049
+
6050
+	/**
6051
+	 * Gets the valid operators
6052
+	 * @return array keys are accepted strings, values are the SQL they are converted to
6053
+	 */
6054
+	public function valid_operators()
6055
+	{
6056
+		return $this->_valid_operators;
6057
+	}
6058
+
6059
+
6060
+
6061
+	/**
6062
+	 * Gets the between-style operators (take 2 arguments).
6063
+	 * @return array keys are accepted strings, values are the SQL they are converted to
6064
+	 */
6065
+	public function valid_between_style_operators()
6066
+	{
6067
+		return array_intersect(
6068
+			$this->valid_operators(),
6069
+			$this->_between_style_operators
6070
+		);
6071
+	}
6072
+
6073
+	/**
6074
+	 * Gets the "like"-style operators (take a single argument, but it may contain wildcards)
6075
+	 * @return array keys are accepted strings, values are the SQL they are converted to
6076
+	 */
6077
+	public function valid_like_style_operators()
6078
+	{
6079
+		return array_intersect(
6080
+			$this->valid_operators(),
6081
+			$this->_like_style_operators
6082
+		);
6083
+	}
6084
+
6085
+	/**
6086
+	 * Gets the "in"-style operators
6087
+	 * @return array keys are accepted strings, values are the SQL they are converted to
6088
+	 */
6089
+	public function valid_in_style_operators()
6090
+	{
6091
+		return array_intersect(
6092
+			$this->valid_operators(),
6093
+			$this->_in_style_operators
6094
+		);
6095
+	}
6096
+
6097
+	/**
6098
+	 * Gets the "null"-style operators (accept no arguments)
6099
+	 * @return array keys are accepted strings, values are the SQL they are converted to
6100
+	 */
6101
+	public function valid_null_style_operators()
6102
+	{
6103
+		return array_intersect(
6104
+			$this->valid_operators(),
6105
+			$this->_null_style_operators
6106
+		);
6107
+	}
6108
+
6109
+	/**
6110
+	 * Gets an array where keys are the primary keys and values are their 'names'
6111
+	 * (as determined by the model object's name() function, which is often overridden)
6112
+	 *
6113
+	 * @param array $query_params like get_all's
6114
+	 * @return string[]
6115
+	 * @throws EE_Error
6116
+	 */
6117
+	public function get_all_names($query_params = array())
6118
+	{
6119
+		$objs = $this->get_all($query_params);
6120
+		$names = array();
6121
+		foreach ($objs as $obj) {
6122
+			$names[ $obj->ID() ] = $obj->name();
6123
+		}
6124
+		return $names;
6125
+	}
6126
+
6127
+
6128
+
6129
+	/**
6130
+	 * Gets an array of primary keys from the model objects. If you acquired the model objects
6131
+	 * using EEM_Base::get_all() you don't need to call this (and probably shouldn't because
6132
+	 * this is duplicated effort and reduces efficiency) you would be better to use
6133
+	 * array_keys() on $model_objects.
6134
+	 *
6135
+	 * @param \EE_Base_Class[] $model_objects
6136
+	 * @param boolean          $filter_out_empty_ids if a model object has an ID of '' or 0, don't bother including it
6137
+	 *                                               in the returned array
6138
+	 * @return array
6139
+	 * @throws EE_Error
6140
+	 */
6141
+	public function get_IDs($model_objects, $filter_out_empty_ids = false)
6142
+	{
6143
+		if (! $this->has_primary_key_field()) {
6144
+			if (WP_DEBUG) {
6145
+				EE_Error::add_error(
6146
+					esc_html__('Trying to get IDs from a model than has no primary key', 'event_espresso'),
6147
+					__FILE__,
6148
+					__FUNCTION__,
6149
+					__LINE__
6150
+				);
6151
+			}
6152
+		}
6153
+		$IDs = array();
6154
+		foreach ($model_objects as $model_object) {
6155
+			$id = $model_object->ID();
6156
+			if (! $id) {
6157
+				if ($filter_out_empty_ids) {
6158
+					continue;
6159
+				}
6160
+				if (WP_DEBUG) {
6161
+					EE_Error::add_error(
6162
+						esc_html__(
6163
+							'Called %1$s on a model object that has no ID and so probably hasn\'t been saved to the database',
6164
+							'event_espresso'
6165
+						),
6166
+						__FILE__,
6167
+						__FUNCTION__,
6168
+						__LINE__
6169
+					);
6170
+				}
6171
+			}
6172
+			$IDs[] = $id;
6173
+		}
6174
+		return $IDs;
6175
+	}
6176
+
6177
+
6178
+
6179
+	/**
6180
+	 * Returns the string used in capabilities relating to this model. If there
6181
+	 * are no capabilities that relate to this model returns false
6182
+	 *
6183
+	 * @return string|false
6184
+	 */
6185
+	public function cap_slug()
6186
+	{
6187
+		return apply_filters('FHEE__EEM_Base__cap_slug', $this->_caps_slug, $this);
6188
+	}
6189
+
6190
+
6191
+
6192
+	/**
6193
+	 * Returns the capability-restrictions array (@see EEM_Base::_cap_restrictions).
6194
+	 * If $context is provided (which should be set to one of EEM_Base::valid_cap_contexts())
6195
+	 * only returns the cap restrictions array in that context (ie, the array
6196
+	 * at that key)
6197
+	 *
6198
+	 * @param string $context
6199
+	 * @return EE_Default_Where_Conditions[] indexed by associated capability
6200
+	 * @throws EE_Error
6201
+	 */
6202
+	public function cap_restrictions($context = EEM_Base::caps_read)
6203
+	{
6204
+		EEM_Base::verify_is_valid_cap_context($context);
6205
+		// check if we ought to run the restriction generator first
6206
+		if (
6207
+			isset($this->_cap_restriction_generators[ $context ])
6208
+			&& $this->_cap_restriction_generators[ $context ] instanceof EE_Restriction_Generator_Base
6209
+			&& ! $this->_cap_restriction_generators[ $context ]->has_generated_cap_restrictions()
6210
+		) {
6211
+			$this->_cap_restrictions[ $context ] = array_merge(
6212
+				$this->_cap_restrictions[ $context ],
6213
+				$this->_cap_restriction_generators[ $context ]->generate_restrictions()
6214
+			);
6215
+		}
6216
+		// and make sure we've finalized the construction of each restriction
6217
+		foreach ($this->_cap_restrictions[ $context ] as $where_conditions_obj) {
6218
+			if ($where_conditions_obj instanceof EE_Default_Where_Conditions) {
6219
+				$where_conditions_obj->_finalize_construct($this);
6220
+			}
6221
+		}
6222
+		return $this->_cap_restrictions[ $context ];
6223
+	}
6224
+
6225
+
6226
+
6227
+	/**
6228
+	 * Indicating whether or not this model thinks its a wp core model
6229
+	 *
6230
+	 * @return boolean
6231
+	 */
6232
+	public function is_wp_core_model()
6233
+	{
6234
+		return $this->_wp_core_model;
6235
+	}
6236
+
6237
+
6238
+
6239
+	/**
6240
+	 * Gets all the caps that are missing which impose a restriction on
6241
+	 * queries made in this context
6242
+	 *
6243
+	 * @param string $context one of EEM_Base::caps_ constants
6244
+	 * @return EE_Default_Where_Conditions[] indexed by capability name
6245
+	 * @throws EE_Error
6246
+	 */
6247
+	public function caps_missing($context = EEM_Base::caps_read)
6248
+	{
6249
+		$missing_caps = array();
6250
+		$cap_restrictions = $this->cap_restrictions($context);
6251
+		foreach ($cap_restrictions as $cap => $restriction_if_no_cap) {
6252
+			if (
6253
+				! EE_Capabilities::instance()
6254
+								 ->current_user_can($cap, $this->get_this_model_name() . '_model_applying_caps')
6255
+			) {
6256
+				$missing_caps[ $cap ] = $restriction_if_no_cap;
6257
+			}
6258
+		}
6259
+		return $missing_caps;
6260
+	}
6261
+
6262
+
6263
+
6264
+	/**
6265
+	 * Gets the mapping from capability contexts to action strings used in capability names
6266
+	 *
6267
+	 * @return array keys are one of EEM_Base::valid_cap_contexts(), and values are usually
6268
+	 * one of 'read', 'edit', or 'delete'
6269
+	 */
6270
+	public function cap_contexts_to_cap_action_map()
6271
+	{
6272
+		return apply_filters(
6273
+			'FHEE__EEM_Base__cap_contexts_to_cap_action_map',
6274
+			$this->_cap_contexts_to_cap_action_map,
6275
+			$this
6276
+		);
6277
+	}
6278
+
6279
+
6280
+
6281
+	/**
6282
+	 * Gets the action string for the specified capability context
6283
+	 *
6284
+	 * @param string $context
6285
+	 * @return string one of EEM_Base::cap_contexts_to_cap_action_map() values
6286
+	 * @throws EE_Error
6287
+	 */
6288
+	public function cap_action_for_context($context)
6289
+	{
6290
+		$mapping = $this->cap_contexts_to_cap_action_map();
6291
+		if (isset($mapping[ $context ])) {
6292
+			return $mapping[ $context ];
6293
+		}
6294
+		if ($action = apply_filters('FHEE__EEM_Base__cap_action_for_context', null, $this, $mapping, $context)) {
6295
+			return $action;
6296
+		}
6297
+		throw new EE_Error(
6298
+			sprintf(
6299
+				esc_html__('Cannot find capability restrictions for context "%1$s", allowed values are:%2$s', 'event_espresso'),
6300
+				$context,
6301
+				implode(',', array_keys($this->cap_contexts_to_cap_action_map()))
6302
+			)
6303
+		);
6304
+	}
6305
+
6306
+
6307
+
6308
+	/**
6309
+	 * Returns all the capability contexts which are valid when querying models
6310
+	 *
6311
+	 * @return array
6312
+	 */
6313
+	public static function valid_cap_contexts()
6314
+	{
6315
+		return apply_filters('FHEE__EEM_Base__valid_cap_contexts', array(
6316
+			self::caps_read,
6317
+			self::caps_read_admin,
6318
+			self::caps_edit,
6319
+			self::caps_delete,
6320
+		));
6321
+	}
6322
+
6323
+
6324
+
6325
+	/**
6326
+	 * Returns all valid options for 'default_where_conditions'
6327
+	 *
6328
+	 * @return array
6329
+	 */
6330
+	public static function valid_default_where_conditions()
6331
+	{
6332
+		return array(
6333
+			EEM_Base::default_where_conditions_all,
6334
+			EEM_Base::default_where_conditions_this_only,
6335
+			EEM_Base::default_where_conditions_others_only,
6336
+			EEM_Base::default_where_conditions_minimum_all,
6337
+			EEM_Base::default_where_conditions_minimum_others,
6338
+			EEM_Base::default_where_conditions_none
6339
+		);
6340
+	}
6341
+
6342
+	// public static function default_where_conditions_full
6343
+	/**
6344
+	 * Verifies $context is one of EEM_Base::valid_cap_contexts(), if not it throws an exception
6345
+	 *
6346
+	 * @param string $context
6347
+	 * @return bool
6348
+	 * @throws EE_Error
6349
+	 */
6350
+	public static function verify_is_valid_cap_context($context)
6351
+	{
6352
+		$valid_cap_contexts = EEM_Base::valid_cap_contexts();
6353
+		if (in_array($context, $valid_cap_contexts)) {
6354
+			return true;
6355
+		}
6356
+		throw new EE_Error(
6357
+			sprintf(
6358
+				esc_html__(
6359
+					'Context "%1$s" passed into model "%2$s" is not a valid context. They are: %3$s',
6360
+					'event_espresso'
6361
+				),
6362
+				$context,
6363
+				'EEM_Base',
6364
+				implode(',', $valid_cap_contexts)
6365
+			)
6366
+		);
6367
+	}
6368
+
6369
+
6370
+
6371
+	/**
6372
+	 * Clears all the models field caches. This is only useful when a sub-class
6373
+	 * might have added a field or something and these caches might be invalidated
6374
+	 */
6375
+	protected function _invalidate_field_caches()
6376
+	{
6377
+		$this->_cache_foreign_key_to_fields = array();
6378
+		$this->_cached_fields = null;
6379
+		$this->_cached_fields_non_db_only = null;
6380
+	}
6381
+
6382
+
6383
+
6384
+	/**
6385
+	 * Gets the list of all the where query param keys that relate to logic instead of field names
6386
+	 * (eg "and", "or", "not").
6387
+	 *
6388
+	 * @return array
6389
+	 */
6390
+	public function logic_query_param_keys()
6391
+	{
6392
+		return $this->_logic_query_param_keys;
6393
+	}
6394
+
6395
+
6396
+
6397
+	/**
6398
+	 * Determines whether or not the where query param array key is for a logic query param.
6399
+	 * Eg 'OR', 'not*', and 'and*because-i-say-so' should all return true, whereas
6400
+	 * 'ATT_fname', 'EVT_name*not-you-or-me', and 'ORG_name' should return false
6401
+	 *
6402
+	 * @param $query_param_key
6403
+	 * @return bool
6404
+	 */
6405
+	public function is_logic_query_param_key($query_param_key)
6406
+	{
6407
+		foreach ($this->logic_query_param_keys() as $logic_query_param_key) {
6408
+			if (
6409
+				$query_param_key === $logic_query_param_key
6410
+				|| strpos($query_param_key, $logic_query_param_key . '*') === 0
6411
+			) {
6412
+				return true;
6413
+			}
6414
+		}
6415
+		return false;
6416
+	}
6417
+
6418
+	/**
6419
+	 * Returns true if this model has a password field on it (regardless of whether that password field has any content)
6420
+	 * @since 4.9.74.p
6421
+	 * @return boolean
6422
+	 */
6423
+	public function hasPassword()
6424
+	{
6425
+		// if we don't yet know if there's a password field, find out and remember it for next time.
6426
+		if ($this->has_password_field === null) {
6427
+			$password_field = $this->getPasswordField();
6428
+			$this->has_password_field = $password_field instanceof EE_Password_Field ? true : false;
6429
+		}
6430
+		return $this->has_password_field;
6431
+	}
6432
+
6433
+	/**
6434
+	 * Returns the password field on this model, if there is one
6435
+	 * @since 4.9.74.p
6436
+	 * @return EE_Password_Field|null
6437
+	 */
6438
+	public function getPasswordField()
6439
+	{
6440
+		// if we definetely already know there is a password field or not (because has_password_field is true or false)
6441
+		// there's no need to search for it. If we don't know yet, then find out
6442
+		if ($this->has_password_field === null && $this->password_field === null) {
6443
+			$this->password_field = $this->get_a_field_of_type('EE_Password_Field');
6444
+		}
6445
+		// don't bother setting has_password_field because that's hasPassword()'s job.
6446
+		return $this->password_field;
6447
+	}
6448
+
6449
+
6450
+	/**
6451
+	 * Returns the list of field (as EE_Model_Field_Bases) that are protected by the password
6452
+	 * @since 4.9.74.p
6453
+	 * @return EE_Model_Field_Base[]
6454
+	 * @throws EE_Error
6455
+	 */
6456
+	public function getPasswordProtectedFields()
6457
+	{
6458
+		$password_field = $this->getPasswordField();
6459
+		$fields = array();
6460
+		if ($password_field instanceof EE_Password_Field) {
6461
+			$field_names = $password_field->protectedFields();
6462
+			foreach ($field_names as $field_name) {
6463
+				$fields[ $field_name ] = $this->field_settings_for($field_name);
6464
+			}
6465
+		}
6466
+		return $fields;
6467
+	}
6468
+
6469
+
6470
+	/**
6471
+	 * Checks if the current user can perform the requested action on this model
6472
+	 * @since 4.9.74.p
6473
+	 * @param string $cap_to_check one of the array keys from _cap_contexts_to_cap_action_map
6474
+	 * @param EE_Base_Class|array $model_obj_or_fields_n_values
6475
+	 * @return bool
6476
+	 * @throws EE_Error
6477
+	 * @throws InvalidArgumentException
6478
+	 * @throws InvalidDataTypeException
6479
+	 * @throws InvalidInterfaceException
6480
+	 * @throws ReflectionException
6481
+	 * @throws UnexpectedEntityException
6482
+	 */
6483
+	public function currentUserCan($cap_to_check, $model_obj_or_fields_n_values)
6484
+	{
6485
+		if ($model_obj_or_fields_n_values instanceof EE_Base_Class) {
6486
+			$model_obj_or_fields_n_values = $model_obj_or_fields_n_values->model_field_array();
6487
+		}
6488
+		if (!is_array($model_obj_or_fields_n_values)) {
6489
+			throw new UnexpectedEntityException(
6490
+				$model_obj_or_fields_n_values,
6491
+				'EE_Base_Class',
6492
+				sprintf(
6493
+					esc_html__('%1$s must be passed an `EE_Base_Class or an array of fields names with their values. You passed in something different.', 'event_espresso'),
6494
+					__FUNCTION__
6495
+				)
6496
+			);
6497
+		}
6498
+		return $this->exists(
6499
+			$this->alter_query_params_to_restrict_by_ID(
6500
+				$this->get_index_primary_key_string($model_obj_or_fields_n_values),
6501
+				array(
6502
+					'default_where_conditions' => 'none',
6503
+					'caps'                     => $cap_to_check,
6504
+				)
6505
+			)
6506
+		);
6507
+	}
6508
+
6509
+	/**
6510
+	 * Returns the query param where conditions key to the password affecting this model.
6511
+	 * Eg on EEM_Event this would just be "password", on EEM_Datetime this would be "Event.password", etc.
6512
+	 * @since 4.9.74.p
6513
+	 * @return null|string
6514
+	 * @throws EE_Error
6515
+	 * @throws InvalidArgumentException
6516
+	 * @throws InvalidDataTypeException
6517
+	 * @throws InvalidInterfaceException
6518
+	 * @throws ModelConfigurationException
6519
+	 * @throws ReflectionException
6520
+	 */
6521
+	public function modelChainAndPassword()
6522
+	{
6523
+		if ($this->model_chain_to_password === null) {
6524
+			throw new ModelConfigurationException(
6525
+				$this,
6526
+				esc_html_x(
6527
+				// @codingStandardsIgnoreStart
6528
+					'Cannot exclude protected data because the model has not specified which model has the password.',
6529
+					// @codingStandardsIgnoreEnd
6530
+					'1: model name',
6531
+					'event_espresso'
6532
+				)
6533
+			);
6534
+		}
6535
+		if ($this->model_chain_to_password === '') {
6536
+			$model_with_password = $this;
6537
+		} else {
6538
+			if ($pos_of_period = strrpos($this->model_chain_to_password, '.')) {
6539
+				$last_model_in_chain = substr($this->model_chain_to_password, $pos_of_period + 1);
6540
+			} else {
6541
+				$last_model_in_chain = $this->model_chain_to_password;
6542
+			}
6543
+			$model_with_password = EE_Registry::instance()->load_model($last_model_in_chain);
6544
+		}
6545
+
6546
+		$password_field = $model_with_password->getPasswordField();
6547
+		if ($password_field instanceof EE_Password_Field) {
6548
+			$password_field_name = $password_field->get_name();
6549
+		} else {
6550
+			throw new ModelConfigurationException(
6551
+				$this,
6552
+				sprintf(
6553
+					esc_html_x(
6554
+						'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"',
6555
+						'1: model name, 2: special string',
6556
+						'event_espresso'
6557
+					),
6558
+					$model_with_password->get_this_model_name(),
6559
+					$this->model_chain_to_password
6560
+				)
6561
+			);
6562
+		}
6563
+		return ($this->model_chain_to_password ? $this->model_chain_to_password . '.' : '') . $password_field_name;
6564
+	}
6565
+
6566
+	/**
6567
+	 * Returns true if there is a password on a related model which restricts access to some of this model's rows,
6568
+	 * or if this model itself has a password affecting access to some of its other fields.
6569
+	 * @since 4.9.74.p
6570
+	 * @return boolean
6571
+	 */
6572
+	public function restrictedByRelatedModelPassword()
6573
+	{
6574
+		return $this->model_chain_to_password !== null;
6575
+	}
6576 6576
 }
Please login to merge, or discard this patch.
libraries/form_sections/strategies/layout/EE_Two_Column_Layout.strategy.php 2 patches
Indentation   +78 added lines, -78 removed lines patch added patch discarded remove patch
@@ -2,93 +2,93 @@
 block discarded – undo
2 2
 
3 3
 class EE_Two_Column_Layout extends EE_Form_Section_Layout_Base
4 4
 {
5
-    /**
6
-     * @param EE_Form_Section_Proper $form
7
-     */
8
-    public function _construct_finalize(EE_Form_Section_Proper $form)
9
-    {
10
-        parent::_construct_finalize($form);
11
-        $this->_form_section->set_html_class( 'ee-two-column-layout');
12
-    }
5
+	/**
6
+	 * @param EE_Form_Section_Proper $form
7
+	 */
8
+	public function _construct_finalize(EE_Form_Section_Proper $form)
9
+	{
10
+		parent::_construct_finalize($form);
11
+		$this->_form_section->set_html_class( 'ee-two-column-layout');
12
+	}
13 13
 
14 14
 
15
-    /**
16
-     * Should be used to start teh form section (Eg a table tag, or a div tag, etc.)
17
-     *
18
-     * @param array $additional_args
19
-     * @return string
20
-     * @throws EE_Error
21
-     */
22
-    public function layout_form_begin($additional_args = array())
23
-    {
24
-        return $this->display_form_wide_errors()
25
-        . EEH_HTML::table(
26
-            '',
27
-            $this->_form_section->html_id(),
28
-            $this->_form_section->html_class(),
29
-            $this->_form_section->html_style()
30
-        ) . EEH_HTML::tbody();
31
-    }
15
+	/**
16
+	 * Should be used to start teh form section (Eg a table tag, or a div tag, etc.)
17
+	 *
18
+	 * @param array $additional_args
19
+	 * @return string
20
+	 * @throws EE_Error
21
+	 */
22
+	public function layout_form_begin($additional_args = array())
23
+	{
24
+		return $this->display_form_wide_errors()
25
+		. EEH_HTML::table(
26
+			'',
27
+			$this->_form_section->html_id(),
28
+			$this->_form_section->html_class(),
29
+			$this->_form_section->html_style()
30
+		) . EEH_HTML::tbody();
31
+	}
32 32
 
33 33
 
34 34
 
35
-    /**
36
-     * Should be used to end the form section (eg a /table tag, or a /div tag, etc)
37
-     *
38
-     * @param array $additional_args
39
-     * @return string
40
-     */
41
-    public function layout_form_end($additional_args = array())
42
-    {
43
-        return EEH_HTML::tbodyx() . EEH_HTML::tablex($this->_form_section->html_id());
44
-    }
35
+	/**
36
+	 * Should be used to end the form section (eg a /table tag, or a /div tag, etc)
37
+	 *
38
+	 * @param array $additional_args
39
+	 * @return string
40
+	 */
41
+	public function layout_form_end($additional_args = array())
42
+	{
43
+		return EEH_HTML::tbodyx() . EEH_HTML::tablex($this->_form_section->html_id());
44
+	}
45 45
 
46 46
 
47 47
 
48
-    /**
49
-     * Lays out the row for the input, including label and errors
50
-     *
51
-     * @param EE_Form_Input_Base $input
52
-     * @return string
53
-     */
54
-    public function layout_input($input)
55
-    {
56
-        $html = '';
57
-        if ($input instanceof EE_Hidden_Input) {
58
-            $html .= $input->get_html_for_input();
59
-        } else {
60
-            $html_for_input = $input->get_html_for_input();
61
-            $html_for_input .= $input->get_html_for_errors() != ''
62
-                ? EEH_HTML::nl() . $input->get_html_for_errors()
63
-                : '';
64
-            $html_for_input .= $input->get_html_for_help() != '' ? EEH_HTML::nl() . $input->get_html_for_help() : '';
65
-            $html .= EEH_HTML::tr(
66
-                EEH_HTML::th($input->get_html_for_label()) .
67
-                EEH_HTML::td($html_for_input)
68
-            );
69
-        }
70
-        return $html;
71
-    }
48
+	/**
49
+	 * Lays out the row for the input, including label and errors
50
+	 *
51
+	 * @param EE_Form_Input_Base $input
52
+	 * @return string
53
+	 */
54
+	public function layout_input($input)
55
+	{
56
+		$html = '';
57
+		if ($input instanceof EE_Hidden_Input) {
58
+			$html .= $input->get_html_for_input();
59
+		} else {
60
+			$html_for_input = $input->get_html_for_input();
61
+			$html_for_input .= $input->get_html_for_errors() != ''
62
+				? EEH_HTML::nl() . $input->get_html_for_errors()
63
+				: '';
64
+			$html_for_input .= $input->get_html_for_help() != '' ? EEH_HTML::nl() . $input->get_html_for_help() : '';
65
+			$html .= EEH_HTML::tr(
66
+				EEH_HTML::th($input->get_html_for_label()) .
67
+				EEH_HTML::td($html_for_input)
68
+			);
69
+		}
70
+		return $html;
71
+	}
72 72
 
73 73
 
74 74
 
75
-    /**
76
-     * Lays out a row for the subsection. Please note that if you have a subsection which you don't want wrapped in
77
-     * a tr and td with a colspan=2, you should use a different layout strategy, like EE_No_Layout, EE_Template_Layout,
78
-     * or EE_Div_Per_Section_Layout, and create subsections using EE_Two_Column_Layout for everywhere you want the
79
-     * two-column layout, and then other sub-sections can be outside the EE_Two_Column_Layout table.
80
-     *
81
-     * @param EE_Form_Section_Proper $form_section
82
-     * @return string
83
-    */
84
-    public function layout_subsection($form_section)
85
-    {
86
-        if (
87
-            $form_section instanceof EE_Form_Section_Proper
88
-            || $form_section instanceof EE_Form_Section_HTML
89
-        ) {
90
-            return EEH_HTML::no_row($form_section->get_html());
91
-        }
92
-        return '';
93
-    }
75
+	/**
76
+	 * Lays out a row for the subsection. Please note that if you have a subsection which you don't want wrapped in
77
+	 * a tr and td with a colspan=2, you should use a different layout strategy, like EE_No_Layout, EE_Template_Layout,
78
+	 * or EE_Div_Per_Section_Layout, and create subsections using EE_Two_Column_Layout for everywhere you want the
79
+	 * two-column layout, and then other sub-sections can be outside the EE_Two_Column_Layout table.
80
+	 *
81
+	 * @param EE_Form_Section_Proper $form_section
82
+	 * @return string
83
+	 */
84
+	public function layout_subsection($form_section)
85
+	{
86
+		if (
87
+			$form_section instanceof EE_Form_Section_Proper
88
+			|| $form_section instanceof EE_Form_Section_HTML
89
+		) {
90
+			return EEH_HTML::no_row($form_section->get_html());
91
+		}
92
+		return '';
93
+	}
94 94
 }
Please login to merge, or discard this patch.
Spacing   +6 added lines, -6 removed lines patch added patch discarded remove patch
@@ -8,7 +8,7 @@  discard block
 block discarded – undo
8 8
     public function _construct_finalize(EE_Form_Section_Proper $form)
9 9
     {
10 10
         parent::_construct_finalize($form);
11
-        $this->_form_section->set_html_class( 'ee-two-column-layout');
11
+        $this->_form_section->set_html_class('ee-two-column-layout');
12 12
     }
13 13
 
14 14
 
@@ -27,7 +27,7 @@  discard block
 block discarded – undo
27 27
             $this->_form_section->html_id(),
28 28
             $this->_form_section->html_class(),
29 29
             $this->_form_section->html_style()
30
-        ) . EEH_HTML::tbody();
30
+        ).EEH_HTML::tbody();
31 31
     }
32 32
 
33 33
 
@@ -40,7 +40,7 @@  discard block
 block discarded – undo
40 40
      */
41 41
     public function layout_form_end($additional_args = array())
42 42
     {
43
-        return EEH_HTML::tbodyx() . EEH_HTML::tablex($this->_form_section->html_id());
43
+        return EEH_HTML::tbodyx().EEH_HTML::tablex($this->_form_section->html_id());
44 44
     }
45 45
 
46 46
 
@@ -59,11 +59,11 @@  discard block
 block discarded – undo
59 59
         } else {
60 60
             $html_for_input = $input->get_html_for_input();
61 61
             $html_for_input .= $input->get_html_for_errors() != ''
62
-                ? EEH_HTML::nl() . $input->get_html_for_errors()
62
+                ? EEH_HTML::nl().$input->get_html_for_errors()
63 63
                 : '';
64
-            $html_for_input .= $input->get_html_for_help() != '' ? EEH_HTML::nl() . $input->get_html_for_help() : '';
64
+            $html_for_input .= $input->get_html_for_help() != '' ? EEH_HTML::nl().$input->get_html_for_help() : '';
65 65
             $html .= EEH_HTML::tr(
66
-                EEH_HTML::th($input->get_html_for_label()) .
66
+                EEH_HTML::th($input->get_html_for_label()).
67 67
                 EEH_HTML::td($html_for_input)
68 68
             );
69 69
         }
Please login to merge, or discard this patch.
form_sections/strategies/layout/EE_Admin_Two_Column_Layout.strategy.php 2 patches
Indentation   +69 added lines, -69 removed lines patch added patch discarded remove patch
@@ -6,78 +6,78 @@
 block discarded – undo
6 6
  */
7 7
 class EE_Admin_Two_Column_Layout extends EE_Two_Column_Layout
8 8
 {
9
-    /**
10
-     * @param EE_Form_Section_Proper $form
11
-     */
12
-    public function _construct_finalize(EE_Form_Section_Proper $form)
13
-    {
14
-        parent::_construct_finalize($form);
15
-        $this->_form_section->set_html_class('ee-admin-two-column-layout form-table');
16
-    }
9
+	/**
10
+	 * @param EE_Form_Section_Proper $form
11
+	 */
12
+	public function _construct_finalize(EE_Form_Section_Proper $form)
13
+	{
14
+		parent::_construct_finalize($form);
15
+		$this->_form_section->set_html_class('ee-admin-two-column-layout form-table');
16
+	}
17 17
 
18 18
 
19
-    /**
20
-     * Overriding the parent table layout to include <tbody> tags
21
-     *
22
-     * @param array $additional_args
23
-     * @return string
24
-     * @throws EE_Error
25
-     */
26
-    public function layout_form_begin($additional_args = array())
27
-    {
28
-        return parent::layout_form_begin($additional_args);
29
-    }
19
+	/**
20
+	 * Overriding the parent table layout to include <tbody> tags
21
+	 *
22
+	 * @param array $additional_args
23
+	 * @return string
24
+	 * @throws EE_Error
25
+	 */
26
+	public function layout_form_begin($additional_args = array())
27
+	{
28
+		return parent::layout_form_begin($additional_args);
29
+	}
30 30
 
31 31
 
32 32
 
33
-    /**
34
-     * Lays out the row for the input, including label and errors
35
-     *
36
-     * @param EE_Form_Input_Base $input
37
-     * @return string
38
-     * @throws EE_Error
39
-     */
40
-    public function layout_input($input)
41
-    {
42
-        if (
43
-            $input->get_display_strategy() instanceof EE_Select_Display_Strategy
44
-            || $input->get_display_strategy() instanceof EE_Text_Area_Display_Strategy
45
-            || $input->get_display_strategy() instanceof EE_Text_Input_Display_Strategy
46
-            || $input->get_display_strategy() instanceof EE_Admin_File_Uploader_Display_Strategy
47
-        ) {
48
-            $html_class = $input->html_class();
49
-            $html_class = strpos($html_class, 'ee-input-width') === false
50
-                ? "$html_class ee-input-width--big"
51
-                : $html_class;
52
-            $input->set_html_class($html_class);
53
-        }
54
-        if ($input instanceof EE_Text_Area_Input) {
55
-            $input->set_rows(4);
56
-            $input->set_cols(60);
57
-        }
58
-        $input_html = $input->get_html_for_input();
59
-        // maybe add errors and help text ?
60
-        $input_html .= $input->get_html_for_errors() !== ''
61
-            ? EEH_HTML::nl() . $input->get_html_for_errors()
62
-            : '';
63
-        $input_html .= $input->get_html_for_help() !== ''
64
-            ? EEH_HTML::nl() . $input->get_html_for_help()
65
-            : '';
66
-        // overriding parent to add wp admin specific things.
67
-        $html = '';
68
-        if ($input instanceof EE_Hidden_Input) {
69
-            $html .= EEH_HTML::no_row($input->get_html_for_input());
70
-        } else {
71
-            $html .= EEH_HTML::tr(
72
-                EEH_HTML::th(
73
-                    $input->get_html_for_label(),
74
-                    '',
75
-                    '',
76
-                    '',
77
-                    'scope="row"'
78
-                ) . EEH_HTML::td($input_html)
79
-            );
80
-        }
81
-        return $html;
82
-    }
33
+	/**
34
+	 * Lays out the row for the input, including label and errors
35
+	 *
36
+	 * @param EE_Form_Input_Base $input
37
+	 * @return string
38
+	 * @throws EE_Error
39
+	 */
40
+	public function layout_input($input)
41
+	{
42
+		if (
43
+			$input->get_display_strategy() instanceof EE_Select_Display_Strategy
44
+			|| $input->get_display_strategy() instanceof EE_Text_Area_Display_Strategy
45
+			|| $input->get_display_strategy() instanceof EE_Text_Input_Display_Strategy
46
+			|| $input->get_display_strategy() instanceof EE_Admin_File_Uploader_Display_Strategy
47
+		) {
48
+			$html_class = $input->html_class();
49
+			$html_class = strpos($html_class, 'ee-input-width') === false
50
+				? "$html_class ee-input-width--big"
51
+				: $html_class;
52
+			$input->set_html_class($html_class);
53
+		}
54
+		if ($input instanceof EE_Text_Area_Input) {
55
+			$input->set_rows(4);
56
+			$input->set_cols(60);
57
+		}
58
+		$input_html = $input->get_html_for_input();
59
+		// maybe add errors and help text ?
60
+		$input_html .= $input->get_html_for_errors() !== ''
61
+			? EEH_HTML::nl() . $input->get_html_for_errors()
62
+			: '';
63
+		$input_html .= $input->get_html_for_help() !== ''
64
+			? EEH_HTML::nl() . $input->get_html_for_help()
65
+			: '';
66
+		// overriding parent to add wp admin specific things.
67
+		$html = '';
68
+		if ($input instanceof EE_Hidden_Input) {
69
+			$html .= EEH_HTML::no_row($input->get_html_for_input());
70
+		} else {
71
+			$html .= EEH_HTML::tr(
72
+				EEH_HTML::th(
73
+					$input->get_html_for_label(),
74
+					'',
75
+					'',
76
+					'',
77
+					'scope="row"'
78
+				) . EEH_HTML::td($input_html)
79
+			);
80
+		}
81
+		return $html;
82
+	}
83 83
 }
Please login to merge, or discard this patch.
Spacing   +3 added lines, -3 removed lines patch added patch discarded remove patch
@@ -58,10 +58,10 @@  discard block
 block discarded – undo
58 58
         $input_html = $input->get_html_for_input();
59 59
         // maybe add errors and help text ?
60 60
         $input_html .= $input->get_html_for_errors() !== ''
61
-            ? EEH_HTML::nl() . $input->get_html_for_errors()
61
+            ? EEH_HTML::nl().$input->get_html_for_errors()
62 62
             : '';
63 63
         $input_html .= $input->get_html_for_help() !== ''
64
-            ? EEH_HTML::nl() . $input->get_html_for_help()
64
+            ? EEH_HTML::nl().$input->get_html_for_help()
65 65
             : '';
66 66
         // overriding parent to add wp admin specific things.
67 67
         $html = '';
@@ -75,7 +75,7 @@  discard block
 block discarded – undo
75 75
                     '',
76 76
                     '',
77 77
                     'scope="row"'
78
-                ) . EEH_HTML::td($input_html)
78
+                ).EEH_HTML::td($input_html)
79 79
             );
80 80
         }
81 81
         return $html;
Please login to merge, or discard this patch.