Completed
Branch dev (7b1daa)
by
unknown
23:00 queued 14:17
created
core/db_models/EEM_Base.model.php 2 patches
Indentation   +6593 added lines, -6593 removed lines patch added patch discarded remove patch
@@ -37,6599 +37,6599 @@
 block discarded – undo
37 37
  */
38 38
 abstract class EEM_Base extends EE_Base implements ResettableInterface
39 39
 {
40
-    /**
41
-     * Flag to indicate whether the values provided to EEM_Base have already been prepared
42
-     * by the model object or not (ie, the model object has used the field's _prepare_for_set function on the values).
43
-     * They almost always WILL NOT, but it's not necessarily a requirement.
44
-     * For example, if you want to run EEM_Event::instance()->get_all(array(array('EVT_ID'=>$_GET['event_id'])));
45
-     *
46
-     * @var boolean
47
-     */
48
-    private $_values_already_prepared_by_model_object = 0;
49
-
50
-    /**
51
-     * when $_values_already_prepared_by_model_object equals this, we assume
52
-     * the data is just like form input that needs to have the model fields'
53
-     * prepare_for_set and prepare_for_use_in_db called on it
54
-     */
55
-    const not_prepared_by_model_object = 0;
56
-
57
-    /**
58
-     * when $_values_already_prepared_by_model_object equals this, we
59
-     * assume this value is coming from a model object and doesn't need to have
60
-     * prepare_for_set called on it, just prepare_for_use_in_db is used
61
-     */
62
-    const prepared_by_model_object = 1;
63
-
64
-    /**
65
-     * when $_values_already_prepared_by_model_object equals this, we assume
66
-     * the values are already to be used in the database (ie no processing is done
67
-     * on them by the model's fields)
68
-     */
69
-    const prepared_for_use_in_db = 2;
70
-
71
-
72
-    protected $singular_item = 'Item';
73
-
74
-    protected $plural_item   = 'Items';
75
-
76
-    /**
77
-     * @type EE_Table_Base[] $_tables array of EE_Table objects for defining which tables comprise this model.
78
-     */
79
-    protected $_tables;
80
-
81
-    /**
82
-     * with two levels: top-level has array keys which are database table aliases (ie, keys in _tables)
83
-     * and the value is an array. Each of those sub-arrays have keys of field names (eg 'ATT_ID', which should also be
84
-     * variable names on the model objects (eg, EE_Attendee), and the keys should be children of EE_Model_Field
85
-     *
86
-     * @var EE_Model_Field_Base[][] $_fields
87
-     */
88
-    protected $_fields;
89
-
90
-    /**
91
-     * array of different kinds of relations
92
-     *
93
-     * @var EE_Model_Relation_Base[] $_model_relations
94
-     */
95
-    protected $_model_relations = [];
96
-
97
-    /**
98
-     * @var EE_Index[] $_indexes
99
-     */
100
-    protected $_indexes = [];
101
-
102
-    /**
103
-     * Default strategy for getting where conditions on this model. This strategy is used to get default
104
-     * where conditions which are added to get_all, update, and delete queries. They can be overridden
105
-     * by setting the same columns as used in these queries in the query yourself.
106
-     *
107
-     * @var EE_Default_Where_Conditions
108
-     */
109
-    protected $_default_where_conditions_strategy;
110
-
111
-    /**
112
-     * Strategy for getting conditions on this model when 'default_where_conditions' equals 'minimum'.
113
-     * This is particularly useful when you want something between 'none' and 'default'
114
-     *
115
-     * @var EE_Default_Where_Conditions
116
-     */
117
-    protected $_minimum_where_conditions_strategy;
118
-
119
-    /**
120
-     * String describing how to find the "owner" of this model's objects.
121
-     * When there is a foreign key on this model to the wp_users table, this isn't needed.
122
-     * But when there isn't, this indicates which related model, or transiently-related model,
123
-     * has the foreign key to the wp_users table.
124
-     * Eg, for EEM_Registration this would be 'Event' because registrations are directly
125
-     * related to events, and events have a foreign key to wp_users.
126
-     * On EEM_Transaction, this would be 'Transaction.Event'
127
-     *
128
-     * @var string
129
-     */
130
-    protected $_model_chain_to_wp_user = '';
131
-
132
-    /**
133
-     * String describing how to find the model with a password controlling access to this model. This property has the
134
-     * same format as $_model_chain_to_wp_user. This is primarily used by the query param "exclude_protected".
135
-     * This value is the path of models to follow to arrive at the model with the password field.
136
-     * If it is an empty string, it means this model has the password field. If it is null, it means there is no
137
-     * model with a password that should affect reading this on the front-end.
138
-     * Eg this is an empty string for the Event model because it has a password.
139
-     * This is null for the Registration model, because its event's password has no bearing on whether
140
-     * you can read the registration or not on the front-end (it just depends on your capabilities.)
141
-     * This is 'Datetime.Event' on the Ticket model, because model queries for tickets that set "exclude_protected"
142
-     * should hide tickets for datetimes for events that have a password set.
143
-     *
144
-     * @var string |null
145
-     */
146
-    protected $model_chain_to_password = null;
147
-
148
-    /**
149
-     * This is a flag typically set by updates so that we don't load the where strategy on updates because updates
150
-     * don't need it (particularly CPT models)
151
-     *
152
-     * @var bool
153
-     */
154
-    protected $_ignore_where_strategy = false;
155
-
156
-    /**
157
-     * String used in caps relating to this model. Eg, if the caps relating to this
158
-     * model are 'ee_edit_events', 'ee_read_events', etc, it would be 'events'.
159
-     *
160
-     * @var string. If null it hasn't been initialized yet. If false then we
161
-     * have indicated capabilities don't apply to this
162
-     */
163
-    protected $_caps_slug = null;
164
-
165
-    /**
166
-     * 2d array where top-level keys are one of EEM_Base::valid_cap_contexts(),
167
-     * and next-level keys are capability names, and each's value is a
168
-     * EE_Default_Where_Condition. If the requester requests to apply caps to the query,
169
-     * they specify which context to use (ie, frontend, backend, edit or delete)
170
-     * and then each capability in the corresponding sub-array that they're missing
171
-     * adds the where conditions onto the query.
172
-     *
173
-     * @var array
174
-     */
175
-    protected $_cap_restrictions = [
176
-        self::caps_read       => [],
177
-        self::caps_read_admin => [],
178
-        self::caps_edit       => [],
179
-        self::caps_delete     => [],
180
-    ];
181
-
182
-    /**
183
-     * Array defining which cap restriction generators to use to create default
184
-     * cap restrictions to put in EEM_Base::_cap_restrictions.
185
-     * Array-keys are one of EEM_Base::valid_cap_contexts(), and values are a child of
186
-     * EE_Restriction_Generator_Base. If you don't want any cap restrictions generated
187
-     * automatically set this to false (not just null).
188
-     *
189
-     * @var EE_Restriction_Generator_Base[]
190
-     */
191
-    protected $_cap_restriction_generators = [];
192
-
193
-    /**
194
-     * constants used to categorize capability restrictions on EEM_Base::_caps_restrictions
195
-     */
196
-    const caps_read       = 'read';
197
-
198
-    const caps_read_admin = 'read_admin';
199
-
200
-    const caps_edit       = 'edit';
201
-
202
-    const caps_delete     = 'delete';
203
-
204
-    /**
205
-     * Keys are all the cap contexts (ie constants EEM_Base::_caps_*) and values are their 'action'
206
-     * as how they'd be used in capability names. Eg EEM_Base::caps_read ('read_frontend')
207
-     * maps to 'read' because when looking for relevant permissions we're going to use
208
-     * 'read' in teh capabilities names like 'ee_read_events' etc.
209
-     *
210
-     * @var array
211
-     */
212
-    protected $_cap_contexts_to_cap_action_map = [
213
-        self::caps_read       => 'read',
214
-        self::caps_read_admin => 'read',
215
-        self::caps_edit       => 'edit',
216
-        self::caps_delete     => 'delete',
217
-    ];
218
-
219
-    /**
220
-     * Timezone
221
-     * This gets set via the constructor so that we know what timezone incoming strings|timestamps are in when there
222
-     * are EE_Datetime_Fields in use.  This can also be used before a get to set what timezone you want strings coming
223
-     * out of the created objects.  NOT all EEM_Base child classes use this property but any that use a
224
-     * EE_Datetime_Field data type will have access to it.
225
-     *
226
-     * @var string
227
-     */
228
-    protected $_timezone;
229
-
230
-
231
-    /**
232
-     * This holds the id of the blog currently making the query.  Has no bearing on single site but is used for
233
-     * multisite.
234
-     *
235
-     * @var int
236
-     */
237
-    protected static $_model_query_blog_id;
238
-
239
-    /**
240
-     * A copy of _fields, except the array keys are the model names pointed to by
241
-     * the field
242
-     *
243
-     * @var EE_Model_Field_Base[]
244
-     */
245
-    private $_cache_foreign_key_to_fields = [];
246
-
247
-    /**
248
-     * Cached list of all the fields on the model, indexed by their name
249
-     *
250
-     * @var EE_Model_Field_Base[]
251
-     */
252
-    private $_cached_fields = null;
253
-
254
-    /**
255
-     * Cached list of all the fields on the model, except those that are
256
-     * marked as only pertinent to the database
257
-     *
258
-     * @var EE_Model_Field_Base[]
259
-     */
260
-    private $_cached_fields_non_db_only = null;
261
-
262
-    /**
263
-     * A cached reference to the primary key for quick lookup
264
-     *
265
-     * @var EE_Model_Field_Base
266
-     */
267
-    private $_primary_key_field = null;
268
-
269
-    /**
270
-     * Flag indicating whether this model has a primary key or not
271
-     *
272
-     * @var boolean
273
-     */
274
-    protected $_has_primary_key_field = null;
275
-
276
-    /**
277
-     * array in the format:  [ FK alias => full PK ]
278
-     * where keys are local column name aliases for foreign keys
279
-     * and values are the fully qualified column name for the primary key they represent
280
-     *  ex:
281
-     *      [ 'Event.EVT_wp_user' => 'WP_User.ID' ]
282
-     *
283
-     * @var array $foreign_key_aliases
284
-     */
285
-    protected $foreign_key_aliases = [];
286
-
287
-    /**
288
-     * Whether or not this model is based off a table in WP core only (CPTs should set
289
-     * this to FALSE, but if we were to make an EE_WP_Post model, it should set this to true).
290
-     * This should be true for models that deal with data that should exist independent of EE.
291
-     * For example, if the model can read and insert data that isn't used by EE, this should be true.
292
-     * It would be false, however, if you could guarantee the model would only interact with EE data,
293
-     * even if it uses a WP core table (eg event and venue models set this to false for that reason:
294
-     * they can only read and insert events and venues custom post types, not arbitrary post types)
295
-     *
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 = [
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 = ['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 = ['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
-     *
373
-     * @var array
374
-     */
375
-    protected $_like_style_operators = ['LIKE', 'NOT LIKE'];
376
-
377
-    /**
378
-     * operators that are used for handling NUll and !NULL queries.  Typically used for when checking if a row exists
379
-     * on a join table.
380
-     *
381
-     * @var array
382
-     */
383
-    protected $_null_style_operators = ['IS NOT NULL', 'IS NULL'];
384
-
385
-    /**
386
-     * Allowed values for $query_params['order'] for ordering in queries
387
-     *
388
-     * @var array
389
-     */
390
-    protected $_allowed_order_values = ['asc', 'desc', 'ASC', 'DESC'];
391
-
392
-    /**
393
-     * When these are keys in a WHERE or HAVING clause, they are handled much differently
394
-     * than regular field names. It is assumed that their values are an array of WHERE conditions
395
-     *
396
-     * @var array
397
-     */
398
-    private $_logic_query_param_keys = ['not', 'and', 'or', 'NOT', 'AND', 'OR'];
399
-
400
-    /**
401
-     * Allowed keys in $query_params arrays passed into queries. Note that 0 is meant to always be a
402
-     * 'where', but 'where' clauses are so common that we thought we'd omit it
403
-     *
404
-     * @var array
405
-     */
406
-    private $_allowed_query_params = [
407
-        0,
408
-        'limit',
409
-        'order_by',
410
-        'group_by',
411
-        'having',
412
-        'force_join',
413
-        'order',
414
-        'on_join_limit',
415
-        'default_where_conditions',
416
-        'caps',
417
-        'extra_selects',
418
-        'exclude_protected',
419
-    ];
420
-
421
-    /**
422
-     * All the data types that can be used in $wpdb->prepare statements.
423
-     *
424
-     * @var array
425
-     */
426
-    private $_valid_wpdb_data_types = ['%d', '%s', '%f'];
427
-
428
-    /**
429
-     * @var EE_Registry $EE
430
-     */
431
-    protected $EE = null;
432
-
433
-
434
-    /**
435
-     * Property which, when set, will have this model echo out the next X queries to the page for debugging.
436
-     *
437
-     * @var int
438
-     */
439
-    protected $_show_next_x_db_queries = 0;
440
-
441
-    /**
442
-     * When using _get_all_wpdb_results, you can specify a custom selection. If you do so,
443
-     * it gets saved on this property as an instance of CustomSelects so those selections can be used in
444
-     * WHERE, GROUP_BY, etc.
445
-     *
446
-     * @var CustomSelects
447
-     */
448
-    protected $_custom_selections = [];
449
-
450
-    /**
451
-     * key => value Entity Map using  array( EEM_Base::$_model_query_blog_id => array( ID => model object ) )
452
-     * caches every model object we've fetched from the DB on this request
453
-     *
454
-     * @var array
455
-     */
456
-    protected $_entity_map;
457
-
458
-    /**
459
-     * @var LoaderInterface
460
-     */
461
-    protected static $loader;
462
-
463
-    /**
464
-     * @var Mirror
465
-     */
466
-    private static $mirror;
467
-
468
-
469
-    /**
470
-     * constant used to show EEM_Base has not yet verified the db on this http request
471
-     */
472
-    const db_verified_none = 0;
473
-
474
-    /**
475
-     * constant used to show EEM_Base has verified the EE core db on this http request,
476
-     * but not the addons' dbs
477
-     */
478
-    const db_verified_core = 1;
479
-
480
-    /**
481
-     * constant used to show EEM_Base has verified the addons' dbs (and implicitly
482
-     * the EE core db too)
483
-     */
484
-    const db_verified_addons = 2;
485
-
486
-    /**
487
-     * indicates whether an EEM_Base child has already re-verified the DB
488
-     * is ok (we don't want to do it repetitively). Should be set to one the constants
489
-     * looking like EEM_Base::db_verified_*
490
-     *
491
-     * @var int - 0 = none, 1 = core, 2 = addons
492
-     */
493
-    protected static $_db_verification_level = EEM_Base::db_verified_none;
494
-
495
-    /**
496
-     * @const constant for 'default_where_conditions' to apply default where conditions to ALL queried models
497
-     *        (eg, if retrieving registrations ordered by their datetimes, this will only return non-trashed
498
-     *        registrations for non-trashed tickets for non-trashed datetimes)
499
-     */
500
-    const default_where_conditions_all = 'all';
501
-
502
-    /**
503
-     * @const constant for 'default_where_conditions' to apply default where conditions to THIS model only, but
504
-     *        no other models which are joined to (eg, if retrieving registrations ordered by their datetimes, this will
505
-     *        return non-trashed registrations, regardless of the related datetimes and tickets' statuses).
506
-     *        It is preferred to use EEM_Base::default_where_conditions_minimum_others because, when joining to
507
-     *        models which share tables with other models, this can return data for the wrong model.
508
-     */
509
-    const default_where_conditions_this_only = 'this_model_only';
510
-
511
-    /**
512
-     * @const constant for 'default_where_conditions' to apply default where conditions to other models queried,
513
-     *        but not the current model (eg, if retrieving registrations ordered by their datetimes, this will
514
-     *        return all registrations related to non-trashed tickets and non-trashed datetimes)
515
-     */
516
-    const default_where_conditions_others_only = 'other_models_only';
517
-
518
-    /**
519
-     * @const constant for 'default_where_conditions' to apply minimum where conditions to all models queried.
520
-     *        For most models this the same as EEM_Base::default_where_conditions_none, except for models which share
521
-     *        their table with other models, like the Event and Venue models. For example, when querying for events
522
-     *        ordered by their venues' name, this will be sure to only return real events with associated real venues
523
-     *        (regardless of whether those events and venues are trashed)
524
-     *        In contrast, using EEM_Base::default_where_conditions_none would could return WP posts other than EE
525
-     *        events.
526
-     */
527
-    const default_where_conditions_minimum_all = 'minimum';
528
-
529
-    /**
530
-     * @const constant for 'default_where_conditions' to apply apply where conditions to other models, and full default
531
-     *        where conditions for the queried model (eg, when querying events ordered by venues' names, this will
532
-     *        return non-trashed events for any venues, regardless of whether those associated venues are trashed or
533
-     *        not)
534
-     */
535
-    const default_where_conditions_minimum_others = 'full_this_minimum_others';
536
-
537
-    /**
538
-     * @const constant for 'default_where_conditions' to NOT apply any where conditions. This should very rarely be
539
-     *        used, because when querying from a model which shares its table with another model (eg Events and Venues)
540
-     *        it's possible it will return table entries for other models. You should use
541
-     *        EEM_Base::default_where_conditions_minimum_all instead.
542
-     */
543
-    const default_where_conditions_none = 'none';
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
-                $table_obj->_construct_finalize_set_table_to_join_with($this->_get_main_table());
596
-            }
597
-        }
598
-        /**
599
-         * Filters the list of fields on a model. It is best to NOT use this directly and instead just use
600
-         * EE_Register_Model_Extension
601
-         *
602
-         * @param EE_Model_Field_Base[] $_fields
603
-         */
604
-        $this->_fields = (array) apply_filters('FHEE__' . get_class($this) . '__construct__fields', $this->_fields);
605
-        $this->_invalidate_field_caches();
606
-        foreach ($this->_fields as $table_alias => $fields_for_table) {
607
-            if (! array_key_exists($table_alias, $this->_tables)) {
608
-                throw new EE_Error(
609
-                    sprintf(
610
-                        esc_html__(
611
-                            "Table alias %s does not exist in EEM_Base child's _tables array. Only tables defined are %s",
612
-                            'event_espresso'
613
-                        ),
614
-                        $table_alias,
615
-                        implode(",", $this->_fields)
616
-                    )
617
-                );
618
-            }
619
-            foreach ($fields_for_table as $field_name => $field_obj) {
620
-                /** @var $field_obj EE_Model_Field_Base | EE_Primary_Key_Field_Base */
621
-                // primary key field base has a slightly different _construct_finalize
622
-                /** @var $field_obj EE_Model_Field_Base */
623
-                $field_obj->_construct_finalize($table_alias, $field_name, $this->get_this_model_name());
624
-            }
625
-        }
626
-        // everything is related to Extra_Meta
627
-        if (get_class($this) !== 'EEM_Extra_Meta') {
628
-            // make extra meta related to everything, but don't block deleting things just
629
-            // because they have related extra meta info. For now just orphan those extra meta
630
-            // in the future we should automatically delete them
631
-            $this->_model_relations['Extra_Meta'] = new EE_Has_Many_Any_Relation(false);
632
-        }
633
-        // and change logs
634
-        if (get_class($this) !== 'EEM_Change_Log') {
635
-            $this->_model_relations['Change_Log'] = new EE_Has_Many_Any_Relation(false);
636
-        }
637
-        /**
638
-         * Filters the list of relations on a model. It is best to NOT use this directly and instead just use
639
-         * EE_Register_Model_Extension
640
-         *
641
-         * @param EE_Model_Relation_Base[] $_model_relations
642
-         */
643
-        $this->_model_relations = (array) apply_filters(
644
-            'FHEE__' . get_class($this) . '__construct__model_relations',
645
-            $this->_model_relations
646
-        );
647
-        foreach ($this->_model_relations as $model_name => $relation_obj) {
648
-            /** @var $relation_obj EE_Model_Relation_Base */
649
-            $relation_obj->_construct_finalize_set_models($this->get_this_model_name(), $model_name);
650
-        }
651
-        foreach ($this->_indexes as $index_name => $index_obj) {
652
-            $index_obj->_construct_finalize($index_name, $this->get_this_model_name());
653
-        }
654
-        $this->set_timezone($timezone);
655
-        // finalize default where condition strategy, or set default
656
-        if (! $this->_default_where_conditions_strategy) {
657
-            // nothing was set during child constructor, so set default
658
-            $this->_default_where_conditions_strategy = new EE_Default_Where_Conditions();
659
-        }
660
-        $this->_default_where_conditions_strategy->_finalize_construct($this);
661
-        if (! $this->_minimum_where_conditions_strategy) {
662
-            // nothing was set during child constructor, so set default
663
-            $this->_minimum_where_conditions_strategy = new EE_Default_Where_Conditions();
664
-        }
665
-        $this->_minimum_where_conditions_strategy->_finalize_construct($this);
666
-        // if the cap slug hasn't been set, and we haven't set it to false on purpose
667
-        // to indicate to NOT set it, set it to the logical default
668
-        if ($this->_caps_slug === null) {
669
-            $this->_caps_slug = EEH_Inflector::pluralize_and_lower($this->get_this_model_name());
670
-        }
671
-        // initialize the standard cap restriction generators if none were specified by the child constructor
672
-        if (is_array($this->_cap_restriction_generators)) {
673
-            foreach ($this->cap_contexts_to_cap_action_map() as $cap_context => $action) {
674
-                if (! isset($this->_cap_restriction_generators[ $cap_context ])) {
675
-                    $this->_cap_restriction_generators[ $cap_context ] = apply_filters(
676
-                        'FHEE__EEM_Base___construct__standard_cap_restriction_generator',
677
-                        new EE_Restriction_Generator_Protected(),
678
-                        $cap_context,
679
-                        $this
680
-                    );
681
-                }
682
-            }
683
-        }
684
-        // if there are cap restriction generators, use them to make the default cap restrictions
685
-        if (is_array($this->_cap_restriction_generators)) {
686
-            foreach ($this->_cap_restriction_generators as $context => $generator_object) {
687
-                if (! $generator_object) {
688
-                    continue;
689
-                }
690
-                if (! $generator_object instanceof EE_Restriction_Generator_Base) {
691
-                    throw new EE_Error(
692
-                        sprintf(
693
-                            esc_html__(
694
-                                '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.',
695
-                                'event_espresso'
696
-                            ),
697
-                            $context,
698
-                            $this->get_this_model_name()
699
-                        )
700
-                    );
701
-                }
702
-                $action = $this->cap_action_for_context($context);
703
-                if (! $generator_object->construction_finalized()) {
704
-                    $generator_object->_construct_finalize($this, $action);
705
-                }
706
-            }
707
-        }
708
-        do_action('AHEE__' . get_class($this) . '__construct__end');
709
-    }
710
-
711
-
712
-    /**
713
-     * @return LoaderInterface
714
-     * @throws InvalidArgumentException
715
-     * @throws InvalidDataTypeException
716
-     * @throws InvalidInterfaceException
717
-     */
718
-    protected static function getLoader(): LoaderInterface
719
-    {
720
-        if (! EEM_Base::$loader instanceof LoaderInterface) {
721
-            EEM_Base::$loader = LoaderFactory::getLoader();
722
-        }
723
-        return EEM_Base::$loader;
724
-    }
725
-
726
-
727
-    /**
728
-     * @return Mirror
729
-     * @since   $VID:$
730
-     */
731
-    private static function getMirror(): Mirror
732
-    {
733
-        if (! EEM_Base::$mirror instanceof Mirror) {
734
-            EEM_Base::$mirror = EEM_Base::getLoader()->getShared(Mirror::class);
735
-        }
736
-        return EEM_Base::$mirror;
737
-    }
738
-
739
-
740
-    /**
741
-     * @param string $model_class_Name
742
-     * @param string $timezone
743
-     * @return array
744
-     * @throws ReflectionException
745
-     * @since   $VID:$
746
-     */
747
-    private static function getModelArguments(string $model_class_Name, string $timezone): array
748
-    {
749
-        $arguments = [$timezone];
750
-        $params    = EEM_Base::getMirror()->getParameters($model_class_Name);
751
-        if (count($params) > 1) {
752
-            if ($params[1]->getName() === 'model_field_factory') {
753
-                $arguments = [
754
-                    $timezone,
755
-                    EEM_Base::getLoader()->getShared(ModelFieldFactory::class),
756
-                ];
757
-            } elseif ($model_class_Name === 'EEM_Form_Section') {
758
-                $arguments = [
759
-                    EEM_Base::getLoader()->getShared('EventEspresso\core\services\form\meta\FormStatus'),
760
-                    $timezone,
761
-                ];
762
-            } elseif ($model_class_Name === 'EEM_Form_Element') {
763
-                $arguments = [
764
-                    EEM_Base::getLoader()->getShared('EventEspresso\core\services\form\meta\FormStatus'),
765
-                    EEM_Base::getLoader()->getShared('EventEspresso\core\services\form\meta\InputTypes'),
766
-                    $timezone,
767
-                ];
768
-            }
769
-        }
770
-        return $arguments;
771
-    }
772
-
773
-
774
-    /**
775
-     * This function is a singleton method used to instantiate the Espresso_model object
776
-     *
777
-     * @param string|null $timezone   string representing the timezone we want to set for returned Date Time Strings
778
-     *                                (and any incoming timezone data that gets saved).
779
-     *                                Note this just sends the timezone info to the date time model field objects.
780
-     *                                Default is NULL
781
-     *                                (and will be assumed using the set timezone in the 'timezone_string' wp option)
782
-     * @return static (as in the concrete child class)
783
-     * @throws EE_Error
784
-     * @throws ReflectionException
785
-     */
786
-    public static function instance($timezone = null)
787
-    {
788
-        // check if instance of Espresso_model already exists
789
-        if (! static::$_instance instanceof static) {
790
-            $arguments = EEM_Base::getModelArguments(static::class, (string) $timezone);
791
-            $model     = new static(...$arguments);
792
-            EEM_Base::getLoader()->share(static::class, $model, $arguments);
793
-            static::$_instance = $model;
794
-        }
795
-        // we might have a timezone set, let set_timezone decide what to do with it
796
-        if ($timezone) {
797
-            static::$_instance->set_timezone($timezone);
798
-        }
799
-        // Espresso_model object
800
-        return static::$_instance;
801
-    }
802
-
803
-
804
-    /**
805
-     * resets the model and returns it
806
-     *
807
-     * @param string|null $timezone
808
-     * @return EEM_Base|null (if the model was already instantiated, returns it, with
809
-     * all its properties reset; if it wasn't instantiated, returns null)
810
-     * @throws EE_Error
811
-     * @throws ReflectionException
812
-     * @throws InvalidArgumentException
813
-     * @throws InvalidDataTypeException
814
-     * @throws InvalidInterfaceException
815
-     */
816
-    public static function reset($timezone = null)
817
-    {
818
-        if (! static::$_instance instanceof EEM_Base) {
819
-            return null;
820
-        }
821
-        // Let's NOT swap out the current instance for a new one
822
-        // because if someone has a reference to it, we can't remove their reference.
823
-        // It's best to keep using the same reference but change the original object instead,
824
-        // so reset all its properties to their original values as defined in the class.
825
-        $static_properties = EEM_Base::getMirror()->getStaticProperties(static::class);
826
-        foreach (EEM_Base::getMirror()->getDefaultProperties(static::class) as $property => $value) {
827
-            // don't set instance to null like it was originally,
828
-            // but it's static anyways, and we're ignoring static properties (for now at least)
829
-            if (! isset($static_properties[ $property ])) {
830
-                static::$_instance->{$property} = $value;
831
-            }
832
-        }
833
-        // and then directly call its constructor again, like we would if we were creating a new one
834
-        $arguments = EEM_Base::getModelArguments(static::class, (string) $timezone);
835
-        static::$_instance->__construct(...$arguments);
836
-        return self::instance();
837
-    }
838
-
839
-
840
-    /**
841
-     * Used to set the $_model_query_blog_id static property.
842
-     *
843
-     * @param int $blog_id  If provided then will set the blog_id for the models to this id.  If not provided then the
844
-     *                      value for get_current_blog_id() will be used.
845
-     */
846
-    public static function set_model_query_blog_id($blog_id = 0)
847
-    {
848
-        EEM_Base::$_model_query_blog_id = $blog_id > 0
849
-            ? (int) $blog_id
850
-            : get_current_blog_id();
851
-    }
852
-
853
-
854
-    /**
855
-     * Returns whatever is set as the internal $model_query_blog_id.
856
-     *
857
-     * @return int
858
-     */
859
-    public static function get_model_query_blog_id()
860
-    {
861
-        return EEM_Base::$_model_query_blog_id;
862
-    }
863
-
864
-
865
-    /**
866
-     * retrieve the status details from esp_status table as an array IF this model has the status table as a relation.
867
-     *
868
-     * @param boolean $translated return localized strings or JUST the array.
869
-     * @return array
870
-     * @throws EE_Error
871
-     * @throws InvalidArgumentException
872
-     * @throws InvalidDataTypeException
873
-     * @throws InvalidInterfaceException
874
-     * @throws ReflectionException
875
-     */
876
-    public function status_array($translated = false)
877
-    {
878
-        if (! array_key_exists('Status', $this->_model_relations)) {
879
-            return [];
880
-        }
881
-        $model_name   = $this->get_this_model_name();
882
-        $status_type  = str_replace(' ', '_', strtolower(str_replace('_', ' ', $model_name)));
883
-        $stati        = EEM_Status::instance()->get_all([['STS_type' => $status_type]]);
884
-        $status_array = [];
885
-        foreach ($stati as $status) {
886
-            $status_array[ $status->ID() ] = $status->get('STS_code');
887
-        }
888
-        return $translated
889
-            ? EEM_Status::instance()->localized_status($status_array, false, 'sentence')
890
-            : $status_array;
891
-    }
892
-
893
-
894
-    /**
895
-     * Gets all the EE_Base_Class objects which match the $query_params, by querying the DB.
896
-     *
897
-     * @param array $query_params             @see
898
-     *                                        https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
899
-     *                                        or if you have the development copy of EE you can view this at the path:
900
-     *                                        /docs/G--Model-System/model-query-params.md
901
-     * @return EE_Base_Class[]  *note that there is NO option to pass the output type. If you want results different
902
-     *                                        from EE_Base_Class[], use get_all_wpdb_results(). Array keys are object
903
-     *                                        IDs (if there is a primary key on the model. if not, numerically indexed)
904
-     *                                        Some full examples: get 10 transactions which have Scottish attendees:
905
-     *                                        EEM_Transaction::instance()->get_all( array( array(
906
-     *                                        'OR'=>array(
907
-     *                                        'Registration.Attendee.ATT_fname'=>array('like','Mc%'),
908
-     *                                        'Registration.Attendee.ATT_fname*other'=>array('like','Mac%')
909
-     *                                        )
910
-     *                                        ),
911
-     *                                        'limit'=>10,
912
-     *                                        'group_by'=>'TXN_ID'
913
-     *                                        ));
914
-     *                                        get all the answers to the question titled "shirt size" for event with id
915
-     *                                        12, ordered by their answer EEM_Answer::instance()->get_all(array( array(
916
-     *                                        'Question.QST_display_text'=>'shirt size',
917
-     *                                        'Registration.Event.EVT_ID'=>12
918
-     *                                        ),
919
-     *                                        'order_by'=>array('ANS_value'=>'ASC')
920
-     *                                        ));
921
-     * @throws EE_Error
922
-     * @throws ReflectionException
923
-     */
924
-    public function get_all($query_params = [])
925
-    {
926
-        if (
927
-            isset($query_params['limit'])
928
-            && ! isset($query_params['group_by'])
929
-        ) {
930
-            $query_params['group_by'] = array_keys($this->get_combined_primary_key_fields());
931
-        }
932
-        return $this->_create_objects($this->_get_all_wpdb_results($query_params));
933
-    }
934
-
935
-
936
-    /**
937
-     * Modifies the query parameters so we only get back model objects
938
-     * that "belong" to the current user
939
-     *
940
-     * @param array $query_params @see
941
-     *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
942
-     * @return array @see
943
-     *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
944
-     * @throws ReflectionException
945
-     * @throws ReflectionException
946
-     */
947
-    public function alter_query_params_to_only_include_mine($query_params = [])
948
-    {
949
-        $wp_user_field_name = $this->wp_user_field_name();
950
-        if ($wp_user_field_name) {
951
-            $query_params[0][ $wp_user_field_name ] = get_current_user_id();
952
-        }
953
-        return $query_params;
954
-    }
955
-
956
-
957
-    /**
958
-     * Returns the name of the field's name that points to the WP_User table
959
-     *  on this model (or follows the _model_chain_to_wp_user and uses that model's
960
-     * foreign key to the WP_User table)
961
-     *
962
-     * @return string|boolean string on success, boolean false when there is no
963
-     * foreign key to the WP_User table
964
-     * @throws ReflectionException
965
-     * @throws ReflectionException
966
-     */
967
-    public function wp_user_field_name()
968
-    {
969
-        try {
970
-            if (! empty($this->_model_chain_to_wp_user)) {
971
-                $models_to_follow_to_wp_users = explode('.', $this->_model_chain_to_wp_user);
972
-                $last_model_name              = end($models_to_follow_to_wp_users);
973
-                $model_with_fk_to_wp_users    = EE_Registry::instance()->load_model($last_model_name);
974
-                $model_chain_to_wp_user       = $this->_model_chain_to_wp_user . '.';
975
-            } else {
976
-                $model_with_fk_to_wp_users = $this;
977
-                $model_chain_to_wp_user    = '';
978
-            }
979
-            $wp_user_field = $model_with_fk_to_wp_users->get_foreign_key_to('WP_User');
980
-            return $model_chain_to_wp_user . $wp_user_field->get_name();
981
-        } catch (EE_Error $e) {
982
-            return false;
983
-        }
984
-    }
985
-
986
-
987
-    /**
988
-     * Returns the _model_chain_to_wp_user string, which indicates which related model
989
-     * (or transiently-related model) has a foreign key to the wp_users table;
990
-     * useful for finding if model objects of this type are 'owned' by the current user.
991
-     * This is an empty string when the foreign key is on this model and when it isn't,
992
-     * but is only non-empty when this model's ownership is indicated by a RELATED model
993
-     * (or transiently-related model)
994
-     *
995
-     * @return string
996
-     */
997
-    public function model_chain_to_wp_user()
998
-    {
999
-        return $this->_model_chain_to_wp_user;
1000
-    }
1001
-
1002
-
1003
-    /**
1004
-     * Whether this model is 'owned' by a specific wordpress user (even indirectly,
1005
-     * like how registrations don't have a foreign key to wp_users, but the
1006
-     * events they are for are), or is unrelated to wp users.
1007
-     * generally available
1008
-     *
1009
-     * @return boolean
1010
-     */
1011
-    public function is_owned()
1012
-    {
1013
-        if ($this->model_chain_to_wp_user()) {
1014
-            return true;
1015
-        }
1016
-        try {
1017
-            $this->get_foreign_key_to('WP_User');
1018
-            return true;
1019
-        } catch (EE_Error $e) {
1020
-            return false;
1021
-        }
1022
-    }
1023
-
1024
-
1025
-    /**
1026
-     * Used internally to get WPDB results, because other functions, besides get_all, may want to do some queries, but
1027
-     * may want to preserve the WPDB results (eg, update, which first queries to make sure we have all the tables on
1028
-     * the model)
1029
-     *
1030
-     * @param array  $query_params      @see
1031
-     *                                  https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1032
-     * @param string $output            ARRAY_A, OBJECT_K, etc. Just like
1033
-     * @param mixed  $columns_to_select , What columns to select. By default, we select all columns specified by the
1034
-     *                                  fields on the model, and the models we joined to in the query. However, you can
1035
-     *                                  override this and set the select to "*", or a specific column name, like
1036
-     *                                  "ATT_ID", etc. If you would like to use these custom selections in WHERE,
1037
-     *                                  GROUP_BY, or HAVING clauses, you must instead provide an array. Array keys are
1038
-     *                                  the aliases used to refer to this selection, and values are to be
1039
-     *                                  numerically-indexed arrays, where 0 is the selection and 1 is the data type.
1040
-     *                                  Eg, array('count'=>array('COUNT(REG_ID)','%d'))
1041
-     * @return array | stdClass[] like results of $wpdb->get_results($sql,OBJECT), (ie, output type is OBJECT)
1042
-     * @throws EE_Error
1043
-     * @throws InvalidArgumentException
1044
-     */
1045
-    protected function _get_all_wpdb_results($query_params = [], $output = ARRAY_A, $columns_to_select = null)
1046
-    {
1047
-        $this->_custom_selections = $this->getCustomSelection($query_params, $columns_to_select);
1048
-        $model_query_info         = $this->_create_model_query_info_carrier($query_params);
1049
-        $select_expressions       = $columns_to_select === null
1050
-            ? $this->_construct_default_select_sql($model_query_info)
1051
-            : '';
1052
-        if ($this->_custom_selections instanceof CustomSelects) {
1053
-            $custom_expressions = $this->_custom_selections->columnsToSelectExpression();
1054
-            $select_expressions .= $select_expressions
1055
-                ? ', ' . $custom_expressions
1056
-                : $custom_expressions;
1057
-        }
1058
-
1059
-        $SQL = "SELECT $select_expressions " . $this->_construct_2nd_half_of_select_query($model_query_info);
1060
-        return $this->_do_wpdb_query('get_results', [$SQL, $output]);
1061
-    }
1062
-
1063
-
1064
-    /**
1065
-     * Get a CustomSelects object if the $query_params or $columns_to_select allows for it.
1066
-     * Note: $query_params['extra_selects'] will always override any $columns_to_select values. It is the preferred
1067
-     * method of including extra select information.
1068
-     *
1069
-     * @param array             $query_params
1070
-     * @param null|array|string $columns_to_select
1071
-     * @return null|CustomSelects
1072
-     * @throws InvalidArgumentException
1073
-     */
1074
-    protected function getCustomSelection(array $query_params, $columns_to_select = null)
1075
-    {
1076
-        if (! isset($query_params['extra_selects']) && $columns_to_select === null) {
1077
-            return null;
1078
-        }
1079
-        $selects = isset($query_params['extra_selects']) ? $query_params['extra_selects'] : $columns_to_select;
1080
-        $selects = is_string($selects) ? explode(',', $selects) : $selects;
1081
-        return new CustomSelects($selects);
1082
-    }
1083
-
1084
-
1085
-    /**
1086
-     * Gets an array of rows from the database just like $wpdb->get_results would,
1087
-     * but you can use the model query params to more easily
1088
-     * take care of joins, field preparation etc.
1089
-     *
1090
-     * @param array  $query_params      @see
1091
-     *                                  https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1092
-     * @param string $output            ARRAY_A, OBJECT_K, etc. Just like
1093
-     * @param mixed  $columns_to_select , What columns to select. By default, we select all columns specified by the
1094
-     *                                  fields on the model, and the models we joined to in the query. However, you can
1095
-     *                                  override this and set the select to "*", or a specific column name, like
1096
-     *                                  "ATT_ID", etc. If you would like to use these custom selections in WHERE,
1097
-     *                                  GROUP_BY, or HAVING clauses, you must instead provide an array. Array keys are
1098
-     *                                  the aliases used to refer to this selection, and values are to be
1099
-     *                                  numerically-indexed arrays, where 0 is the selection and 1 is the data type.
1100
-     *                                  Eg, array('count'=>array('COUNT(REG_ID)','%d'))
1101
-     * @return array|stdClass[] like results of $wpdb->get_results($sql,OBJECT), (ie, output type is OBJECT)
1102
-     * @throws EE_Error
1103
-     */
1104
-    public function get_all_wpdb_results($query_params = [], $output = ARRAY_A, $columns_to_select = null)
1105
-    {
1106
-        return $this->_get_all_wpdb_results($query_params, $output, $columns_to_select);
1107
-    }
1108
-
1109
-
1110
-    /**
1111
-     * For creating a custom select statement
1112
-     *
1113
-     * @param mixed $columns_to_select either a string to be inserted directly as the select statement,
1114
-     *                                 or an array where keys are aliases, and values are arrays where 0=>the selection
1115
-     *                                 SQL, and 1=>is the datatype
1116
-     * @return string
1117
-     * @throws EE_Error
1118
-     */
1119
-    private function _construct_select_from_input($columns_to_select)
1120
-    {
1121
-        if (is_array($columns_to_select)) {
1122
-            $select_sql_array = [];
1123
-            foreach ($columns_to_select as $alias => $selection_and_datatype) {
1124
-                if (! is_array($selection_and_datatype) || ! isset($selection_and_datatype[1])) {
1125
-                    throw new EE_Error(
1126
-                        sprintf(
1127
-                            esc_html__(
1128
-                                "Custom selection %s (alias %s) needs to be an array like array('COUNT(REG_ID)','%%d')",
1129
-                                'event_espresso'
1130
-                            ),
1131
-                            $selection_and_datatype,
1132
-                            $alias
1133
-                        )
1134
-                    );
1135
-                }
1136
-                if (! in_array($selection_and_datatype[1], $this->_valid_wpdb_data_types, true)) {
1137
-                    throw new EE_Error(
1138
-                        sprintf(
1139
-                            esc_html__(
1140
-                                "Datatype %s (for selection '%s' and alias '%s') is not a valid wpdb datatype (eg %%s)",
1141
-                                'event_espresso'
1142
-                            ),
1143
-                            $selection_and_datatype[1],
1144
-                            $selection_and_datatype[0],
1145
-                            $alias,
1146
-                            implode(', ', $this->_valid_wpdb_data_types)
1147
-                        )
1148
-                    );
1149
-                }
1150
-                $select_sql_array[] = "{$selection_and_datatype[0]} AS $alias";
1151
-            }
1152
-            $columns_to_select_string = implode(', ', $select_sql_array);
1153
-        } else {
1154
-            $columns_to_select_string = $columns_to_select;
1155
-        }
1156
-        return $columns_to_select_string;
1157
-    }
1158
-
1159
-
1160
-    /**
1161
-     * Convenient wrapper for getting the primary key field's name. Eg, on Registration, this would be 'REG_ID'
1162
-     *
1163
-     * @return string
1164
-     * @throws EE_Error
1165
-     */
1166
-    public function primary_key_name()
1167
-    {
1168
-        return $this->get_primary_key_field()->get_name();
1169
-    }
1170
-
1171
-
1172
-    /**
1173
-     * Gets a single item for this model from the DB, given only its ID (or null if none is found).
1174
-     * If there is no primary key on this model, $id is treated as primary key string
1175
-     *
1176
-     * @param mixed $id int or string, depending on the type of the model's primary key
1177
-     * @return EE_Base_Class|mixed|null
1178
-     * @throws EE_Error
1179
-     * @throws ReflectionException
1180
-     */
1181
-    public function get_one_by_ID($id)
1182
-    {
1183
-        if ($this->get_from_entity_map($id)) {
1184
-            return $this->get_from_entity_map($id);
1185
-        }
1186
-        $model_object = $this->get_one(
1187
-            $this->alter_query_params_to_restrict_by_ID(
1188
-                $id,
1189
-                ['default_where_conditions' => EEM_Base::default_where_conditions_minimum_all]
1190
-            )
1191
-        );
1192
-        $className    = $this->_get_class_name();
1193
-        if ($model_object instanceof $className) {
1194
-            // make sure valid objects get added to the entity map
1195
-            // so that the next call to this method doesn't trigger another trip to the db
1196
-            $this->add_to_entity_map($model_object);
1197
-        }
1198
-        return $model_object;
1199
-    }
1200
-
1201
-
1202
-    /**
1203
-     * Alters query parameters to only get items with this ID are returned.
1204
-     * Takes into account that the ID might be a string produced by EEM_Base::get_index_primary_key_string(),
1205
-     * or could just be a simple primary key ID
1206
-     *
1207
-     * @param int   $id
1208
-     * @param array $query_params
1209
-     * @return array of normal query params, @see
1210
-     *               https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1211
-     * @throws EE_Error
1212
-     */
1213
-    public function alter_query_params_to_restrict_by_ID($id, $query_params = [])
1214
-    {
1215
-        if (! isset($query_params[0])) {
1216
-            $query_params[0] = [];
1217
-        }
1218
-        $conditions_from_id = $this->parse_index_primary_key_string($id);
1219
-        if ($conditions_from_id === null) {
1220
-            $query_params[0][ $this->primary_key_name() ] = $id;
1221
-        } else {
1222
-            // no primary key, so the $id must be from the get_index_primary_key_string()
1223
-            $query_params[0] = array_replace_recursive($query_params[0], $this->parse_index_primary_key_string($id));
1224
-        }
1225
-        return $query_params;
1226
-    }
1227
-
1228
-
1229
-    /**
1230
-     * Gets a single item for this model from the DB, given the $query_params. Only returns a single class, not an
1231
-     * array. If no item is found, null is returned.
1232
-     *
1233
-     * @param array $query_params like EEM_Base's $query_params variable.
1234
-     * @return EE_Base_Class|EE_Soft_Delete_Base_Class|NULL
1235
-     * @throws EE_Error
1236
-     */
1237
-    public function get_one($query_params = [])
1238
-    {
1239
-        if (! is_array($query_params)) {
1240
-            EE_Error::doing_it_wrong(
1241
-                'EEM_Base::get_one',
1242
-                sprintf(
1243
-                    esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1244
-                    gettype($query_params)
1245
-                ),
1246
-                '4.6.0'
1247
-            );
1248
-            $query_params = [];
1249
-        }
1250
-        $query_params['limit'] = 1;
1251
-        $items                 = $this->get_all($query_params);
1252
-        if (empty($items)) {
1253
-            return null;
1254
-        }
1255
-        return array_shift($items);
1256
-    }
1257
-
1258
-
1259
-    /**
1260
-     * Returns the next x number of items in sequence from the given value as
1261
-     * found in the database matching the given query conditions.
1262
-     *
1263
-     * @param mixed $current_field_value    Value used for the reference point.
1264
-     * @param null  $field_to_order_by      What field is used for the
1265
-     *                                      reference point.
1266
-     * @param int   $limit                  How many to return.
1267
-     * @param array $query_params           Extra conditions on the query.
1268
-     * @param null  $columns_to_select      If left null, then an array of
1269
-     *                                      EE_Base_Class objects is returned,
1270
-     *                                      otherwise you can indicate just the
1271
-     *                                      columns you want returned.
1272
-     * @return EE_Base_Class[]|array
1273
-     * @throws EE_Error
1274
-     */
1275
-    public function next_x(
1276
-        $current_field_value,
1277
-        $field_to_order_by = null,
1278
-        $limit = 1,
1279
-        $query_params = [],
1280
-        $columns_to_select = null
1281
-    ) {
1282
-        return $this->_get_consecutive(
1283
-            $current_field_value,
1284
-            '>',
1285
-            $field_to_order_by,
1286
-            $limit,
1287
-            $query_params,
1288
-            $columns_to_select
1289
-        );
1290
-    }
1291
-
1292
-
1293
-    /**
1294
-     * Returns the previous x number of items in sequence from the given value
1295
-     * as found in the database matching the given query conditions.
1296
-     *
1297
-     * @param mixed $current_field_value    Value used for the reference point.
1298
-     * @param null  $field_to_order_by      What field is used for the
1299
-     *                                      reference point.
1300
-     * @param int   $limit                  How many to return.
1301
-     * @param array $query_params           Extra conditions on the query.
1302
-     * @param null  $columns_to_select      If left null, then an array of
1303
-     *                                      EE_Base_Class objects is returned,
1304
-     *                                      otherwise you can indicate just the
1305
-     *                                      columns you want returned.
1306
-     * @return EE_Base_Class[]|array
1307
-     * @throws EE_Error
1308
-     */
1309
-    public function previous_x(
1310
-        $current_field_value,
1311
-        $field_to_order_by = null,
1312
-        $limit = 1,
1313
-        $query_params = [],
1314
-        $columns_to_select = null
1315
-    ) {
1316
-        return $this->_get_consecutive(
1317
-            $current_field_value,
1318
-            '<',
1319
-            $field_to_order_by,
1320
-            $limit,
1321
-            $query_params,
1322
-            $columns_to_select
1323
-        );
1324
-    }
1325
-
1326
-
1327
-    /**
1328
-     * Returns the next item in sequence from the given value as found in the
1329
-     * database matching the given query conditions.
1330
-     *
1331
-     * @param mixed $current_field_value    Value used for the reference point.
1332
-     * @param null  $field_to_order_by      What field is used for the
1333
-     *                                      reference point.
1334
-     * @param array $query_params           Extra conditions on the query.
1335
-     * @param null  $columns_to_select      If left null, then an EE_Base_Class
1336
-     *                                      object is returned, otherwise you
1337
-     *                                      can indicate just the columns you
1338
-     *                                      want and a single array indexed by
1339
-     *                                      the columns will be returned.
1340
-     * @return EE_Base_Class|null|array()
1341
-     * @throws EE_Error
1342
-     */
1343
-    public function next(
1344
-        $current_field_value,
1345
-        $field_to_order_by = null,
1346
-        $query_params = [],
1347
-        $columns_to_select = null
1348
-    ) {
1349
-        $results = $this->_get_consecutive(
1350
-            $current_field_value,
1351
-            '>',
1352
-            $field_to_order_by,
1353
-            1,
1354
-            $query_params,
1355
-            $columns_to_select
1356
-        );
1357
-        return empty($results) ? null : reset($results);
1358
-    }
1359
-
1360
-
1361
-    /**
1362
-     * Returns the previous item in sequence from the given value as found in
1363
-     * the database matching the given query conditions.
1364
-     *
1365
-     * @param mixed $current_field_value    Value used for the reference point.
1366
-     * @param null  $field_to_order_by      What field is used for the
1367
-     *                                      reference point.
1368
-     * @param array $query_params           Extra conditions on the query.
1369
-     * @param null  $columns_to_select      If left null, then an EE_Base_Class
1370
-     *                                      object is returned, otherwise you
1371
-     *                                      can indicate just the columns you
1372
-     *                                      want and a single array indexed by
1373
-     *                                      the columns will be returned.
1374
-     * @return EE_Base_Class|null|array()
1375
-     * @throws EE_Error
1376
-     */
1377
-    public function previous(
1378
-        $current_field_value,
1379
-        $field_to_order_by = null,
1380
-        $query_params = [],
1381
-        $columns_to_select = null
1382
-    ) {
1383
-        $results = $this->_get_consecutive(
1384
-            $current_field_value,
1385
-            '<',
1386
-            $field_to_order_by,
1387
-            1,
1388
-            $query_params,
1389
-            $columns_to_select
1390
-        );
1391
-        return empty($results) ? null : reset($results);
1392
-    }
1393
-
1394
-
1395
-    /**
1396
-     * Returns the a consecutive number of items in sequence from the given
1397
-     * value as found in the database matching the given query conditions.
1398
-     *
1399
-     * @param mixed  $current_field_value   Value used for the reference point.
1400
-     * @param string $operand               What operand is used for the sequence.
1401
-     * @param string $field_to_order_by     What field is used for the reference point.
1402
-     * @param int    $limit                 How many to return.
1403
-     * @param array  $query_params          Extra conditions on the query.
1404
-     * @param null   $columns_to_select     If left null, then an array of EE_Base_Class objects is returned,
1405
-     *                                      otherwise you can indicate just the columns you want returned.
1406
-     * @return EE_Base_Class[]|array
1407
-     * @throws EE_Error
1408
-     */
1409
-    protected function _get_consecutive(
1410
-        $current_field_value,
1411
-        $operand = '>',
1412
-        $field_to_order_by = null,
1413
-        $limit = 1,
1414
-        $query_params = [],
1415
-        $columns_to_select = null
1416
-    ) {
1417
-        // if $field_to_order_by is empty then let's assume we're ordering by the primary key.
1418
-        if (empty($field_to_order_by)) {
1419
-            if ($this->has_primary_key_field()) {
1420
-                $field_to_order_by = $this->get_primary_key_field()->get_name();
1421
-            } else {
1422
-                if (WP_DEBUG) {
1423
-                    throw new EE_Error(
1424
-                        esc_html__(
1425
-                            '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).',
1426
-                            'event_espresso'
1427
-                        )
1428
-                    );
1429
-                }
1430
-                EE_Error::add_error(
1431
-                    esc_html__('There was an error with the query.', 'event_espresso'),
1432
-                    __FILE__,
1433
-                    __FUNCTION__,
1434
-                    __LINE__
1435
-                );
1436
-                return [];
1437
-            }
1438
-        }
1439
-        if (! is_array($query_params)) {
1440
-            EE_Error::doing_it_wrong(
1441
-                'EEM_Base::_get_consecutive',
1442
-                sprintf(
1443
-                    esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1444
-                    gettype($query_params)
1445
-                ),
1446
-                '4.6.0'
1447
-            );
1448
-            $query_params = [];
1449
-        }
1450
-        // let's add the where query param for consecutive look up.
1451
-        $query_params[0][ $field_to_order_by ] = [$operand, $current_field_value];
1452
-        $query_params['limit']                 = $limit;
1453
-        // set direction
1454
-        $incoming_orderby         = isset($query_params['order_by']) ? (array) $query_params['order_by'] : [];
1455
-        $query_params['order_by'] = $operand === '>'
1456
-            ? [$field_to_order_by => 'ASC'] + $incoming_orderby
1457
-            : [$field_to_order_by => 'DESC'] + $incoming_orderby;
1458
-        // if $columns_to_select is empty then that means we're returning EE_Base_Class objects
1459
-        if (empty($columns_to_select)) {
1460
-            return $this->get_all($query_params);
1461
-        }
1462
-        // getting just the fields
1463
-        return $this->_get_all_wpdb_results($query_params, ARRAY_A, $columns_to_select);
1464
-    }
1465
-
1466
-
1467
-    /**
1468
-     * This sets the _timezone property after model object has been instantiated.
1469
-     *
1470
-     * @param null | string $timezone valid PHP DateTimeZone timezone string
1471
-     */
1472
-    public function set_timezone($timezone)
1473
-    {
1474
-        if ($timezone !== null) {
1475
-            $this->_timezone = $timezone;
1476
-        }
1477
-        // note we need to loop through relations and set the timezone on those objects as well.
1478
-        foreach ($this->_model_relations as $relation) {
1479
-            $relation->set_timezone($timezone);
1480
-        }
1481
-        // and finally we do the same for any datetime fields
1482
-        foreach ($this->_fields as $field) {
1483
-            if ($field instanceof EE_Datetime_Field) {
1484
-                $field->set_timezone($timezone);
1485
-            }
1486
-        }
1487
-    }
1488
-
1489
-
1490
-    /**
1491
-     * This just returns whatever is set for the current timezone.
1492
-     *
1493
-     * @access public
1494
-     * @return string
1495
-     */
1496
-    public function get_timezone()
1497
-    {
1498
-        // first validate if timezone is set.  If not, then let's set it be whatever is set on the model fields.
1499
-        if (empty($this->_timezone)) {
1500
-            foreach ($this->_fields as $field) {
1501
-                if ($field instanceof EE_Datetime_Field) {
1502
-                    $this->set_timezone($field->get_timezone());
1503
-                    break;
1504
-                }
1505
-            }
1506
-        }
1507
-        // if timezone STILL empty then return the default timezone for the site.
1508
-        if (empty($this->_timezone)) {
1509
-            $this->set_timezone(EEH_DTT_Helper::get_timezone());
1510
-        }
1511
-        return $this->_timezone;
1512
-    }
1513
-
1514
-
1515
-    /**
1516
-     * This returns the date formats set for the given field name and also ensures that
1517
-     * $this->_timezone property is set correctly.
1518
-     *
1519
-     * @param string $field_name The name of the field the formats are being retrieved for.
1520
-     * @param bool   $pretty     Whether to return the pretty formats (true) or not (false).
1521
-     * @return array formats in an array with the date format first, and the time format last.
1522
-     * @throws EE_Error   If the given field_name is not of the EE_Datetime_Field type.
1523
-     * @since 4.6.x
1524
-     */
1525
-    public function get_formats_for($field_name, $pretty = false)
1526
-    {
1527
-        $field_settings = $this->field_settings_for($field_name);
1528
-        // if not a valid EE_Datetime_Field then throw error
1529
-        if (! $field_settings instanceof EE_Datetime_Field) {
1530
-            throw new EE_Error(
1531
-                sprintf(
1532
-                    esc_html__(
1533
-                        '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.',
1534
-                        'event_espresso'
1535
-                    ),
1536
-                    $field_name
1537
-                )
1538
-            );
1539
-        }
1540
-        // while we are here, let's make sure the timezone internally in EEM_Base matches what is stored on
1541
-        // the field.
1542
-        $this->_timezone = $field_settings->get_timezone();
1543
-        return [$field_settings->get_date_format($pretty), $field_settings->get_time_format($pretty)];
1544
-    }
1545
-
1546
-
1547
-    /**
1548
-     * This returns the current time in a format setup for a query on this model.
1549
-     * Usage of this method makes it easier to setup queries against EE_Datetime_Field columns because
1550
-     * it will return:
1551
-     *  - a formatted string in the timezone and format currently set on the EE_Datetime_Field for the given field for
1552
-     *  NOW
1553
-     *  - or a unix timestamp (equivalent to time())
1554
-     * Note: When requesting a formatted string, if the date or time format doesn't include seconds, for example,
1555
-     * the time returned, because it uses that format, will also NOT include seconds. For this reason, if you want
1556
-     * the time returned to be the current time down to the exact second, set $timestamp to true.
1557
-     *
1558
-     * @param string $field_name       The field the current time is needed for.
1559
-     * @param bool   $timestamp        True means to return a unix timestamp. Otherwise a
1560
-     *                                 formatted string matching the set format for the field in the set timezone will
1561
-     *                                 be returned.
1562
-     * @param string $what             Whether to return the string in just the time format, the date format, or both.
1563
-     * @return int|string  If the given field_name is not of the EE_Datetime_Field type, then an EE_Error
1564
-     *                                 exception is triggered.
1565
-     * @throws EE_Error    If the given field_name is not of the EE_Datetime_Field type.
1566
-     * @throws Exception
1567
-     * @since 4.6.x
1568
-     */
1569
-    public function current_time_for_query($field_name, $timestamp = false, $what = 'both')
1570
-    {
1571
-        $formats  = $this->get_formats_for($field_name);
1572
-        $DateTime = new DateTime("now", new DateTimeZone($this->_timezone));
1573
-        if ($timestamp) {
1574
-            return $DateTime->format('U');
1575
-        }
1576
-        // not returning timestamp, so return formatted string in timezone.
1577
-        switch ($what) {
1578
-            case 'time':
1579
-                return $DateTime->format($formats[1]);
1580
-            case 'date':
1581
-                return $DateTime->format($formats[0]);
1582
-            default:
1583
-                return $DateTime->format(implode(' ', $formats));
1584
-        }
1585
-    }
1586
-
1587
-
1588
-    /**
1589
-     * This receives a time string for a given field and ensures that it is setup to match what the internal settings
1590
-     * for the model are.  Returns a DateTime object.
1591
-     * Note: a gotcha for when you send in unix timestamp.  Remember a unix timestamp is already timezone agnostic,
1592
-     * (functionally the equivalent of UTC+0).  So when you send it in, whatever timezone string you include is
1593
-     * ignored.
1594
-     *
1595
-     * @param string $field_name      The field being setup.
1596
-     * @param string $timestring      The date time string being used.
1597
-     * @param string $incoming_format The format for the time string.
1598
-     * @param string $timezone        By default, it is assumed the incoming time string is in timezone for
1599
-     *                                the blog.  If this is not the case, then it can be specified here.  If incoming
1600
-     *                                format is
1601
-     *                                'U', this is ignored.
1602
-     * @return DateTime
1603
-     * @throws EE_Error
1604
-     */
1605
-    public function convert_datetime_for_query($field_name, $timestring, $incoming_format, $timezone = '')
1606
-    {
1607
-        // just using this to ensure the timezone is set correctly internally
1608
-        $this->get_formats_for($field_name);
1609
-        // load EEH_DTT_Helper
1610
-        $set_timezone     = empty($timezone) ? EEH_DTT_Helper::get_timezone() : $timezone;
1611
-        $incomingDateTime = date_create_from_format($incoming_format, $timestring, new DateTimeZone($set_timezone));
1612
-        EEH_DTT_Helper::setTimezone($incomingDateTime, new DateTimeZone($this->_timezone));
1613
-        return \EventEspresso\core\domain\entities\DbSafeDateTime::createFromDateTime($incomingDateTime);
1614
-    }
1615
-
1616
-
1617
-    /**
1618
-     * Gets all the tables comprising this model. Array keys are the table aliases, and values are EE_Table objects
1619
-     *
1620
-     * @return EE_Table_Base[]
1621
-     */
1622
-    public function get_tables()
1623
-    {
1624
-        return $this->_tables;
1625
-    }
1626
-
1627
-
1628
-    /**
1629
-     * Updates all the database entries (in each table for this model) according to $fields_n_values and optionally
1630
-     * also updates all the model objects, where the criteria expressed in $query_params are met..
1631
-     * Also note: if this model has multiple tables, this update verifies all the secondary tables have an entry for
1632
-     * each row (in the primary table) we're trying to update; if not, it inserts an entry in the secondary table. Eg:
1633
-     * if our model has 2 tables: wp_posts (primary), and wp_esp_event (secondary). Let's say we are trying to update a
1634
-     * model object with EVT_ID = 1
1635
-     * (which means where wp_posts has ID = 1, because wp_posts.ID is the primary key's column), which exists, but
1636
-     * there is no entry in wp_esp_event for this entry in wp_posts. So, this update script will insert a row into
1637
-     * wp_esp_event, using any available parameters from $fields_n_values (eg, if "EVT_limit" => 40 is in
1638
-     * $fields_n_values, the new entry in wp_esp_event will set EVT_limit = 40, and use default for other columns which
1639
-     * are not specified)
1640
-     *
1641
-     * @param array   $fields_n_values         keys are model fields (exactly like keys in EEM_Base::_fields, NOT db
1642
-     *                                         columns!), values are strings, ints, floats, and maybe arrays if they
1643
-     *                                         are to be serialized. Basically, the values are what you'd expect to be
1644
-     *                                         values on the model, NOT necessarily what's in the DB. For example, if
1645
-     *                                         we wanted to update only the TXN_details on any Transactions where its
1646
-     *                                         ID=34, we'd use this method as follows:
1647
-     *                                         EEM_Transaction::instance()->update(
1648
-     *                                         array('TXN_details'=>array('detail1'=>'monkey','detail2'=>'banana'),
1649
-     *                                         array(array('TXN_ID'=>34)));
1650
-     * @param array   $query_params            @see
1651
-     *                                         https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1652
-     *                                         Eg, consider updating Question's QST_admin_label field is of type
1653
-     *                                         Simple_HTML. If you use this function to update that field to $new_value
1654
-     *                                         = (note replace 8's with appropriate opening and closing tags in the
1655
-     *                                         following example)"8script8alert('I hack all');8/script88b8boom
1656
-     *                                         baby8/b8", then if you set $values_already_prepared_by_model_object to
1657
-     *                                         TRUE, it is assumed that you've already called
1658
-     *                                         EE_Simple_HTML_Field->prepare_for_set($new_value), which removes the
1659
-     *                                         malicious javascript. However, if
1660
-     *                                         $values_already_prepared_by_model_object is left as FALSE, then
1661
-     *                                         EE_Simple_HTML_Field->prepare_for_set($new_value) will be called on it,
1662
-     *                                         and every other field, before insertion. We provide this parameter
1663
-     *                                         because model objects perform their prepare_for_set function on all
1664
-     *                                         their values, and so don't need to be called again (and in many cases,
1665
-     *                                         shouldn't be called again. Eg: if we escape HTML characters in the
1666
-     *                                         prepare_for_set method...)
1667
-     * @param boolean $keep_model_objs_in_sync if TRUE, makes sure we ALSO update model objects
1668
-     *                                         in this model's entity map according to $fields_n_values that match
1669
-     *                                         $query_params. This obviously has some overhead, so you can disable it
1670
-     *                                         by setting this to FALSE, but be aware that model objects being used
1671
-     *                                         could get out-of-sync with the database
1672
-     * @return int how many rows got updated or FALSE if something went wrong with the query (wp returns FALSE or num
1673
-     *                                         rows affected which *could* include 0 which DOES NOT mean the query was
1674
-     *                                         bad)
1675
-     * @throws EE_Error
1676
-     * @throws ReflectionException
1677
-     */
1678
-    public function update($fields_n_values, $query_params, $keep_model_objs_in_sync = true)
1679
-    {
1680
-        if (! is_array($query_params)) {
1681
-            EE_Error::doing_it_wrong(
1682
-                'EEM_Base::update',
1683
-                sprintf(
1684
-                    esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1685
-                    gettype($query_params)
1686
-                ),
1687
-                '4.6.0'
1688
-            );
1689
-            $query_params = [];
1690
-        }
1691
-        /**
1692
-         * Action called before a model update call has been made.
1693
-         *
1694
-         * @param EEM_Base $model
1695
-         * @param array    $fields_n_values the updated fields and their new values
1696
-         * @param array    $query_params    @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1697
-         */
1698
-        do_action('AHEE__EEM_Base__update__begin', $this, $fields_n_values, $query_params);
1699
-        /**
1700
-         * Filters the fields about to be updated given the query parameters. You can provide the
1701
-         * $query_params to $this->get_all() to find exactly which records will be updated
1702
-         *
1703
-         * @param array    $fields_n_values fields and their new values
1704
-         * @param EEM_Base $model           the model being queried
1705
-         * @param array    $query_params    @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1706
-         */
1707
-        $fields_n_values = (array) apply_filters(
1708
-            'FHEE__EEM_Base__update__fields_n_values',
1709
-            $fields_n_values,
1710
-            $this,
1711
-            $query_params
1712
-        );
1713
-        // need to verify that, for any entry we want to update, there are entries in each secondary table.
1714
-        // to do that, for each table, verify that it's PK isn't null.
1715
-        $tables = $this->get_tables();
1716
-        // 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
1717
-        // NOTE: we should make this code more efficient by NOT querying twice
1718
-        // before the real update, but that needs to first go through ALPHA testing
1719
-        // as it's dangerous. says Mike August 8 2014
1720
-        // we want to make sure the default_where strategy is ignored
1721
-        $this->_ignore_where_strategy = true;
1722
-        $wpdb_select_results          = $this->_get_all_wpdb_results($query_params);
1723
-        foreach ($wpdb_select_results as $wpdb_result) {
1724
-            // type cast stdClass as array
1725
-            $wpdb_result = (array) $wpdb_result;
1726
-            // get the model object's PK, as we'll want this if we need to insert a row into secondary tables
1727
-            if ($this->has_primary_key_field()) {
1728
-                $main_table_pk_value = $wpdb_result[ $this->get_primary_key_field()->get_qualified_column() ];
1729
-            } else {
1730
-                // 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)
1731
-                $main_table_pk_value = null;
1732
-            }
1733
-            // 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
1734
-            // 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
1735
-            if (count($tables) > 1) {
1736
-                // foreach matching row in the DB, ensure that each table's PK isn't null. If so, there must not be an entry
1737
-                // in that table, and so we'll want to insert one
1738
-                foreach ($tables as $table_obj) {
1739
-                    $this_table_pk_column = $table_obj->get_fully_qualified_pk_column();
1740
-                    // if there is no private key for this table on the results, it means there's no entry
1741
-                    // in this table, right? so insert a row in the current table, using any fields available
1742
-                    if (
1743
-                        ! (array_key_exists($this_table_pk_column, $wpdb_result)
1744
-                           && $wpdb_result[ $this_table_pk_column ])
1745
-                    ) {
1746
-                        $success = $this->_insert_into_specific_table(
1747
-                            $table_obj,
1748
-                            $fields_n_values,
1749
-                            $main_table_pk_value
1750
-                        );
1751
-                        // if we died here, report the error
1752
-                        if (! $success) {
1753
-                            return false;
1754
-                        }
1755
-                    }
1756
-                }
1757
-            }
1758
-            //              //and now check that if we have cached any models by that ID on the model, that
1759
-            //              //they also get updated properly
1760
-            //              $model_object = $this->get_from_entity_map( $main_table_pk_value );
1761
-            //              if( $model_object ){
1762
-            //                  foreach( $fields_n_values as $field => $value ){
1763
-            //                      $model_object->set($field, $value);
1764
-            // let's make sure default_where strategy is followed now
1765
-            $this->_ignore_where_strategy = false;
1766
-        }
1767
-        // if we want to keep model objects in sync, AND
1768
-        // if this wasn't called from a model object (to update itself)
1769
-        // then we want to make sure we keep all the existing
1770
-        // model objects in sync with the db
1771
-        if ($keep_model_objs_in_sync && ! $this->_values_already_prepared_by_model_object) {
1772
-            if ($this->has_primary_key_field()) {
1773
-                $model_objs_affected_ids = $this->get_col($query_params);
1774
-            } else {
1775
-                // we need to select a bunch of columns and then combine them into the the "index primary key string"s
1776
-                $models_affected_key_columns = $this->_get_all_wpdb_results($query_params, ARRAY_A);
1777
-                $model_objs_affected_ids     = [];
1778
-                foreach ($models_affected_key_columns as $row) {
1779
-                    $combined_index_key                             = $this->get_index_primary_key_string($row);
1780
-                    $model_objs_affected_ids[ $combined_index_key ] = $combined_index_key;
1781
-                }
1782
-            }
1783
-            if (! $model_objs_affected_ids) {
1784
-                // wait wait wait- if nothing was affected let's stop here
1785
-                return 0;
1786
-            }
1787
-            foreach ($model_objs_affected_ids as $id) {
1788
-                $model_obj_in_entity_map = $this->get_from_entity_map($id);
1789
-                if ($model_obj_in_entity_map) {
1790
-                    foreach ($fields_n_values as $field => $new_value) {
1791
-                        $model_obj_in_entity_map->set($field, $new_value);
1792
-                    }
1793
-                }
1794
-            }
1795
-            // if there is a primary key on this model, we can now do a slight optimization
1796
-            if ($this->has_primary_key_field()) {
1797
-                // we already know what we want to update. So let's make the query simpler so it's a little more efficient
1798
-                $query_params = [
1799
-                    [$this->primary_key_name() => ['IN', $model_objs_affected_ids]],
1800
-                    'limit'                    => count($model_objs_affected_ids),
1801
-                    'default_where_conditions' => EEM_Base::default_where_conditions_none,
1802
-                ];
1803
-            }
1804
-        }
1805
-        $model_query_info = $this->_create_model_query_info_carrier($query_params);
1806
-        $SQL              = "UPDATE "
1807
-                            . $model_query_info->get_full_join_sql()
1808
-                            . " SET "
1809
-                            . $this->_construct_update_sql($fields_n_values)
1810
-                            . $model_query_info->get_where_sql(
1811
-                            );// note: doesn't use _construct_2nd_half_of_select_query() because doesn't accept LIMIT, ORDER BY, etc.
1812
-        $rows_affected    = $this->_do_wpdb_query('query', [$SQL]);
1813
-        /**
1814
-         * Action called after a model update call has been made.
1815
-         *
1816
-         * @param EEM_Base $model
1817
-         * @param array    $fields_n_values the updated fields and their new values
1818
-         * @param array    $query_params    @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1819
-         * @param int      $rows_affected
1820
-         */
1821
-        do_action('AHEE__EEM_Base__update__end', $this, $fields_n_values, $query_params, $rows_affected);
1822
-        return $rows_affected;// how many supposedly got updated
1823
-    }
1824
-
1825
-
1826
-    /**
1827
-     * Analogous to $wpdb->get_col, returns a 1-dimensional array where teh values
1828
-     * are teh values of the field specified (or by default the primary key field)
1829
-     * that matched the query params. Note that you should pass the name of the
1830
-     * model FIELD, not the database table's column name.
1831
-     *
1832
-     * @param array  $query_params @see
1833
-     *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1834
-     * @param string $field_to_select
1835
-     * @return array just like $wpdb->get_col()
1836
-     * @throws EE_Error
1837
-     */
1838
-    public function get_col($query_params = [], $field_to_select = null)
1839
-    {
1840
-        if ($field_to_select) {
1841
-            $field = $this->field_settings_for($field_to_select);
1842
-        } elseif ($this->has_primary_key_field()) {
1843
-            $field = $this->get_primary_key_field();
1844
-        } else {
1845
-            $field_settings = $this->field_settings();
1846
-            // no primary key, just grab the first column
1847
-            $field = reset($field_settings);
1848
-            // don't need this array now
1849
-            unset($field_settings);
1850
-        }
1851
-        $model_query_info   = $this->_create_model_query_info_carrier($query_params);
1852
-        $select_expressions = $field->get_qualified_column();
1853
-        $SQL                =
1854
-            "SELECT $select_expressions " . $this->_construct_2nd_half_of_select_query($model_query_info);
1855
-        return $this->_do_wpdb_query('get_col', [$SQL]);
1856
-    }
1857
-
1858
-
1859
-    /**
1860
-     * Returns a single column value for a single row from the database
1861
-     *
1862
-     * @param array  $query_params    @see
1863
-     *                                https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1864
-     * @param string $field_to_select @see EEM_Base::get_col()
1865
-     * @return string
1866
-     * @throws EE_Error
1867
-     */
1868
-    public function get_var($query_params = [], $field_to_select = null)
1869
-    {
1870
-        $query_params['limit'] = 1;
1871
-        $col                   = $this->get_col($query_params, $field_to_select);
1872
-        if (! empty($col)) {
1873
-            return reset($col);
1874
-        }
1875
-        return null;
1876
-    }
1877
-
1878
-
1879
-    /**
1880
-     * Makes the SQL for after "UPDATE table_X inner join table_Y..." and before "...WHERE". Eg "Question.name='party
1881
-     * time?', Question.desc='what do you think?',..." Values are filtered through wpdb->prepare to avoid against SQL
1882
-     * injection, but currently no further filtering is done
1883
-     *
1884
-     * @param array $fields_n_values array keys are field names on this model, and values are what those fields should
1885
-     *                               be updated to in the DB
1886
-     * @return string of SQL
1887
-     * @throws EE_Error
1888
-     * @global      $wpdb
1889
-     */
1890
-    public function _construct_update_sql($fields_n_values)
1891
-    {
1892
-        /** @type WPDB $wpdb */
1893
-        global $wpdb;
1894
-        $cols_n_values = [];
1895
-        foreach ($fields_n_values as $field_name => $value) {
1896
-            $field_obj = $this->field_settings_for($field_name);
1897
-            // if the value is NULL, we want to assign the value to that.
1898
-            // wpdb->prepare doesn't really handle that properly
1899
-            $prepared_value  = $this->_prepare_value_or_use_default($field_obj, $fields_n_values);
1900
-            $value_sql       = $prepared_value === null ? 'NULL'
1901
-                : $wpdb->prepare($field_obj->get_wpdb_data_type(), $prepared_value);
1902
-            $cols_n_values[] = $field_obj->get_qualified_column() . "=" . $value_sql;
1903
-        }
1904
-        return implode(",", $cols_n_values);
1905
-    }
1906
-
1907
-
1908
-    /**
1909
-     * Deletes a single row from the DB given the model object's primary key value. (eg, EE_Attendee->ID()'s value).
1910
-     * Performs a HARD delete, meaning the database row should always be removed,
1911
-     * not just have a flag field on it switched
1912
-     * Wrapper for EEM_Base::delete_permanently()
1913
-     *
1914
-     * @param mixed   $id
1915
-     * @param boolean $allow_blocking
1916
-     * @return int the number of rows deleted
1917
-     * @throws EE_Error
1918
-     * @throws ReflectionException
1919
-     */
1920
-    public function delete_permanently_by_ID($id, $allow_blocking = true)
1921
-    {
1922
-        return $this->delete_permanently(
1923
-            [
1924
-                [$this->get_primary_key_field()->get_name() => $id],
1925
-                'limit' => 1,
1926
-            ],
1927
-            $allow_blocking
1928
-        );
1929
-    }
1930
-
1931
-
1932
-    /**
1933
-     * Deletes a single row from the DB given the model object's primary key value. (eg, EE_Attendee->ID()'s value).
1934
-     * Wrapper for EEM_Base::delete()
1935
-     *
1936
-     * @param mixed   $id
1937
-     * @param boolean $allow_blocking
1938
-     * @return int the number of rows deleted
1939
-     * @throws EE_Error
1940
-     */
1941
-    public function delete_by_ID($id, $allow_blocking = true)
1942
-    {
1943
-        return $this->delete(
1944
-            [
1945
-                [$this->get_primary_key_field()->get_name() => $id],
1946
-                'limit' => 1,
1947
-            ],
1948
-            $allow_blocking
1949
-        );
1950
-    }
1951
-
1952
-
1953
-    /**
1954
-     * Identical to delete_permanently, but does a "soft" delete if possible,
1955
-     * meaning if the model has a field that indicates its been "trashed" or
1956
-     * "soft deleted", we will just set that instead of actually deleting the rows.
1957
-     *
1958
-     * @param array   $query_params
1959
-     * @param boolean $allow_blocking
1960
-     * @return int how many rows got deleted
1961
-     * @throws EE_Error
1962
-     * @throws ReflectionException
1963
-     * @see EEM_Base::delete_permanently
1964
-     */
1965
-    public function delete($query_params, $allow_blocking = true)
1966
-    {
1967
-        return $this->delete_permanently($query_params, $allow_blocking);
1968
-    }
1969
-
1970
-
1971
-    /**
1972
-     * Deletes the model objects that meet the query params. Note: this method is overridden
1973
-     * in EEM_Soft_Delete_Base so that soft-deleted model objects are instead only flagged
1974
-     * as archived, not actually deleted
1975
-     *
1976
-     * @param array   $query_params   @see
1977
-     *                                https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1978
-     * @param boolean $allow_blocking if TRUE, matched objects will only be deleted if there is no related model info
1979
-     *                                that blocks it (ie, there' sno other data that depends on this data); if false,
1980
-     *                                deletes regardless of other objects which may depend on it. Its generally
1981
-     *                                advisable to always leave this as TRUE, otherwise you could easily corrupt your
1982
-     *                                DB
1983
-     * @return int how many rows got deleted
1984
-     * @throws EE_Error
1985
-     * @throws ReflectionException
1986
-     */
1987
-    public function delete_permanently($query_params, $allow_blocking = true)
1988
-    {
1989
-        /**
1990
-         * Action called just before performing a real deletion query. You can use the
1991
-         * model and its $query_params to find exactly which items will be deleted
1992
-         *
1993
-         * @param EEM_Base $model
1994
-         * @param array    $query_params   @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1995
-         * @param boolean  $allow_blocking whether or not to allow related model objects
1996
-         *                                 to block (prevent) this deletion
1997
-         */
1998
-        do_action('AHEE__EEM_Base__delete__begin', $this, $query_params, $allow_blocking);
1999
-        // some MySQL databases may be running safe mode, which may restrict
2000
-        // deletion if there is no KEY column used in the WHERE statement of a deletion.
2001
-        // to get around this, we first do a SELECT, get all the IDs, and then run another query
2002
-        // to delete them
2003
-        $items_for_deletion           = $this->_get_all_wpdb_results($query_params);
2004
-        $columns_and_ids_for_deleting = $this->_get_ids_for_delete($items_for_deletion, $allow_blocking);
2005
-        $deletion_where_query_part    = $this->_build_query_part_for_deleting_from_columns_and_values(
2006
-            $columns_and_ids_for_deleting
2007
-        );
2008
-        /**
2009
-         * Allows client code to act on the items being deleted before the query is actually executed.
2010
-         *
2011
-         * @param EEM_Base $this                            The model instance being acted on.
2012
-         * @param array    $query_params                    The incoming array of query parameters influencing what gets deleted.
2013
-         * @param bool     $allow_blocking                  @see param description in method phpdoc block.
2014
-         * @param array    $columns_and_ids_for_deleting    An array indicating what entities will get removed as
2015
-         *                                                  derived from the incoming query parameters.
2016
-         * @see details on the structure of this array in the phpdocs
2017
-         *                                                  for the `_get_ids_for_delete_method`
2018
-         *
2019
-         */
2020
-        do_action(
2021
-            'AHEE__EEM_Base__delete__before_query',
2022
-            $this,
2023
-            $query_params,
2024
-            $allow_blocking,
2025
-            $columns_and_ids_for_deleting
2026
-        );
2027
-        if ($deletion_where_query_part) {
2028
-            $model_query_info = $this->_create_model_query_info_carrier($query_params);
2029
-            $table_aliases    = array_keys($this->_tables);
2030
-            $SQL              = "DELETE "
2031
-                                . implode(", ", $table_aliases)
2032
-                                . " FROM "
2033
-                                . $model_query_info->get_full_join_sql()
2034
-                                . " WHERE "
2035
-                                . $deletion_where_query_part;
2036
-            $rows_deleted     = $this->_do_wpdb_query('query', [$SQL]);
2037
-        } else {
2038
-            $rows_deleted = 0;
2039
-        }
2040
-
2041
-        // Next, make sure those items are removed from the entity map; if they could be put into it at all; and if
2042
-        // there was no error with the delete query.
2043
-        if (
2044
-            $this->has_primary_key_field()
2045
-            && $rows_deleted !== false
2046
-            && isset($columns_and_ids_for_deleting[ $this->get_primary_key_field()->get_qualified_column() ])
2047
-        ) {
2048
-            $ids_for_removal = $columns_and_ids_for_deleting[ $this->get_primary_key_field()->get_qualified_column() ];
2049
-            foreach ($ids_for_removal as $id) {
2050
-                if (isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])) {
2051
-                    unset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ]);
2052
-                }
2053
-            }
2054
-
2055
-            // delete any extra meta attached to the deleted entities but ONLY if this model is not an instance of
2056
-            // `EEM_Extra_Meta`.  In other words we want to prevent recursion on EEM_Extra_Meta::delete_permanently calls
2057
-            // unnecessarily.  It's very unlikely that users will have assigned Extra Meta to Extra Meta
2058
-            // (although it is possible).
2059
-            // Note this can be skipped by using the provided filter and returning false.
2060
-            if (
2061
-                apply_filters(
2062
-                    'FHEE__EEM_Base__delete_permanently__dont_delete_extra_meta_for_extra_meta',
2063
-                    ! $this instanceof EEM_Extra_Meta,
2064
-                    $this
2065
-                )
2066
-            ) {
2067
-                EEM_Extra_Meta::instance()->delete_permanently([
2068
-                                                                   0 => [
2069
-                                                                       'EXM_type' => $this->get_this_model_name(),
2070
-                                                                       'OBJ_ID'   => [
2071
-                                                                           'IN',
2072
-                                                                           $ids_for_removal,
2073
-                                                                       ],
2074
-                                                                   ],
2075
-                                                               ]);
2076
-            }
2077
-        }
2078
-
2079
-        /**
2080
-         * Action called just after performing a real deletion query. Although at this point the
2081
-         * items should have been deleted
2082
-         *
2083
-         * @param EEM_Base $model
2084
-         * @param array    $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2085
-         * @param int      $rows_deleted
2086
-         */
2087
-        do_action('AHEE__EEM_Base__delete__end', $this, $query_params, $rows_deleted, $columns_and_ids_for_deleting);
2088
-        return $rows_deleted;// how many supposedly got deleted
2089
-    }
2090
-
2091
-
2092
-    /**
2093
-     * Checks all the relations that throw error messages when there are blocking related objects
2094
-     * for related model objects. If there are any related model objects on those relations,
2095
-     * adds an EE_Error, and return true
2096
-     *
2097
-     * @param EE_Base_Class|int $this_model_obj_or_id
2098
-     * @param EE_Base_Class     $ignore_this_model_obj a model object like 'EE_Event', or 'EE_Term_Taxonomy', which
2099
-     *                                                 should be ignored when determining whether there are related
2100
-     *                                                 model objects which block this model object's deletion. Useful
2101
-     *                                                 if you know A is related to B and are considering deleting A,
2102
-     *                                                 but want to see if A has any other objects blocking its deletion
2103
-     *                                                 before removing the relation between A and B
2104
-     * @return boolean
2105
-     * @throws EE_Error
2106
-     * @throws ReflectionException
2107
-     */
2108
-    public function delete_is_blocked_by_related_models($this_model_obj_or_id, $ignore_this_model_obj = null)
2109
-    {
2110
-        // first, if $ignore_this_model_obj was supplied, get its model
2111
-        if ($ignore_this_model_obj && $ignore_this_model_obj instanceof EE_Base_Class) {
2112
-            $ignored_model = $ignore_this_model_obj->get_model();
2113
-        } else {
2114
-            $ignored_model = null;
2115
-        }
2116
-        // now check all the relations of $this_model_obj_or_id and see if there
2117
-        // are any related model objects blocking it?
2118
-        $is_blocked = false;
2119
-        foreach ($this->_model_relations as $relation_name => $relation_obj) {
2120
-            if ($relation_obj->block_delete_if_related_models_exist()) {
2121
-                // if $ignore_this_model_obj was supplied, then for the query
2122
-                // on that model needs to be told to ignore $ignore_this_model_obj
2123
-                if ($ignored_model && $relation_name === $ignored_model->get_this_model_name()) {
2124
-                    $related_model_objects = $relation_obj->get_all_related($this_model_obj_or_id, [
2125
-                        [
2126
-                            $ignored_model->get_primary_key_field()->get_name() => [
2127
-                                '!=',
2128
-                                $ignore_this_model_obj->ID(),
2129
-                            ],
2130
-                        ],
2131
-                    ]);
2132
-                } else {
2133
-                    $related_model_objects = $relation_obj->get_all_related($this_model_obj_or_id);
2134
-                }
2135
-                if ($related_model_objects) {
2136
-                    EE_Error::add_error($relation_obj->get_deletion_error_message(), __FILE__, __FUNCTION__, __LINE__);
2137
-                    $is_blocked = true;
2138
-                }
2139
-            }
2140
-        }
2141
-        return $is_blocked;
2142
-    }
2143
-
2144
-
2145
-    /**
2146
-     * Builds the columns and values for items to delete from the incoming $row_results_for_deleting array.
2147
-     *
2148
-     * @param array $row_results_for_deleting
2149
-     * @param bool  $allow_blocking
2150
-     * @return array   The shape of this array depends on whether the model `has_primary_key_field` or not.  If the
2151
-     *                              model DOES have a primary_key_field, then the array will be a simple single
2152
-     *                              dimension array where the key is the fully qualified primary key column and the
2153
-     *                              value is an array of ids that will be deleted. Example: array('Event.EVT_ID' =>
2154
-     *                              array( 1,2,3)) If the model DOES NOT have a primary_key_field, then the array will
2155
-     *                              be a two dimensional array where each element is a group of columns and values that
2156
-     *                              get deleted. Example: array(
2157
-     *                              0 => array(
2158
-     *                              'Term_Relationship.object_id' => 1
2159
-     *                              'Term_Relationship.term_taxonomy_id' => 5
2160
-     *                              ),
2161
-     *                              1 => array(
2162
-     *                              'Term_Relationship.object_id' => 1
2163
-     *                              'Term_Relationship.term_taxonomy_id' => 6
2164
-     *                              )
2165
-     *                              )
2166
-     * @throws EE_Error
2167
-     * @throws ReflectionException
2168
-     */
2169
-    protected function _get_ids_for_delete(array $row_results_for_deleting, $allow_blocking = true)
2170
-    {
2171
-        $ids_to_delete_indexed_by_column = [];
2172
-        if ($this->has_primary_key_field()) {
2173
-            $primary_table                   = $this->_get_main_table();
2174
-            $primary_table_pk_field          =
2175
-                $this->get_field_by_column($primary_table->get_fully_qualified_pk_column());
2176
-            $other_tables                    = $this->_get_other_tables();
2177
-            $ids_to_delete_indexed_by_column = $query = [];
2178
-            foreach ($row_results_for_deleting as $item_to_delete) {
2179
-                // before we mark this item for deletion,
2180
-                // make sure there's no related entities blocking its deletion (if we're checking)
2181
-                if (
2182
-                    $allow_blocking
2183
-                    && $this->delete_is_blocked_by_related_models(
2184
-                        $item_to_delete[ $primary_table->get_fully_qualified_pk_column() ]
2185
-                    )
2186
-                ) {
2187
-                    continue;
2188
-                }
2189
-                // primary table deletes
2190
-                if (isset($item_to_delete[ $primary_table->get_fully_qualified_pk_column() ])) {
2191
-                    $ids_to_delete_indexed_by_column[ $primary_table->get_fully_qualified_pk_column() ][] =
2192
-                        $item_to_delete[ $primary_table->get_fully_qualified_pk_column() ];
2193
-                }
2194
-            }
2195
-        } elseif (count($this->get_combined_primary_key_fields()) > 1) {
2196
-            $fields = $this->get_combined_primary_key_fields();
2197
-            foreach ($row_results_for_deleting as $item_to_delete) {
2198
-                $ids_to_delete_indexed_by_column_for_row = [];
2199
-                foreach ($fields as $cpk_field) {
2200
-                    if ($cpk_field instanceof EE_Model_Field_Base) {
2201
-                        $ids_to_delete_indexed_by_column_for_row[ $cpk_field->get_qualified_column() ] =
2202
-                            $item_to_delete[ $cpk_field->get_qualified_column() ];
2203
-                    }
2204
-                }
2205
-                $ids_to_delete_indexed_by_column[] = $ids_to_delete_indexed_by_column_for_row;
2206
-            }
2207
-        } else {
2208
-            // so there's no primary key and no combined key...
2209
-            // sorry, can't help you
2210
-            throw new EE_Error(
2211
-                sprintf(
2212
-                    esc_html__(
2213
-                        "Cannot delete objects of type %s because there is no primary key NOR combined key",
2214
-                        "event_espresso"
2215
-                    ),
2216
-                    get_class($this)
2217
-                )
2218
-            );
2219
-        }
2220
-        return $ids_to_delete_indexed_by_column;
2221
-    }
2222
-
2223
-
2224
-    /**
2225
-     * This receives an array of columns and values set to be deleted (as prepared by _get_ids_for_delete) and prepares
2226
-     * the corresponding query_part for the query performing the delete.
2227
-     *
2228
-     * @param array $ids_to_delete_indexed_by_column @see _get_ids_for_delete for how this array might be shaped.
2229
-     * @return string
2230
-     * @throws EE_Error
2231
-     */
2232
-    protected function _build_query_part_for_deleting_from_columns_and_values(array $ids_to_delete_indexed_by_column)
2233
-    {
2234
-        $query_part = '';
2235
-        if (empty($ids_to_delete_indexed_by_column)) {
2236
-            return $query_part;
2237
-        } elseif ($this->has_primary_key_field()) {
2238
-            $query = [];
2239
-            foreach ($ids_to_delete_indexed_by_column as $column => $ids) {
2240
-                $query[] = $column . ' IN' . $this->_construct_in_value($ids, $this->_primary_key_field);
2241
-            }
2242
-            $query_part = ! empty($query) ? implode(' AND ', $query) : $query_part;
2243
-        } elseif (count($this->get_combined_primary_key_fields()) > 1) {
2244
-            $ways_to_identify_a_row = [];
2245
-            foreach ($ids_to_delete_indexed_by_column as $ids_to_delete_indexed_by_column_for_each_row) {
2246
-                $values_for_each_combined_primary_key_for_a_row = [];
2247
-                foreach ($ids_to_delete_indexed_by_column_for_each_row as $column => $id) {
2248
-                    $values_for_each_combined_primary_key_for_a_row[] = $column . '=' . $id;
2249
-                }
2250
-                $ways_to_identify_a_row[] = '('
2251
-                                            . implode(' AND ', $values_for_each_combined_primary_key_for_a_row)
2252
-                                            . ')';
2253
-            }
2254
-            $query_part = implode(' OR ', $ways_to_identify_a_row);
2255
-        }
2256
-        return $query_part;
2257
-    }
2258
-
2259
-
2260
-    /**
2261
-     * Gets the model field by the fully qualified name
2262
-     *
2263
-     * @param string $qualified_column_name eg 'Event_CPT.post_name' or $field_obj->get_qualified_column()
2264
-     * @return EE_Model_Field_Base
2265
-     * @throws EE_Error
2266
-     * @throws EE_Error
2267
-     */
2268
-    public function get_field_by_column($qualified_column_name)
2269
-    {
2270
-        foreach ($this->field_settings(true) as $field_name => $field_obj) {
2271
-            if ($field_obj->get_qualified_column() === $qualified_column_name) {
2272
-                return $field_obj;
2273
-            }
2274
-        }
2275
-        throw new EE_Error(
2276
-            sprintf(
2277
-                esc_html__('Could not find a field on the model "%1$s" for qualified column "%2$s"', 'event_espresso'),
2278
-                $this->get_this_model_name(),
2279
-                $qualified_column_name
2280
-            )
2281
-        );
2282
-    }
2283
-
2284
-
2285
-    /**
2286
-     * Count all the rows that match criteria the model query params.
2287
-     * If $field_to_count isn't provided, the model's primary key is used. Otherwise, we count by field_to_count's
2288
-     * column
2289
-     *
2290
-     * @param array  $query_params   @see
2291
-     *                               https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2292
-     * @param string $field_to_count field on model to count by (not column name)
2293
-     * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2294
-     *                               that by the setting $distinct to TRUE;
2295
-     * @return int
2296
-     * @throws EE_Error
2297
-     */
2298
-    public function count($query_params = [], $field_to_count = null, $distinct = false)
2299
-    {
2300
-        $model_query_info = $this->_create_model_query_info_carrier($query_params);
2301
-        if ($field_to_count) {
2302
-            $field_obj       = $this->field_settings_for($field_to_count);
2303
-            $column_to_count = $field_obj->get_qualified_column();
2304
-        } elseif ($this->has_primary_key_field()) {
2305
-            $pk_field_obj    = $this->get_primary_key_field();
2306
-            $column_to_count = $pk_field_obj->get_qualified_column();
2307
-        } else {
2308
-            // there's no primary key
2309
-            // if we're counting distinct items, and there's no primary key,
2310
-            // we need to list out the columns for distinction;
2311
-            // otherwise we can just use star
2312
-            if ($distinct) {
2313
-                $columns_to_use = [];
2314
-                foreach ($this->get_combined_primary_key_fields() as $field_obj) {
2315
-                    $columns_to_use[] = $field_obj->get_qualified_column();
2316
-                }
2317
-                $column_to_count = implode(',', $columns_to_use);
2318
-            } else {
2319
-                $column_to_count = '*';
2320
-            }
2321
-        }
2322
-        $column_to_count = $distinct ? "DISTINCT " . $column_to_count : $column_to_count;
2323
-        $SQL             =
2324
-            "SELECT COUNT(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2325
-        return (int) $this->_do_wpdb_query('get_var', [$SQL]);
2326
-    }
2327
-
2328
-
2329
-    /**
2330
-     * Sums up the value of the $field_to_sum (defaults to the primary key, which isn't terribly useful)
2331
-     *
2332
-     * @param array  $query_params @see
2333
-     *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2334
-     * @param string $field_to_sum name of field (array key in $_fields array)
2335
-     * @return float
2336
-     * @throws EE_Error
2337
-     */
2338
-    public function sum($query_params, $field_to_sum = null)
2339
-    {
2340
-        $model_query_info = $this->_create_model_query_info_carrier($query_params);
2341
-        if ($field_to_sum) {
2342
-            $field_obj = $this->field_settings_for($field_to_sum);
2343
-        } else {
2344
-            $field_obj = $this->get_primary_key_field();
2345
-        }
2346
-        $column_to_count = $field_obj->get_qualified_column();
2347
-        $SQL             =
2348
-            "SELECT SUM(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2349
-        $return_value    = $this->_do_wpdb_query('get_var', [$SQL]);
2350
-        $data_type       = $field_obj->get_wpdb_data_type();
2351
-        if ($data_type === '%d' || $data_type === '%s') {
2352
-            return (float) $return_value;
2353
-        }
2354
-        // must be %f
2355
-        return (float) $return_value;
2356
-    }
2357
-
2358
-
2359
-    /**
2360
-     * Just calls the specified method on $wpdb with the given arguments
2361
-     * Consolidates a little extra error handling code
2362
-     *
2363
-     * @param string $wpdb_method
2364
-     * @param array  $arguments_to_provide
2365
-     * @return mixed
2366
-     * @throws EE_Error
2367
-     * @global wpdb  $wpdb
2368
-     */
2369
-    protected function _do_wpdb_query($wpdb_method, $arguments_to_provide)
2370
-    {
2371
-        // if we're in maintenance mode level 2, DON'T run any queries
2372
-        // because level 2 indicates the database needs updating and
2373
-        // is probably out of sync with the code
2374
-        if (! EE_Maintenance_Mode::instance()->models_can_query()) {
2375
-            throw new EE_Error(
2376
-                sprintf(
2377
-                    esc_html__(
2378
-                        "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.",
2379
-                        "event_espresso"
2380
-                    )
2381
-                )
2382
-            );
2383
-        }
2384
-        /** @type WPDB $wpdb */
2385
-        global $wpdb;
2386
-        if (! method_exists($wpdb, $wpdb_method)) {
2387
-            throw new EE_Error(
2388
-                sprintf(
2389
-                    esc_html__(
2390
-                        'There is no method named "%s" on Wordpress\' $wpdb object',
2391
-                        'event_espresso'
2392
-                    ),
2393
-                    $wpdb_method
2394
-                )
2395
-            );
2396
-        }
2397
-        if (WP_DEBUG) {
2398
-            $old_show_errors_value = $wpdb->show_errors;
2399
-            $wpdb->show_errors(false);
2400
-        }
2401
-        $result = $this->_process_wpdb_query($wpdb_method, $arguments_to_provide);
2402
-        $this->show_db_query_if_previously_requested($wpdb->last_query);
2403
-        if (WP_DEBUG) {
2404
-            $wpdb->show_errors($old_show_errors_value);
2405
-            if (! empty($wpdb->last_error)) {
2406
-                throw new EE_Error(sprintf(esc_html__('WPDB Error: "%s"', 'event_espresso'), $wpdb->last_error));
2407
-            }
2408
-            if ($result === false) {
2409
-                throw new EE_Error(
2410
-                    sprintf(
2411
-                        esc_html__(
2412
-                            'WPDB Error occurred, but no error message was logged by wpdb! The wpdb method called was "%1$s" and the arguments were "%2$s"',
2413
-                            'event_espresso'
2414
-                        ),
2415
-                        $wpdb_method,
2416
-                        var_export($arguments_to_provide, true)
2417
-                    )
2418
-                );
2419
-            }
2420
-        } elseif ($result === false) {
2421
-            EE_Error::add_error(
2422
-                sprintf(
2423
-                    esc_html__(
2424
-                        '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"',
2425
-                        'event_espresso'
2426
-                    ),
2427
-                    $wpdb_method,
2428
-                    var_export($arguments_to_provide, true),
2429
-                    $wpdb->last_error
2430
-                ),
2431
-                __FILE__,
2432
-                __FUNCTION__,
2433
-                __LINE__
2434
-            );
2435
-        }
2436
-        return $result;
2437
-    }
2438
-
2439
-
2440
-    /**
2441
-     * Attempts to run the indicated WPDB method with the provided arguments,
2442
-     * and if there's an error tries to verify the DB is correct. Uses
2443
-     * the static property EEM_Base::$_db_verification_level to determine whether
2444
-     * we should try to fix the EE core db, the addons, or just give up
2445
-     *
2446
-     * @param string $wpdb_method
2447
-     * @param array  $arguments_to_provide
2448
-     * @return mixed
2449
-     */
2450
-    private function _process_wpdb_query($wpdb_method, $arguments_to_provide)
2451
-    {
2452
-        /** @type WPDB $wpdb */
2453
-        global $wpdb;
2454
-        $wpdb->last_error = null;
2455
-        $result           = call_user_func_array([$wpdb, $wpdb_method], $arguments_to_provide);
2456
-        // was there an error running the query? but we don't care on new activations
2457
-        // (we're going to setup the DB anyway on new activations)
2458
-        if (
2459
-            ($result === false || ! empty($wpdb->last_error))
2460
-            && EE_System::instance()->detect_req_type() !== EE_System::req_type_new_activation
2461
-        ) {
2462
-            switch (EEM_Base::$_db_verification_level) {
2463
-                case EEM_Base::db_verified_none:
2464
-                    // let's double-check core's DB
2465
-                    $error_message = $this->_verify_core_db($wpdb_method, $arguments_to_provide);
2466
-                    break;
2467
-                case EEM_Base::db_verified_core:
2468
-                    // STILL NO LOVE?? verify all the addons too. Maybe they need to be fixed
2469
-                    $error_message = $this->_verify_addons_db($wpdb_method, $arguments_to_provide);
2470
-                    break;
2471
-                case EEM_Base::db_verified_addons:
2472
-                    // ummmm... you in trouble
2473
-                    return $result;
2474
-            }
2475
-            if (! empty($error_message)) {
2476
-                EE_Log::instance()->log(__FILE__, __FUNCTION__, $error_message, 'error');
2477
-                trigger_error($error_message);
2478
-            }
2479
-            return $this->_process_wpdb_query($wpdb_method, $arguments_to_provide);
2480
-        }
2481
-        return $result;
2482
-    }
2483
-
2484
-
2485
-    /**
2486
-     * Verifies the EE core database is up-to-date and records that we've done it on
2487
-     * EEM_Base::$_db_verification_level
2488
-     *
2489
-     * @param string $wpdb_method
2490
-     * @param array  $arguments_to_provide
2491
-     * @return string
2492
-     */
2493
-    private function _verify_core_db($wpdb_method, $arguments_to_provide)
2494
-    {
2495
-        /** @type WPDB $wpdb */
2496
-        global $wpdb;
2497
-        // ok remember that we've already attempted fixing the core db, in case the problem persists
2498
-        EEM_Base::$_db_verification_level = EEM_Base::db_verified_core;
2499
-        $error_message                    = sprintf(
2500
-            esc_html__(
2501
-                'WPDB Error "%1$s" while running wpdb method "%2$s" with arguments %3$s. Automatically attempting to fix EE Core DB',
2502
-                'event_espresso'
2503
-            ),
2504
-            $wpdb->last_error,
2505
-            $wpdb_method,
2506
-            wp_json_encode($arguments_to_provide)
2507
-        );
2508
-        EE_System::instance()->initialize_db_if_no_migrations_required(false, true);
2509
-        return $error_message;
2510
-    }
2511
-
2512
-
2513
-    /**
2514
-     * Verifies the EE addons' database is up-to-date and records that we've done it on
2515
-     * EEM_Base::$_db_verification_level
2516
-     *
2517
-     * @param $wpdb_method
2518
-     * @param $arguments_to_provide
2519
-     * @return string
2520
-     */
2521
-    private function _verify_addons_db($wpdb_method, $arguments_to_provide)
2522
-    {
2523
-        /** @type WPDB $wpdb */
2524
-        global $wpdb;
2525
-        // ok remember that we've already attempted fixing the addons dbs, in case the problem persists
2526
-        EEM_Base::$_db_verification_level = EEM_Base::db_verified_addons;
2527
-        $error_message                    = sprintf(
2528
-            esc_html__(
2529
-                'WPDB AGAIN: Error "%1$s" while running the same method and arguments as before. Automatically attempting to fix EE Addons DB',
2530
-                'event_espresso'
2531
-            ),
2532
-            $wpdb->last_error,
2533
-            $wpdb_method,
2534
-            wp_json_encode($arguments_to_provide)
2535
-        );
2536
-        EE_System::instance()->initialize_addons();
2537
-        return $error_message;
2538
-    }
2539
-
2540
-
2541
-    /**
2542
-     * In order to avoid repeating this code for the get_all, sum, and count functions, put the code parts
2543
-     * that are identical in here. Returns a string of SQL of everything in a SELECT query except the beginning
2544
-     * SELECT clause, eg " FROM wp_posts AS Event INNER JOIN ... WHERE ... ORDER BY ... LIMIT ... GROUP BY ... HAVING
2545
-     * ..."
2546
-     *
2547
-     * @param EE_Model_Query_Info_Carrier $model_query_info
2548
-     * @return string
2549
-     */
2550
-    private function _construct_2nd_half_of_select_query(EE_Model_Query_Info_Carrier $model_query_info)
2551
-    {
2552
-        return " FROM " . $model_query_info->get_full_join_sql() .
2553
-               $model_query_info->get_where_sql() .
2554
-               $model_query_info->get_group_by_sql() .
2555
-               $model_query_info->get_having_sql() .
2556
-               $model_query_info->get_order_by_sql() .
2557
-               $model_query_info->get_limit_sql();
2558
-    }
2559
-
2560
-
2561
-    /**
2562
-     * Set to easily debug the next X queries ran from this model.
2563
-     *
2564
-     * @param int $count
2565
-     */
2566
-    public function show_next_x_db_queries($count = 1)
2567
-    {
2568
-        $this->_show_next_x_db_queries = $count;
2569
-    }
2570
-
2571
-
2572
-    /**
2573
-     * @param $sql_query
2574
-     */
2575
-    public function show_db_query_if_previously_requested($sql_query)
2576
-    {
2577
-        if ($this->_show_next_x_db_queries > 0) {
2578
-            echo esc_html($sql_query);
2579
-            $this->_show_next_x_db_queries--;
2580
-        }
2581
-    }
2582
-
2583
-
2584
-    /**
2585
-     * Adds a relationship of the correct type between $modelObject and $otherModelObject.
2586
-     * There are the 3 cases:
2587
-     * 'belongsTo' relationship: sets $id_or_obj's foreign_key to be $other_model_id_or_obj's primary_key. If
2588
-     * $otherModelObject has no ID, it is first saved.
2589
-     * 'hasMany' relationship: sets $other_model_id_or_obj's foreign_key to be $id_or_obj's primary_key. If $id_or_obj
2590
-     * has no ID, it is first saved.
2591
-     * 'hasAndBelongsToMany' relationships: checks that there isn't already an entry in the join table, and adds one.
2592
-     * If one of the model Objects has not yet been saved to the database, it is saved before adding the entry in the
2593
-     * join table
2594
-     *
2595
-     * @param EE_Base_Class                     /int $thisModelObject
2596
-     * @param EE_Base_Class                     /int $id_or_obj EE_base_Class or ID of other Model Object
2597
-     * @param string $relationName                     , key in EEM_Base::_relations
2598
-     *                                                 an attendee to a group, you also want to specify which role they
2599
-     *                                                 will have in that group. So you would use this parameter to
2600
-     *                                                 specify array('role-column-name'=>'role-id')
2601
-     * @param array  $extra_join_model_fields_n_values This allows you to enter further query params for the relation
2602
-     *                                                 to for relation to methods that allow you to further specify
2603
-     *                                                 extra columns to join by (such as HABTM).  Keep in mind that the
2604
-     *                                                 only acceptable query_params is strict "col" => "value" pairs
2605
-     *                                                 because these will be inserted in any new rows created as well.
2606
-     * @return EE_Base_Class which was added as a relation. Object referred to by $other_model_id_or_obj
2607
-     * @throws EE_Error
2608
-     */
2609
-    public function add_relationship_to(
2610
-        $id_or_obj,
2611
-        $other_model_id_or_obj,
2612
-        $relationName,
2613
-        $extra_join_model_fields_n_values = []
2614
-    ) {
2615
-        $relation_obj = $this->related_settings_for($relationName);
2616
-        return $relation_obj->add_relation_to($id_or_obj, $other_model_id_or_obj, $extra_join_model_fields_n_values);
2617
-    }
2618
-
2619
-
2620
-    /**
2621
-     * Removes a relationship of the correct type between $modelObject and $otherModelObject.
2622
-     * There are the 3 cases:
2623
-     * 'belongsTo' relationship: sets $modelObject's foreign_key to null, if that field is nullable.Otherwise throws an
2624
-     * error
2625
-     * 'hasMany' relationship: sets $otherModelObject's foreign_key to null,if that field is nullable.Otherwise throws
2626
-     * an error
2627
-     * 'hasAndBelongsToMany' relationships:removes any existing entry in the join table between the two models.
2628
-     *
2629
-     * @param EE_Base_Class /int $id_or_obj
2630
-     * @param EE_Base_Class /int $other_model_id_or_obj EE_Base_Class or ID of other Model Object
2631
-     * @param string $relationName key in EEM_Base::_relations
2632
-     * @param array  $where_query  This allows you to enter further query params for the relation to for relation to
2633
-     *                             methods that allow you to further specify extra columns to join by (such as HABTM).
2634
-     *                             Keep in mind that the only acceptable query_params is strict "col" => "value" pairs
2635
-     *                             because these will be inserted in any new rows created as well.
2636
-     * @return boolean of success
2637
-     * @throws EE_Error
2638
-     */
2639
-    public function remove_relationship_to($id_or_obj, $other_model_id_or_obj, $relationName, $where_query = [])
2640
-    {
2641
-        $relation_obj = $this->related_settings_for($relationName);
2642
-        return $relation_obj->remove_relation_to($id_or_obj, $other_model_id_or_obj, $where_query);
2643
-    }
2644
-
2645
-
2646
-    /**
2647
-     * @param mixed  $id_or_obj
2648
-     * @param string $relationName
2649
-     * @param array  $where_query_params
2650
-     * @param EE_Base_Class[] objects to which relations were removed
2651
-     * @return EE_Base_Class[]
2652
-     * @throws EE_Error
2653
-     */
2654
-    public function remove_relations($id_or_obj, $relationName, $where_query_params = [])
2655
-    {
2656
-        $relation_obj = $this->related_settings_for($relationName);
2657
-        return $relation_obj->remove_relations($id_or_obj, $where_query_params);
2658
-    }
2659
-
2660
-
2661
-    /**
2662
-     * Gets all the related items of the specified $model_name, using $query_params.
2663
-     * Note: by default, we remove the "default query params"
2664
-     * because we want to get even deleted items etc.
2665
-     *
2666
-     * @param mixed  $id_or_obj    EE_Base_Class child or its ID
2667
-     * @param string $model_name   like 'Event', 'Registration', etc. always singular
2668
-     * @param array  $query_params @see
2669
-     *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2670
-     * @return EE_Base_Class[]
2671
-     * @throws EE_Error
2672
-     * @throws ReflectionException
2673
-     */
2674
-    public function get_all_related($id_or_obj, $model_name, $query_params = null)
2675
-    {
2676
-        $model_obj         = $this->ensure_is_obj($id_or_obj);
2677
-        $relation_settings = $this->related_settings_for($model_name);
2678
-        return $relation_settings->get_all_related($model_obj, $query_params);
2679
-    }
2680
-
2681
-
2682
-    /**
2683
-     * Deletes all the model objects across the relation indicated by $model_name
2684
-     * which are related to $id_or_obj which meet the criteria set in $query_params.
2685
-     * However, if the model objects can't be deleted because of blocking related model objects, then
2686
-     * they aren't deleted. (Unless the thing that would have been deleted can be soft-deleted, that still happens).
2687
-     *
2688
-     * @param EE_Base_Class|int|string $id_or_obj
2689
-     * @param string                   $model_name
2690
-     * @param array                    $query_params
2691
-     * @return int how many deleted
2692
-     * @throws EE_Error
2693
-     * @throws ReflectionException
2694
-     */
2695
-    public function delete_related($id_or_obj, $model_name, $query_params = [])
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_all_related($model_obj, $query_params);
2700
-    }
2701
-
2702
-
2703
-    /**
2704
-     * Hard deletes all the model objects across the relation indicated by $model_name
2705
-     * which are related to $id_or_obj which meet the criteria set in $query_params. If
2706
-     * the model objects can't be hard deleted because of blocking related model objects,
2707
-     * just does a soft-delete on them instead.
2708
-     *
2709
-     * @param EE_Base_Class|int|string $id_or_obj
2710
-     * @param string                   $model_name
2711
-     * @param array                    $query_params
2712
-     * @return int how many deleted
2713
-     * @throws EE_Error
2714
-     * @throws ReflectionException
2715
-     */
2716
-    public function delete_related_permanently($id_or_obj, $model_name, $query_params = [])
2717
-    {
2718
-        $model_obj         = $this->ensure_is_obj($id_or_obj);
2719
-        $relation_settings = $this->related_settings_for($model_name);
2720
-        return $relation_settings->delete_related_permanently($model_obj, $query_params);
2721
-    }
2722
-
2723
-
2724
-    /**
2725
-     * Instead of getting the related model objects, simply counts them. Ignores default_where_conditions by default,
2726
-     * unless otherwise specified in the $query_params
2727
-     *
2728
-     * @param int             /EE_Base_Class $id_or_obj
2729
-     * @param string $model_name     like 'Event', or 'Registration'
2730
-     * @param array  $query_params   @see
2731
-     *                               https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2732
-     * @param string $field_to_count name of field to count by. By default, uses primary key
2733
-     * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2734
-     *                               that by the setting $distinct to TRUE;
2735
-     * @return int
2736
-     * @throws EE_Error
2737
-     */
2738
-    public function count_related(
2739
-        $id_or_obj,
2740
-        $model_name,
2741
-        $query_params = [],
2742
-        $field_to_count = null,
2743
-        $distinct = false
2744
-    ) {
2745
-        $related_model = $this->get_related_model_obj($model_name);
2746
-        // we're just going to use the query params on the related model's normal get_all query,
2747
-        // except add a condition to say to match the current mod
2748
-        if (! isset($query_params['default_where_conditions'])) {
2749
-            $query_params['default_where_conditions'] = EEM_Base::default_where_conditions_none;
2750
-        }
2751
-        $this_model_name                                                 = $this->get_this_model_name();
2752
-        $this_pk_field_name                                              = $this->get_primary_key_field()->get_name();
2753
-        $query_params[0][ $this_model_name . "." . $this_pk_field_name ] = $id_or_obj;
2754
-        return $related_model->count($query_params, $field_to_count, $distinct);
2755
-    }
2756
-
2757
-
2758
-    /**
2759
-     * Instead of getting the related model objects, simply sums up the values of the specified field.
2760
-     * Note: ignores default_where_conditions by default, unless otherwise specified in the $query_params
2761
-     *
2762
-     * @param int           /EE_Base_Class $id_or_obj
2763
-     * @param string $model_name   like 'Event', or 'Registration'
2764
-     * @param array  $query_params @see
2765
-     *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2766
-     * @param string $field_to_sum name of field to count by. By default, uses primary key
2767
-     * @return float
2768
-     * @throws EE_Error
2769
-     */
2770
-    public function sum_related($id_or_obj, $model_name, $query_params, $field_to_sum = null)
2771
-    {
2772
-        $related_model = $this->get_related_model_obj($model_name);
2773
-        if (! is_array($query_params)) {
2774
-            EE_Error::doing_it_wrong(
2775
-                'EEM_Base::sum_related',
2776
-                sprintf(
2777
-                    esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
2778
-                    gettype($query_params)
2779
-                ),
2780
-                '4.6.0'
2781
-            );
2782
-            $query_params = [];
2783
-        }
2784
-        // we're just going to use the query params on the related model's normal get_all query,
2785
-        // except add a condition to say to match the current mod
2786
-        if (! isset($query_params['default_where_conditions'])) {
2787
-            $query_params['default_where_conditions'] = EEM_Base::default_where_conditions_none;
2788
-        }
2789
-        $this_model_name                                                 = $this->get_this_model_name();
2790
-        $this_pk_field_name                                              = $this->get_primary_key_field()->get_name();
2791
-        $query_params[0][ $this_model_name . "." . $this_pk_field_name ] = $id_or_obj;
2792
-        return $related_model->sum($query_params, $field_to_sum);
2793
-    }
2794
-
2795
-
2796
-    /**
2797
-     * Uses $this->_relatedModels info to find the first related model object of relation $relationName to the given
2798
-     * $modelObject
2799
-     *
2800
-     * @param int | EE_Base_Class $id_or_obj        EE_Base_Class child or its ID
2801
-     * @param string              $other_model_name , key in $this->_relatedModels, eg 'Registration', or 'Events'
2802
-     * @param array               $query_params     @see
2803
-     *                                              https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2804
-     * @return EE_Base_Class
2805
-     * @throws EE_Error
2806
-     */
2807
-    public function get_first_related(EE_Base_Class $id_or_obj, $other_model_name, $query_params)
2808
-    {
2809
-        $query_params['limit'] = 1;
2810
-        $results               = $this->get_all_related($id_or_obj, $other_model_name, $query_params);
2811
-        if ($results) {
2812
-            return array_shift($results);
2813
-        }
2814
-        return null;
2815
-    }
2816
-
2817
-
2818
-    /**
2819
-     * Gets the model's name as it's expected in queries. For example, if this is EEM_Event model, that would be Event
2820
-     *
2821
-     * @return string
2822
-     */
2823
-    public function get_this_model_name()
2824
-    {
2825
-        return str_replace("EEM_", "", get_class($this));
2826
-    }
2827
-
2828
-
2829
-    /**
2830
-     * Gets the model field on this model which is of type EE_Any_Foreign_Model_Name_Field
2831
-     *
2832
-     * @return EE_Any_Foreign_Model_Name_Field
2833
-     * @throws EE_Error
2834
-     */
2835
-    public function get_field_containing_related_model_name()
2836
-    {
2837
-        foreach ($this->field_settings(true) as $field) {
2838
-            if ($field instanceof EE_Any_Foreign_Model_Name_Field) {
2839
-                $field_with_model_name = $field;
2840
-            }
2841
-        }
2842
-        if (! isset($field_with_model_name) || ! $field_with_model_name) {
2843
-            throw new EE_Error(
2844
-                sprintf(
2845
-                    esc_html__("There is no EE_Any_Foreign_Model_Name field on model %s", "event_espresso"),
2846
-                    $this->get_this_model_name()
2847
-                )
2848
-            );
2849
-        }
2850
-        return $field_with_model_name;
2851
-    }
2852
-
2853
-
2854
-    /**
2855
-     * Inserts a new entry into the database, for each table.
2856
-     * Note: does not add the item to the entity map because that is done by EE_Base_Class::save() right after this.
2857
-     * If client code uses EEM_Base::insert() directly, then although the item isn't in the entity map,
2858
-     * we also know there is no model object with the newly inserted item's ID at the moment (because
2859
-     * if there were, then they would already be in the DB and this would fail); and in the future if someone
2860
-     * creates a model object with this ID (or grabs it from the DB) then it will be added to the
2861
-     * entity map at that time anyways. SO, no need for EEM_Base::insert ot add to the entity map
2862
-     *
2863
-     * @param array $field_n_values keys are field names, values are their values (in the client code's domain if
2864
-     *                              $values_already_prepared_by_model_object is false, in the model object's domain if
2865
-     *                              $values_already_prepared_by_model_object is true. See comment about this at the top
2866
-     *                              of EEM_Base)
2867
-     * @return int|string new primary key on main table that got inserted
2868
-     * @throws EE_Error
2869
-     */
2870
-    public function insert($field_n_values)
2871
-    {
2872
-        /**
2873
-         * Filters the fields and their values before inserting an item using the models
2874
-         *
2875
-         * @param array    $fields_n_values keys are the fields and values are their new values
2876
-         * @param EEM_Base $model           the model used
2877
-         */
2878
-        $field_n_values = (array) apply_filters('FHEE__EEM_Base__insert__fields_n_values', $field_n_values, $this);
2879
-        if ($this->_satisfies_unique_indexes($field_n_values)) {
2880
-            $main_table = $this->_get_main_table();
2881
-            $new_id     = $this->_insert_into_specific_table($main_table, $field_n_values, false);
2882
-            if ($new_id !== false) {
2883
-                foreach ($this->_get_other_tables() as $other_table) {
2884
-                    $this->_insert_into_specific_table($other_table, $field_n_values, $new_id);
2885
-                }
2886
-            }
2887
-            /**
2888
-             * Done just after attempting to insert a new model object
2889
-             *
2890
-             * @param EEM_Base $model           used
2891
-             * @param array    $fields_n_values fields and their values
2892
-             * @param int|string the              ID of the newly-inserted model object
2893
-             */
2894
-            do_action('AHEE__EEM_Base__insert__end', $this, $field_n_values, $new_id);
2895
-            return $new_id;
2896
-        }
2897
-        return false;
2898
-    }
2899
-
2900
-
2901
-    /**
2902
-     * Checks that the result would satisfy the unique indexes on this model
2903
-     *
2904
-     * @param array  $field_n_values
2905
-     * @param string $action
2906
-     * @return boolean
2907
-     * @throws EE_Error
2908
-     */
2909
-    protected function _satisfies_unique_indexes($field_n_values, $action = 'insert')
2910
-    {
2911
-        foreach ($this->unique_indexes() as $index_name => $index) {
2912
-            $uniqueness_where_params = array_intersect_key($field_n_values, $index->fields());
2913
-            if ($this->exists([$uniqueness_where_params])) {
2914
-                EE_Error::add_error(
2915
-                    sprintf(
2916
-                        esc_html__(
2917
-                            "Could not %s %s. %s uniqueness index failed. Fields %s must form a unique set, but an entry already exists with values %s.",
2918
-                            "event_espresso"
2919
-                        ),
2920
-                        $action,
2921
-                        $this->_get_class_name(),
2922
-                        $index_name,
2923
-                        implode(",", $index->field_names()),
2924
-                        http_build_query($uniqueness_where_params)
2925
-                    ),
2926
-                    __FILE__,
2927
-                    __FUNCTION__,
2928
-                    __LINE__
2929
-                );
2930
-                return false;
2931
-            }
2932
-        }
2933
-        return true;
2934
-    }
2935
-
2936
-
2937
-    /**
2938
-     * Checks the database for an item that conflicts (ie, if this item were
2939
-     * saved to the DB would break some uniqueness requirement, like a primary key
2940
-     * or an index primary key set) with the item specified. $id_obj_or_fields_array
2941
-     * can be either an EE_Base_Class or an array of fields n values
2942
-     *
2943
-     * @param EE_Base_Class|array $obj_or_fields_array
2944
-     * @param boolean             $include_primary_key whether to use the model object's primary key
2945
-     *                                                 when looking for conflicts
2946
-     *                                                 (ie, if false, we ignore the model object's primary key
2947
-     *                                                 when finding "conflicts". If true, it's also considered).
2948
-     *                                                 Only works for INT primary key,
2949
-     *                                                 STRING primary keys cannot be ignored
2950
-     * @return EE_Base_Class|array
2951
-     * @throws EE_Error
2952
-     * @throws ReflectionException
2953
-     */
2954
-    public function get_one_conflicting($obj_or_fields_array, $include_primary_key = true)
2955
-    {
2956
-        if ($obj_or_fields_array instanceof EE_Base_Class) {
2957
-            $fields_n_values = $obj_or_fields_array->model_field_array();
2958
-        } elseif (is_array($obj_or_fields_array)) {
2959
-            $fields_n_values = $obj_or_fields_array;
2960
-        } else {
2961
-            throw new EE_Error(
2962
-                sprintf(
2963
-                    esc_html__(
2964
-                        "%s get_all_conflicting should be called with a model object or an array of field names and values, you provided %d",
2965
-                        "event_espresso"
2966
-                    ),
2967
-                    get_class($this),
2968
-                    $obj_or_fields_array
2969
-                )
2970
-            );
2971
-        }
2972
-        $query_params = [];
2973
-        if (
2974
-            $this->has_primary_key_field()
2975
-            && ($include_primary_key
2976
-                || $this->get_primary_key_field()
2977
-                   instanceof
2978
-                   EE_Primary_Key_String_Field)
2979
-            && isset($fields_n_values[ $this->primary_key_name() ])
2980
-        ) {
2981
-            $query_params[0]['OR'][ $this->primary_key_name() ] = $fields_n_values[ $this->primary_key_name() ];
2982
-        }
2983
-        foreach ($this->unique_indexes() as $unique_index_name => $unique_index) {
2984
-            $uniqueness_where_params                              =
2985
-                array_intersect_key($fields_n_values, $unique_index->fields());
2986
-            $query_params[0]['OR'][ 'AND*' . $unique_index_name ] = $uniqueness_where_params;
2987
-        }
2988
-        // if there is nothing to base this search on, then we shouldn't find anything
2989
-        if (empty($query_params)) {
2990
-            return [];
2991
-        }
2992
-        return $this->get_one($query_params);
2993
-    }
2994
-
2995
-
2996
-    /**
2997
-     * Like count, but is optimized and returns a boolean instead of an int
2998
-     *
2999
-     * @param array $query_params
3000
-     * @return boolean
3001
-     * @throws EE_Error
3002
-     */
3003
-    public function exists($query_params)
3004
-    {
3005
-        $query_params['limit'] = 1;
3006
-        return $this->count($query_params) > 0;
3007
-    }
3008
-
3009
-
3010
-    /**
3011
-     * Wrapper for exists, except ignores default query parameters so we're only considering ID
3012
-     *
3013
-     * @param int|string $id
3014
-     * @return boolean
3015
-     * @throws EE_Error
3016
-     */
3017
-    public function exists_by_ID($id)
3018
-    {
3019
-        return $this->exists(
3020
-            [
3021
-                'default_where_conditions' => EEM_Base::default_where_conditions_none,
3022
-                [
3023
-                    $this->primary_key_name() => $id,
3024
-                ],
3025
-            ]
3026
-        );
3027
-    }
3028
-
3029
-
3030
-    /**
3031
-     * Inserts a new row in $table, using the $cols_n_values which apply to that table.
3032
-     * If a $new_id is supplied and if $table is an EE_Other_Table, we assume
3033
-     * we need to add a foreign key column to point to $new_id (which should be the primary key's value
3034
-     * on the main table)
3035
-     * This is protected rather than private because private is not accessible to any child methods and there MAY be
3036
-     * cases where we want to call it directly rather than via insert().
3037
-     *
3038
-     * @access   protected
3039
-     * @param EE_Table_Base $table
3040
-     * @param array         $fields_n_values each key should be in field's keys, and value should be an int, string or
3041
-     *                                       float
3042
-     * @param int           $new_id          for now we assume only int keys
3043
-     * @return int ID of new row inserted, or FALSE on failure
3044
-     * @throws EE_Error
3045
-     * @global WPDB         $wpdb            only used to get the $wpdb->insert_id after performing an insert
3046
-     */
3047
-    protected function _insert_into_specific_table(EE_Table_Base $table, $fields_n_values, $new_id = 0)
3048
-    {
3049
-        global $wpdb;
3050
-        $insertion_col_n_values = [];
3051
-        $format_for_insertion   = [];
3052
-        $fields_on_table        = $this->_get_fields_for_table($table->get_table_alias());
3053
-        foreach ($fields_on_table as $field_name => $field_obj) {
3054
-            // check if its an auto-incrementing column, in which case we should just leave it to do its autoincrement thing
3055
-            if ($field_obj->is_auto_increment()) {
3056
-                continue;
3057
-            }
3058
-            $prepared_value = $this->_prepare_value_or_use_default($field_obj, $fields_n_values);
3059
-            // if the value we want to assign it to is NULL, just don't mention it for the insertion
3060
-            if ($prepared_value !== null) {
3061
-                $insertion_col_n_values[ $field_obj->get_table_column() ] = $prepared_value;
3062
-                $format_for_insertion[]                                   = $field_obj->get_wpdb_data_type();
3063
-            }
3064
-        }
3065
-        if ($table instanceof EE_Secondary_Table && $new_id) {
3066
-            // its not the main table, so we should have already saved the main table's PK which we just inserted
3067
-            // so add the fk to the main table as a column
3068
-            $insertion_col_n_values[ $table->get_fk_on_table() ] = $new_id;
3069
-            $format_for_insertion[]                              =
3070
-                '%d';// yes right now we're only allowing these foreign keys to be INTs
3071
-        }
3072
-
3073
-        // insert the new entry
3074
-        $result = $this->_do_wpdb_query(
3075
-            'insert',
3076
-            [$table->get_table_name(), $insertion_col_n_values, $format_for_insertion]
3077
-        );
3078
-        if ($result === false) {
3079
-            return false;
3080
-        }
3081
-        // ok, now what do we return for the ID of the newly-inserted thing?
3082
-        if ($this->has_primary_key_field()) {
3083
-            if ($this->get_primary_key_field()->is_auto_increment()) {
3084
-                return $wpdb->insert_id;
3085
-            }
3086
-            // it's not an auto-increment primary key, so
3087
-            // it must have been supplied
3088
-            return $fields_n_values[ $this->get_primary_key_field()->get_name() ];
3089
-        }
3090
-        // we can't return a  primary key because there is none. instead return
3091
-        // a unique string indicating this model
3092
-        return $this->get_index_primary_key_string($fields_n_values);
3093
-    }
3094
-
3095
-
3096
-    /**
3097
-     * Prepare the $field_obj 's value in $fields_n_values for use in the database.
3098
-     * If the field doesn't allow NULL, try to use its default. (If it doesn't allow NULL,
3099
-     * and there is no default, we pass it along. WPDB will take care of it)
3100
-     *
3101
-     * @param EE_Model_Field_Base $field_obj
3102
-     * @param array               $fields_n_values
3103
-     * @return mixed string|int|float depending on what the table column will be expecting
3104
-     * @throws EE_Error
3105
-     */
3106
-    protected function _prepare_value_or_use_default($field_obj, $fields_n_values)
3107
-    {
3108
-        $field_name = $field_obj->get_name();
3109
-        // if this field doesn't allow nullable, don't allow it
3110
-        if (! $field_obj->is_nullable() && ! isset($fields_n_values[ $field_name ])) {
3111
-            $fields_n_values[ $field_name ] = $field_obj->get_default_value();
3112
-        }
3113
-        $unprepared_value = $fields_n_values[ $field_name ] ?? null;
3114
-        return $this->_prepare_value_for_use_in_db($unprepared_value, $field_obj);
3115
-    }
3116
-
3117
-
3118
-    /**
3119
-     * Consolidates code for preparing  a value supplied to the model for use int eh db. Calls the field's
3120
-     * prepare_for_use_in_db method on the value, and depending on $value_already_prepare_by_model_obj, may also call
3121
-     * the field's prepare_for_set() method.
3122
-     *
3123
-     * @param mixed               $value value in the client code domain if $value_already_prepared_by_model_object is
3124
-     *                                   false, otherwise a value in the model object's domain (see lengthy comment at
3125
-     *                                   top of file)
3126
-     * @param EE_Model_Field_Base $field field which will be doing the preparing of the value. If null, we assume
3127
-     *                                   $value is a custom selection
3128
-     * @return mixed a value ready for use in the database for insertions, updating, or in a where clause
3129
-     */
3130
-    private function _prepare_value_for_use_in_db($value, $field)
3131
-    {
3132
-        if ($field instanceof EE_Model_Field_Base) {
3133
-            // phpcs:disable PSR2.ControlStructures.SwitchDeclaration.TerminatingComment
3134
-            switch ($this->_values_already_prepared_by_model_object) {
3135
-                /** @noinspection PhpMissingBreakStatementInspection */
3136
-                case self::not_prepared_by_model_object:
3137
-                    $value = $field->prepare_for_set($value);
3138
-                // purposefully left out "return"
3139
-                // no break
3140
-                case self::prepared_by_model_object:
3141
-                    /** @noinspection SuspiciousAssignmentsInspection */
3142
-                    $value = $field->prepare_for_use_in_db($value);
3143
-                // no break
3144
-                case self::prepared_for_use_in_db:
3145
-                    // leave the value alone
3146
-            }
3147
-            // phpcs:enable
3148
-        }
3149
-        return $value;
3150
-    }
3151
-
3152
-
3153
-    /**
3154
-     * Returns the main table on this model
3155
-     *
3156
-     * @return EE_Primary_Table
3157
-     * @throws EE_Error
3158
-     */
3159
-    protected function _get_main_table()
3160
-    {
3161
-        foreach ($this->_tables as $table) {
3162
-            if ($table instanceof EE_Primary_Table) {
3163
-                return $table;
3164
-            }
3165
-        }
3166
-        throw new EE_Error(
3167
-            sprintf(
3168
-                esc_html__(
3169
-                    'There are no main tables on %s. They should be added to _tables array in the constructor',
3170
-                    'event_espresso'
3171
-                ),
3172
-                get_class($this)
3173
-            )
3174
-        );
3175
-    }
3176
-
3177
-
3178
-    /**
3179
-     * table
3180
-     * returns EE_Primary_Table table name
3181
-     *
3182
-     * @return string
3183
-     * @throws EE_Error
3184
-     */
3185
-    public function table()
3186
-    {
3187
-        return $this->_get_main_table()->get_table_name();
3188
-    }
3189
-
3190
-
3191
-    /**
3192
-     * table
3193
-     * returns first EE_Secondary_Table table name
3194
-     *
3195
-     * @return string
3196
-     */
3197
-    public function second_table()
3198
-    {
3199
-        // grab second table from tables array
3200
-        $second_table = end($this->_tables);
3201
-        return $second_table instanceof EE_Secondary_Table ? $second_table->get_table_name() : null;
3202
-    }
3203
-
3204
-
3205
-    /**
3206
-     * get_table_obj_by_alias
3207
-     * returns table name given it's alias
3208
-     *
3209
-     * @param string $table_alias
3210
-     * @return EE_Primary_Table | EE_Secondary_Table
3211
-     */
3212
-    public function get_table_obj_by_alias($table_alias = '')
3213
-    {
3214
-        return isset($this->_tables[ $table_alias ]) ? $this->_tables[ $table_alias ] : null;
3215
-    }
3216
-
3217
-
3218
-    /**
3219
-     * Gets all the tables of type EE_Other_Table from EEM_CPT_Basel_Model::_tables
3220
-     *
3221
-     * @return EE_Secondary_Table[]
3222
-     */
3223
-    protected function _get_other_tables()
3224
-    {
3225
-        $other_tables = [];
3226
-        foreach ($this->_tables as $table_alias => $table) {
3227
-            if ($table instanceof EE_Secondary_Table) {
3228
-                $other_tables[ $table_alias ] = $table;
3229
-            }
3230
-        }
3231
-        return $other_tables;
3232
-    }
3233
-
3234
-
3235
-    /**
3236
-     * Finds all the fields that correspond to the given table
3237
-     *
3238
-     * @param string $table_alias , array key in EEM_Base::_tables
3239
-     * @return EE_Model_Field_Base[]
3240
-     */
3241
-    public function _get_fields_for_table($table_alias)
3242
-    {
3243
-        return $this->_fields[ $table_alias ];
3244
-    }
3245
-
3246
-
3247
-    /**
3248
-     * Recurses through all the where parameters, and finds all the related models we'll need
3249
-     * to complete this query. Eg, given where parameters like array('EVT_ID'=>3) from within Event model, we won't
3250
-     * need any related models. But if the array were array('Registrations.REG_ID'=>3), we'd need the related
3251
-     * Registration model. If it were array('Registrations.Transactions.Payments.PAY_ID'=>3), then we'd need the
3252
-     * related Registration, Transaction, and Payment models.
3253
-     *
3254
-     * @param array $query_params @see
3255
-     *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
3256
-     * @return EE_Model_Query_Info_Carrier
3257
-     * @throws EE_Error
3258
-     */
3259
-    public function _extract_related_models_from_query($query_params)
3260
-    {
3261
-        $query_info_carrier = new EE_Model_Query_Info_Carrier();
3262
-        if (array_key_exists(0, $query_params)) {
3263
-            $this->_extract_related_models_from_sub_params_array_keys($query_params[0], $query_info_carrier, 0);
3264
-        }
3265
-        if (array_key_exists('group_by', $query_params)) {
3266
-            if (is_array($query_params['group_by'])) {
3267
-                $this->_extract_related_models_from_sub_params_array_values(
3268
-                    $query_params['group_by'],
3269
-                    $query_info_carrier,
3270
-                    'group_by'
3271
-                );
3272
-            } elseif (! empty($query_params['group_by'])) {
3273
-                $this->_extract_related_model_info_from_query_param(
3274
-                    $query_params['group_by'],
3275
-                    $query_info_carrier,
3276
-                    'group_by'
3277
-                );
3278
-            }
3279
-        }
3280
-        if (array_key_exists('having', $query_params)) {
3281
-            $this->_extract_related_models_from_sub_params_array_keys(
3282
-                $query_params[0],
3283
-                $query_info_carrier,
3284
-                'having'
3285
-            );
3286
-        }
3287
-        if (array_key_exists('order_by', $query_params)) {
3288
-            if (is_array($query_params['order_by'])) {
3289
-                $this->_extract_related_models_from_sub_params_array_keys(
3290
-                    $query_params['order_by'],
3291
-                    $query_info_carrier,
3292
-                    'order_by'
3293
-                );
3294
-            } elseif (! empty($query_params['order_by'])) {
3295
-                $this->_extract_related_model_info_from_query_param(
3296
-                    $query_params['order_by'],
3297
-                    $query_info_carrier,
3298
-                    'order_by'
3299
-                );
3300
-            }
3301
-        }
3302
-        if (array_key_exists('force_join', $query_params)) {
3303
-            $this->_extract_related_models_from_sub_params_array_values(
3304
-                $query_params['force_join'],
3305
-                $query_info_carrier,
3306
-                'force_join'
3307
-            );
3308
-        }
3309
-        $this->extractRelatedModelsFromCustomSelects($query_info_carrier);
3310
-        return $query_info_carrier;
3311
-    }
3312
-
3313
-
3314
-    /**
3315
-     * For extracting related models from WHERE (0), HAVING (having), ORDER BY (order_by) or forced joins (force_join)
3316
-     *
3317
-     * @param array                       $sub_query_params @see
3318
-     *                                                      https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#-0-where-conditions
3319
-     * @param EE_Model_Query_Info_Carrier $model_query_info_carrier
3320
-     * @param string                      $query_param_type one of $this->_allowed_query_params
3321
-     * @return EE_Model_Query_Info_Carrier
3322
-     * @throws EE_Error
3323
-     */
3324
-    private function _extract_related_models_from_sub_params_array_keys(
3325
-        $sub_query_params,
3326
-        EE_Model_Query_Info_Carrier $model_query_info_carrier,
3327
-        $query_param_type
3328
-    ) {
3329
-        if (! empty($sub_query_params)) {
3330
-            $sub_query_params = (array) $sub_query_params;
3331
-            foreach ($sub_query_params as $param => $possibly_array_of_params) {
3332
-                // $param could be simply 'EVT_ID', or it could be 'Registrations.REG_ID', or even 'Registrations.Transactions.Payments.PAY_amount'
3333
-                $this->_extract_related_model_info_from_query_param(
3334
-                    $param,
3335
-                    $model_query_info_carrier,
3336
-                    $query_param_type
3337
-                );
3338
-                // if $possibly_array_of_params is an array, try recursing into it, searching for keys which
3339
-                // indicate needed joins. Eg, array('NOT'=>array('Registration.TXN_ID'=>23)). In this case, we tried
3340
-                // extracting models out of the 'NOT', which obviously wasn't successful, and then we recurse into the value
3341
-                // of array('Registration.TXN_ID'=>23)
3342
-                $query_param_sans_stars =
3343
-                    $this->_remove_stars_and_anything_after_from_condition_query_param_key($param);
3344
-                if (in_array($query_param_sans_stars, $this->_logic_query_param_keys, true)) {
3345
-                    if (! is_array($possibly_array_of_params)) {
3346
-                        throw new EE_Error(
3347
-                            sprintf(
3348
-                                esc_html__(
3349
-                                    "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'))",
3350
-                                    "event_espresso"
3351
-                                ),
3352
-                                $param,
3353
-                                $possibly_array_of_params
3354
-                            )
3355
-                        );
3356
-                    }
3357
-                    $this->_extract_related_models_from_sub_params_array_keys(
3358
-                        $possibly_array_of_params,
3359
-                        $model_query_info_carrier,
3360
-                        $query_param_type
3361
-                    );
3362
-                } elseif (
3363
-                    $query_param_type === 0 // ie WHERE
3364
-                    && is_array($possibly_array_of_params)
3365
-                    && isset($possibly_array_of_params[2])
3366
-                    && $possibly_array_of_params[2] == true
3367
-                ) {
3368
-                    // then $possible_array_of_params looks something like array('<','DTT_sold',true)
3369
-                    // indicating that $possible_array_of_params[1] is actually a field name,
3370
-                    // from which we should extract query parameters!
3371
-                    if (! isset($possibly_array_of_params[0], $possibly_array_of_params[1])) {
3372
-                        throw new EE_Error(
3373
-                            sprintf(
3374
-                                esc_html__(
3375
-                                    "Improperly formed query parameter %s. It should be numerically indexed like array('<','DTT_sold',true); but you provided %s",
3376
-                                    "event_espresso"
3377
-                                ),
3378
-                                $query_param_type,
3379
-                                implode(",", $possibly_array_of_params)
3380
-                            )
3381
-                        );
3382
-                    }
3383
-                    $this->_extract_related_model_info_from_query_param(
3384
-                        $possibly_array_of_params[1],
3385
-                        $model_query_info_carrier,
3386
-                        $query_param_type
3387
-                    );
3388
-                }
3389
-            }
3390
-        }
3391
-        return $model_query_info_carrier;
3392
-    }
3393
-
3394
-
3395
-    /**
3396
-     * For extracting related models from forced_joins, where the array values contain the info about what
3397
-     * models to join with. Eg an array like array('Attendee','Price.Price_Type');
3398
-     *
3399
-     * @param array                       $sub_query_params @see
3400
-     *                                                      https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3401
-     * @param EE_Model_Query_Info_Carrier $model_query_info_carrier
3402
-     * @param string                      $query_param_type one of $this->_allowed_query_params
3403
-     * @return EE_Model_Query_Info_Carrier
3404
-     * @throws EE_Error
3405
-     */
3406
-    private function _extract_related_models_from_sub_params_array_values(
3407
-        $sub_query_params,
3408
-        EE_Model_Query_Info_Carrier $model_query_info_carrier,
3409
-        $query_param_type
3410
-    ) {
3411
-        if (! empty($sub_query_params)) {
3412
-            if (! is_array($sub_query_params)) {
3413
-                throw new EE_Error(
3414
-                    sprintf(
3415
-                        esc_html__("Query parameter %s should be an array, but it isn't.", "event_espresso"),
3416
-                        $sub_query_params
3417
-                    )
3418
-                );
3419
-            }
3420
-            foreach ($sub_query_params as $param) {
3421
-                // $param could be simply 'EVT_ID', or it could be 'Registrations.REG_ID', or even 'Registrations.Transactions.Payments.PAY_amount'
3422
-                $this->_extract_related_model_info_from_query_param(
3423
-                    $param,
3424
-                    $model_query_info_carrier,
3425
-                    $query_param_type
3426
-                );
3427
-            }
3428
-        }
3429
-        return $model_query_info_carrier;
3430
-    }
3431
-
3432
-
3433
-    /**
3434
-     * Extract all the query parts from  model query params
3435
-     * and put into a EEM_Related_Model_Info_Carrier for easy extraction into a query. We create this object
3436
-     * instead of directly constructing the SQL because often we need to extract info from the $query_params
3437
-     * but use them in a different order. Eg, we need to know what models we are querying
3438
-     * before we know what joins to perform. However, we need to know what data types correspond to which fields on
3439
-     * other models before we can finalize the where clause SQL.
3440
-     *
3441
-     * @param array $query_params @see
3442
-     *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
3443
-     * @return EE_Model_Query_Info_Carrier
3444
-     * @throws EE_Error
3445
-     * @throws ModelConfigurationException*@throws ReflectionException
3446
-     * @throws ReflectionException
3447
-     */
3448
-    public function _create_model_query_info_carrier($query_params)
3449
-    {
3450
-        if (! is_array($query_params)) {
3451
-            EE_Error::doing_it_wrong(
3452
-                'EEM_Base::_create_model_query_info_carrier',
3453
-                sprintf(
3454
-                    esc_html__(
3455
-                        '$query_params should be an array, you passed a variable of type %s',
3456
-                        'event_espresso'
3457
-                    ),
3458
-                    gettype($query_params)
3459
-                ),
3460
-                '4.6.0'
3461
-            );
3462
-            $query_params = [];
3463
-        }
3464
-        $query_params[0] = isset($query_params[0]) ? $query_params[0] : [];
3465
-        // first check if we should alter the query to account for caps or not
3466
-        // because the caps might require us to do extra joins
3467
-        if (isset($query_params['caps']) && $query_params['caps'] !== 'none') {
3468
-            $query_params[0] = array_replace_recursive(
3469
-                $query_params[0],
3470
-                $this->caps_where_conditions($query_params['caps'])
3471
-            );
3472
-        }
3473
-
3474
-        // check if we should alter the query to remove data related to protected
3475
-        // custom post types
3476
-        if (isset($query_params['exclude_protected']) && $query_params['exclude_protected'] === true) {
3477
-            $where_param_key_for_password = $this->modelChainAndPassword();
3478
-            // only include if related to a cpt where no password has been set
3479
-            $query_params[0]['OR*nopassword'] = [
3480
-                $where_param_key_for_password       => '',
3481
-                $where_param_key_for_password . '*' => ['IS_NULL'],
3482
-            ];
3483
-        }
3484
-        $query_object = $this->_extract_related_models_from_query($query_params);
3485
-        // verify where_query_params has NO numeric indexes.... that's simply not how you use it!
3486
-        foreach ($query_params[0] as $key => $value) {
3487
-            if (is_int($key)) {
3488
-                throw new EE_Error(
3489
-                    sprintf(
3490
-                        esc_html__(
3491
-                            "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.",
3492
-                            "event_espresso"
3493
-                        ),
3494
-                        $key,
3495
-                        var_export($value, true),
3496
-                        var_export($query_params, true),
3497
-                        get_class($this)
3498
-                    )
3499
-                );
3500
-            }
3501
-        }
3502
-        if (
3503
-            array_key_exists('default_where_conditions', $query_params)
3504
-            && ! empty($query_params['default_where_conditions'])
3505
-        ) {
3506
-            $use_default_where_conditions = $query_params['default_where_conditions'];
3507
-        } else {
3508
-            $use_default_where_conditions = EEM_Base::default_where_conditions_all;
3509
-        }
3510
-        $query_params[0] = array_merge(
3511
-            $this->_get_default_where_conditions_for_models_in_query(
3512
-                $query_object,
3513
-                $use_default_where_conditions,
3514
-                $query_params[0]
3515
-            ),
3516
-            $query_params[0]
3517
-        );
3518
-        $query_object->set_where_sql($this->_construct_where_clause($query_params[0]));
3519
-        // if this is a "on_join_limit" then we are limiting on on a specific table in a multi_table join.
3520
-        // So we need to setup a subquery and use that for the main join.
3521
-        // Note for now this only works on the primary table for the model.
3522
-        // So for instance, you could set the limit array like this:
3523
-        // array( 'on_join_limit' => array('Primary_Table_Alias', array(1,10) ) )
3524
-        if (array_key_exists('on_join_limit', $query_params) && ! empty($query_params['on_join_limit'])) {
3525
-            $query_object->set_main_model_join_sql(
3526
-                $this->_construct_limit_join_select(
3527
-                    $query_params['on_join_limit'][0],
3528
-                    $query_params['on_join_limit'][1]
3529
-                )
3530
-            );
3531
-        }
3532
-        // set limit
3533
-        if (array_key_exists('limit', $query_params)) {
3534
-            if (is_array($query_params['limit'])) {
3535
-                if (! isset($query_params['limit'][0], $query_params['limit'][1])) {
3536
-                    $e = sprintf(
3537
-                        esc_html__(
3538
-                            "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)",
3539
-                            "event_espresso"
3540
-                        ),
3541
-                        http_build_query($query_params['limit'])
3542
-                    );
3543
-                    throw new EE_Error($e . "|" . $e);
3544
-                }
3545
-                // they passed us an array for the limit. Assume it's like array(50,25), meaning offset by 50, and get 25
3546
-                $query_object->set_limit_sql(" LIMIT " . $query_params['limit'][0] . "," . $query_params['limit'][1]);
3547
-            } elseif (! empty($query_params['limit'])) {
3548
-                $query_object->set_limit_sql(" LIMIT " . $query_params['limit']);
3549
-            }
3550
-        }
3551
-        // set order by
3552
-        if (array_key_exists('order_by', $query_params)) {
3553
-            if (is_array($query_params['order_by'])) {
3554
-                // if they're using 'order_by' as an array, they can't use 'order' (because 'order_by' must
3555
-                // specify whether to ascend or descend on each field. Eg 'order_by'=>array('EVT_ID'=>'ASC'). So
3556
-                // including 'order' wouldn't make any sense if 'order_by' has already specified which way to order!
3557
-                if (array_key_exists('order', $query_params)) {
3558
-                    throw new EE_Error(
3559
-                        sprintf(
3560
-                            esc_html__(
3561
-                                "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 ",
3562
-                                "event_espresso"
3563
-                            ),
3564
-                            get_class($this),
3565
-                            implode(", ", array_keys($query_params['order_by'])),
3566
-                            implode(", ", $query_params['order_by']),
3567
-                            $query_params['order']
3568
-                        )
3569
-                    );
3570
-                }
3571
-                $this->_extract_related_models_from_sub_params_array_keys(
3572
-                    $query_params['order_by'],
3573
-                    $query_object,
3574
-                    'order_by'
3575
-                );
3576
-                // assume it's an array of fields to order by
3577
-                $order_array = [];
3578
-                foreach ($query_params['order_by'] as $field_name_to_order_by => $order) {
3579
-                    $order         = $this->_extract_order($order);
3580
-                    $order_array[] = $this->_deduce_column_name_from_query_param($field_name_to_order_by) . SP . $order;
3581
-                }
3582
-                $query_object->set_order_by_sql(" ORDER BY " . implode(",", $order_array));
3583
-            } elseif (! empty($query_params['order_by'])) {
3584
-                $this->_extract_related_model_info_from_query_param(
3585
-                    $query_params['order_by'],
3586
-                    $query_object,
3587
-                    'order',
3588
-                    $query_params['order_by']
3589
-                );
3590
-                $order = isset($query_params['order'])
3591
-                    ? $this->_extract_order($query_params['order'])
3592
-                    : 'DESC';
3593
-                $query_object->set_order_by_sql(
3594
-                    " ORDER BY " . $this->_deduce_column_name_from_query_param($query_params['order_by']) . SP . $order
3595
-                );
3596
-            }
3597
-        }
3598
-        // if 'order_by' wasn't set, maybe they are just using 'order' on its own?
3599
-        if (
3600
-            ! array_key_exists('order_by', $query_params)
3601
-            && array_key_exists('order', $query_params)
3602
-            && ! empty($query_params['order'])
3603
-        ) {
3604
-            $pk_field = $this->get_primary_key_field();
3605
-            $order    = $this->_extract_order($query_params['order']);
3606
-            $query_object->set_order_by_sql(" ORDER BY " . $pk_field->get_qualified_column() . SP . $order);
3607
-        }
3608
-        // set group by
3609
-        if (array_key_exists('group_by', $query_params)) {
3610
-            if (is_array($query_params['group_by'])) {
3611
-                // it's an array, so assume we'll be grouping by a bunch of stuff
3612
-                $group_by_array = [];
3613
-                foreach ($query_params['group_by'] as $field_name_to_group_by) {
3614
-                    $group_by_array[] = $this->_deduce_column_name_from_query_param($field_name_to_group_by);
3615
-                }
3616
-                $query_object->set_group_by_sql(" GROUP BY " . implode(", ", $group_by_array));
3617
-            } elseif (! empty($query_params['group_by'])) {
3618
-                $query_object->set_group_by_sql(
3619
-                    " GROUP BY " . $this->_deduce_column_name_from_query_param($query_params['group_by'])
3620
-                );
3621
-            }
3622
-        }
3623
-        // set having
3624
-        if (array_key_exists('having', $query_params) && $query_params['having']) {
3625
-            $query_object->set_having_sql($this->_construct_having_clause($query_params['having']));
3626
-        }
3627
-        // now, just verify they didn't pass anything wack
3628
-        foreach ($query_params as $query_key => $query_value) {
3629
-            if (! in_array($query_key, $this->_allowed_query_params, true)) {
3630
-                throw new EE_Error(
3631
-                    sprintf(
3632
-                        esc_html__(
3633
-                            "You passed %s as a query parameter to %s, which is illegal! The allowed query parameters are %s",
3634
-                            'event_espresso'
3635
-                        ),
3636
-                        $query_key,
3637
-                        get_class($this),
3638
-                        //                      print_r( $this->_allowed_query_params, TRUE )
3639
-                        implode(',', $this->_allowed_query_params)
3640
-                    )
3641
-                );
3642
-            }
3643
-        }
3644
-        $main_model_join_sql = $query_object->get_main_model_join_sql();
3645
-        if (empty($main_model_join_sql)) {
3646
-            $query_object->set_main_model_join_sql($this->_construct_internal_join());
3647
-        }
3648
-        return $query_object;
3649
-    }
3650
-
3651
-
3652
-    /**
3653
-     * Gets the where conditions that should be imposed on the query based on the
3654
-     * context (eg reading frontend, backend, edit or delete).
3655
-     *
3656
-     * @param string $context one of EEM_Base::valid_cap_contexts()
3657
-     * @return array @see
3658
-     *               https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3659
-     * @throws EE_Error
3660
-     */
3661
-    public function caps_where_conditions($context = self::caps_read)
3662
-    {
3663
-        EEM_Base::verify_is_valid_cap_context($context);
3664
-        $cap_where_conditions = [];
3665
-        $cap_restrictions     = $this->caps_missing($context);
3666
-        /**
3667
-         * @var $cap_restrictions EE_Default_Where_Conditions[]
3668
-         */
3669
-        foreach ($cap_restrictions as $cap => $restriction_if_no_cap) {
3670
-            $cap_where_conditions = array_replace_recursive(
3671
-                $cap_where_conditions,
3672
-                $restriction_if_no_cap->get_default_where_conditions()
3673
-            );
3674
-        }
3675
-        return apply_filters(
3676
-            'FHEE__EEM_Base__caps_where_conditions__return',
3677
-            $cap_where_conditions,
3678
-            $this,
3679
-            $context,
3680
-            $cap_restrictions
3681
-        );
3682
-    }
3683
-
3684
-
3685
-    /**
3686
-     * Verifies that $should_be_order_string is in $this->_allowed_order_values,
3687
-     * otherwise throws an exception
3688
-     *
3689
-     * @param string $should_be_order_string
3690
-     * @return string either ASC, asc, DESC or desc
3691
-     * @throws EE_Error
3692
-     */
3693
-    private function _extract_order($should_be_order_string)
3694
-    {
3695
-        if (in_array($should_be_order_string, $this->_allowed_order_values)) {
3696
-            return $should_be_order_string;
3697
-        }
3698
-        throw new EE_Error(
3699
-            sprintf(
3700
-                esc_html__(
3701
-                    "While performing a query on '%s', tried to use '%s' as an order parameter. ",
3702
-                    "event_espresso"
3703
-                ),
3704
-                get_class($this),
3705
-                $should_be_order_string
3706
-            )
3707
-        );
3708
-    }
3709
-
3710
-
3711
-    /**
3712
-     * Looks at all the models which are included in this query, and asks each
3713
-     * for their universal_where_params, and returns them in the same format as $query_params[0] (where),
3714
-     * so they can be merged
3715
-     *
3716
-     * @param EE_Model_Query_Info_Carrier $query_info_carrier
3717
-     * @param string                      $use_default_where_conditions can be 'none','other_models_only', or 'all'.
3718
-     *                                                                  'none' means NO default where conditions will
3719
-     *                                                                  be used AT ALL during this query.
3720
-     *                                                                  'other_models_only' means default where
3721
-     *                                                                  conditions from other models will be used, but
3722
-     *                                                                  not for this primary model. 'all', the default,
3723
-     *                                                                  means default where conditions will apply as
3724
-     *                                                                  normal
3725
-     * @param array                       $where_query_params           @see
3726
-     *                                                                  https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3727
-     * @throws EE_Error
3728
-     */
3729
-    private function _get_default_where_conditions_for_models_in_query(
3730
-        EE_Model_Query_Info_Carrier $query_info_carrier,
3731
-        $use_default_where_conditions = EEM_Base::default_where_conditions_all,
3732
-        $where_query_params = []
3733
-    ) {
3734
-        $allowed_used_default_where_conditions_values = EEM_Base::valid_default_where_conditions();
3735
-        if (! in_array($use_default_where_conditions, $allowed_used_default_where_conditions_values)) {
3736
-            throw new EE_Error(
3737
-                sprintf(
3738
-                    esc_html__(
3739
-                        "You passed an invalid value to the query parameter 'default_where_conditions' of '%s'. Allowed values are %s",
3740
-                        "event_espresso"
3741
-                    ),
3742
-                    $use_default_where_conditions,
3743
-                    implode(", ", $allowed_used_default_where_conditions_values)
3744
-                )
3745
-            );
3746
-        }
3747
-        $universal_query_params = [];
3748
-        if ($this->_should_use_default_where_conditions($use_default_where_conditions, true)) {
3749
-            $universal_query_params = $this->_get_default_where_conditions();
3750
-        } elseif ($this->_should_use_minimum_where_conditions($use_default_where_conditions, true)) {
3751
-            $universal_query_params = $this->_get_minimum_where_conditions();
3752
-        }
3753
-        foreach ($query_info_carrier->get_model_names_included() as $model_relation_path => $model_name) {
3754
-            $related_model = $this->get_related_model_obj($model_name);
3755
-            if ($this->_should_use_default_where_conditions($use_default_where_conditions, false)) {
3756
-                $related_model_universal_where_params =
3757
-                    $related_model->_get_default_where_conditions($model_relation_path);
3758
-            } elseif ($this->_should_use_minimum_where_conditions($use_default_where_conditions, false)) {
3759
-                $related_model_universal_where_params =
3760
-                    $related_model->_get_minimum_where_conditions($model_relation_path);
3761
-            } else {
3762
-                // we don't want to add full or even minimum default where conditions from this model, so just continue
3763
-                continue;
3764
-            }
3765
-            $overrides              = $this->_override_defaults_or_make_null_friendly(
3766
-                $related_model_universal_where_params,
3767
-                $where_query_params,
3768
-                $related_model,
3769
-                $model_relation_path
3770
-            );
3771
-            $universal_query_params = EEH_Array::merge_arrays_and_overwrite_keys(
3772
-                $universal_query_params,
3773
-                $overrides
3774
-            );
3775
-        }
3776
-        return $universal_query_params;
3777
-    }
3778
-
3779
-
3780
-    /**
3781
-     * Determines whether or not we should use default where conditions for the model in question
3782
-     * (this model, or other related models).
3783
-     * Basically, we should use default where conditions on this model if they have requested to use them on all models,
3784
-     * this model only, or to use minimum where conditions on all other models and normal where conditions on this one.
3785
-     * We should use default where conditions on related models when they requested to use default where conditions
3786
-     * on all models, or specifically just on other related models
3787
-     *
3788
-     * @param      $default_where_conditions_value
3789
-     * @param bool $for_this_model false means this is for OTHER related models
3790
-     * @return bool
3791
-     */
3792
-    private function _should_use_default_where_conditions($default_where_conditions_value, $for_this_model = true)
3793
-    {
3794
-        return (
3795
-                   $for_this_model
3796
-                   && in_array(
3797
-                       $default_where_conditions_value,
3798
-                       [
3799
-                           EEM_Base::default_where_conditions_all,
3800
-                           EEM_Base::default_where_conditions_this_only,
3801
-                           EEM_Base::default_where_conditions_minimum_others,
3802
-                       ],
3803
-                       true
3804
-                   )
3805
-               )
3806
-               || (
3807
-                   ! $for_this_model
3808
-                   && in_array(
3809
-                       $default_where_conditions_value,
3810
-                       [
3811
-                           EEM_Base::default_where_conditions_all,
3812
-                           EEM_Base::default_where_conditions_others_only,
3813
-                       ],
3814
-                       true
3815
-                   )
3816
-               );
3817
-    }
3818
-
3819
-
3820
-    /**
3821
-     * Determines whether or not we should use default minimum conditions for the model in question
3822
-     * (this model, or other related models).
3823
-     * Basically, we should use minimum where conditions on this model only if they requested all models to use minimum
3824
-     * where conditions.
3825
-     * We should use minimum where conditions on related models if they requested to use minimum where conditions
3826
-     * on this model or others
3827
-     *
3828
-     * @param      $default_where_conditions_value
3829
-     * @param bool $for_this_model false means this is for OTHER related models
3830
-     * @return bool
3831
-     */
3832
-    private function _should_use_minimum_where_conditions($default_where_conditions_value, $for_this_model = true)
3833
-    {
3834
-        return (
3835
-                   $for_this_model
3836
-                   && $default_where_conditions_value === EEM_Base::default_where_conditions_minimum_all
3837
-               )
3838
-               || (
3839
-                   ! $for_this_model
3840
-                   && in_array(
3841
-                       $default_where_conditions_value,
3842
-                       [
3843
-                           EEM_Base::default_where_conditions_minimum_others,
3844
-                           EEM_Base::default_where_conditions_minimum_all,
3845
-                       ],
3846
-                       true
3847
-                   )
3848
-               );
3849
-    }
3850
-
3851
-
3852
-    /**
3853
-     * Checks if any of the defaults have been overridden. If there are any that AREN'T overridden,
3854
-     * then we also add a special where condition which allows for that model's primary key
3855
-     * to be null (which is important for JOINs. Eg, if you want to see all Events ordered by Venue's name,
3856
-     * then Event's with NO Venue won't appear unless you allow VNU_ID to be NULL)
3857
-     *
3858
-     * @param array    $default_where_conditions
3859
-     * @param array    $provided_where_conditions
3860
-     * @param EEM_Base $model
3861
-     * @param string   $model_relation_path like 'Transaction.Payment.'
3862
-     * @return array @see
3863
-     *               https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3864
-     * @throws EE_Error
3865
-     */
3866
-    private function _override_defaults_or_make_null_friendly(
3867
-        $default_where_conditions,
3868
-        $provided_where_conditions,
3869
-        $model,
3870
-        $model_relation_path
3871
-    ) {
3872
-        $null_friendly_where_conditions = [];
3873
-        $none_overridden                = true;
3874
-        $or_condition_key_for_defaults  = 'OR*' . get_class($model);
3875
-        foreach ($default_where_conditions as $key => $val) {
3876
-            if (isset($provided_where_conditions[ $key ])) {
3877
-                $none_overridden = false;
3878
-            } else {
3879
-                $null_friendly_where_conditions[ $or_condition_key_for_defaults ]['AND'][ $key ] = $val;
3880
-            }
3881
-        }
3882
-        if ($none_overridden && $default_where_conditions) {
3883
-            if ($model->has_primary_key_field()) {
3884
-                $null_friendly_where_conditions[ $or_condition_key_for_defaults ][ $model_relation_path
3885
-                                                                                   . "."
3886
-                                                                                   . $model->primary_key_name() ] =
3887
-                    ['IS NULL'];
3888
-            }/*else{
40
+	/**
41
+	 * Flag to indicate whether the values provided to EEM_Base have already been prepared
42
+	 * by the model object or not (ie, the model object has used the field's _prepare_for_set function on the values).
43
+	 * They almost always WILL NOT, but it's not necessarily a requirement.
44
+	 * For example, if you want to run EEM_Event::instance()->get_all(array(array('EVT_ID'=>$_GET['event_id'])));
45
+	 *
46
+	 * @var boolean
47
+	 */
48
+	private $_values_already_prepared_by_model_object = 0;
49
+
50
+	/**
51
+	 * when $_values_already_prepared_by_model_object equals this, we assume
52
+	 * the data is just like form input that needs to have the model fields'
53
+	 * prepare_for_set and prepare_for_use_in_db called on it
54
+	 */
55
+	const not_prepared_by_model_object = 0;
56
+
57
+	/**
58
+	 * when $_values_already_prepared_by_model_object equals this, we
59
+	 * assume this value is coming from a model object and doesn't need to have
60
+	 * prepare_for_set called on it, just prepare_for_use_in_db is used
61
+	 */
62
+	const prepared_by_model_object = 1;
63
+
64
+	/**
65
+	 * when $_values_already_prepared_by_model_object equals this, we assume
66
+	 * the values are already to be used in the database (ie no processing is done
67
+	 * on them by the model's fields)
68
+	 */
69
+	const prepared_for_use_in_db = 2;
70
+
71
+
72
+	protected $singular_item = 'Item';
73
+
74
+	protected $plural_item   = 'Items';
75
+
76
+	/**
77
+	 * @type EE_Table_Base[] $_tables array of EE_Table objects for defining which tables comprise this model.
78
+	 */
79
+	protected $_tables;
80
+
81
+	/**
82
+	 * with two levels: top-level has array keys which are database table aliases (ie, keys in _tables)
83
+	 * and the value is an array. Each of those sub-arrays have keys of field names (eg 'ATT_ID', which should also be
84
+	 * variable names on the model objects (eg, EE_Attendee), and the keys should be children of EE_Model_Field
85
+	 *
86
+	 * @var EE_Model_Field_Base[][] $_fields
87
+	 */
88
+	protected $_fields;
89
+
90
+	/**
91
+	 * array of different kinds of relations
92
+	 *
93
+	 * @var EE_Model_Relation_Base[] $_model_relations
94
+	 */
95
+	protected $_model_relations = [];
96
+
97
+	/**
98
+	 * @var EE_Index[] $_indexes
99
+	 */
100
+	protected $_indexes = [];
101
+
102
+	/**
103
+	 * Default strategy for getting where conditions on this model. This strategy is used to get default
104
+	 * where conditions which are added to get_all, update, and delete queries. They can be overridden
105
+	 * by setting the same columns as used in these queries in the query yourself.
106
+	 *
107
+	 * @var EE_Default_Where_Conditions
108
+	 */
109
+	protected $_default_where_conditions_strategy;
110
+
111
+	/**
112
+	 * Strategy for getting conditions on this model when 'default_where_conditions' equals 'minimum'.
113
+	 * This is particularly useful when you want something between 'none' and 'default'
114
+	 *
115
+	 * @var EE_Default_Where_Conditions
116
+	 */
117
+	protected $_minimum_where_conditions_strategy;
118
+
119
+	/**
120
+	 * String describing how to find the "owner" of this model's objects.
121
+	 * When there is a foreign key on this model to the wp_users table, this isn't needed.
122
+	 * But when there isn't, this indicates which related model, or transiently-related model,
123
+	 * has the foreign key to the wp_users table.
124
+	 * Eg, for EEM_Registration this would be 'Event' because registrations are directly
125
+	 * related to events, and events have a foreign key to wp_users.
126
+	 * On EEM_Transaction, this would be 'Transaction.Event'
127
+	 *
128
+	 * @var string
129
+	 */
130
+	protected $_model_chain_to_wp_user = '';
131
+
132
+	/**
133
+	 * String describing how to find the model with a password controlling access to this model. This property has the
134
+	 * same format as $_model_chain_to_wp_user. This is primarily used by the query param "exclude_protected".
135
+	 * This value is the path of models to follow to arrive at the model with the password field.
136
+	 * If it is an empty string, it means this model has the password field. If it is null, it means there is no
137
+	 * model with a password that should affect reading this on the front-end.
138
+	 * Eg this is an empty string for the Event model because it has a password.
139
+	 * This is null for the Registration model, because its event's password has no bearing on whether
140
+	 * you can read the registration or not on the front-end (it just depends on your capabilities.)
141
+	 * This is 'Datetime.Event' on the Ticket model, because model queries for tickets that set "exclude_protected"
142
+	 * should hide tickets for datetimes for events that have a password set.
143
+	 *
144
+	 * @var string |null
145
+	 */
146
+	protected $model_chain_to_password = null;
147
+
148
+	/**
149
+	 * This is a flag typically set by updates so that we don't load the where strategy on updates because updates
150
+	 * don't need it (particularly CPT models)
151
+	 *
152
+	 * @var bool
153
+	 */
154
+	protected $_ignore_where_strategy = false;
155
+
156
+	/**
157
+	 * String used in caps relating to this model. Eg, if the caps relating to this
158
+	 * model are 'ee_edit_events', 'ee_read_events', etc, it would be 'events'.
159
+	 *
160
+	 * @var string. If null it hasn't been initialized yet. If false then we
161
+	 * have indicated capabilities don't apply to this
162
+	 */
163
+	protected $_caps_slug = null;
164
+
165
+	/**
166
+	 * 2d array where top-level keys are one of EEM_Base::valid_cap_contexts(),
167
+	 * and next-level keys are capability names, and each's value is a
168
+	 * EE_Default_Where_Condition. If the requester requests to apply caps to the query,
169
+	 * they specify which context to use (ie, frontend, backend, edit or delete)
170
+	 * and then each capability in the corresponding sub-array that they're missing
171
+	 * adds the where conditions onto the query.
172
+	 *
173
+	 * @var array
174
+	 */
175
+	protected $_cap_restrictions = [
176
+		self::caps_read       => [],
177
+		self::caps_read_admin => [],
178
+		self::caps_edit       => [],
179
+		self::caps_delete     => [],
180
+	];
181
+
182
+	/**
183
+	 * Array defining which cap restriction generators to use to create default
184
+	 * cap restrictions to put in EEM_Base::_cap_restrictions.
185
+	 * Array-keys are one of EEM_Base::valid_cap_contexts(), and values are a child of
186
+	 * EE_Restriction_Generator_Base. If you don't want any cap restrictions generated
187
+	 * automatically set this to false (not just null).
188
+	 *
189
+	 * @var EE_Restriction_Generator_Base[]
190
+	 */
191
+	protected $_cap_restriction_generators = [];
192
+
193
+	/**
194
+	 * constants used to categorize capability restrictions on EEM_Base::_caps_restrictions
195
+	 */
196
+	const caps_read       = 'read';
197
+
198
+	const caps_read_admin = 'read_admin';
199
+
200
+	const caps_edit       = 'edit';
201
+
202
+	const caps_delete     = 'delete';
203
+
204
+	/**
205
+	 * Keys are all the cap contexts (ie constants EEM_Base::_caps_*) and values are their 'action'
206
+	 * as how they'd be used in capability names. Eg EEM_Base::caps_read ('read_frontend')
207
+	 * maps to 'read' because when looking for relevant permissions we're going to use
208
+	 * 'read' in teh capabilities names like 'ee_read_events' etc.
209
+	 *
210
+	 * @var array
211
+	 */
212
+	protected $_cap_contexts_to_cap_action_map = [
213
+		self::caps_read       => 'read',
214
+		self::caps_read_admin => 'read',
215
+		self::caps_edit       => 'edit',
216
+		self::caps_delete     => 'delete',
217
+	];
218
+
219
+	/**
220
+	 * Timezone
221
+	 * This gets set via the constructor so that we know what timezone incoming strings|timestamps are in when there
222
+	 * are EE_Datetime_Fields in use.  This can also be used before a get to set what timezone you want strings coming
223
+	 * out of the created objects.  NOT all EEM_Base child classes use this property but any that use a
224
+	 * EE_Datetime_Field data type will have access to it.
225
+	 *
226
+	 * @var string
227
+	 */
228
+	protected $_timezone;
229
+
230
+
231
+	/**
232
+	 * This holds the id of the blog currently making the query.  Has no bearing on single site but is used for
233
+	 * multisite.
234
+	 *
235
+	 * @var int
236
+	 */
237
+	protected static $_model_query_blog_id;
238
+
239
+	/**
240
+	 * A copy of _fields, except the array keys are the model names pointed to by
241
+	 * the field
242
+	 *
243
+	 * @var EE_Model_Field_Base[]
244
+	 */
245
+	private $_cache_foreign_key_to_fields = [];
246
+
247
+	/**
248
+	 * Cached list of all the fields on the model, indexed by their name
249
+	 *
250
+	 * @var EE_Model_Field_Base[]
251
+	 */
252
+	private $_cached_fields = null;
253
+
254
+	/**
255
+	 * Cached list of all the fields on the model, except those that are
256
+	 * marked as only pertinent to the database
257
+	 *
258
+	 * @var EE_Model_Field_Base[]
259
+	 */
260
+	private $_cached_fields_non_db_only = null;
261
+
262
+	/**
263
+	 * A cached reference to the primary key for quick lookup
264
+	 *
265
+	 * @var EE_Model_Field_Base
266
+	 */
267
+	private $_primary_key_field = null;
268
+
269
+	/**
270
+	 * Flag indicating whether this model has a primary key or not
271
+	 *
272
+	 * @var boolean
273
+	 */
274
+	protected $_has_primary_key_field = null;
275
+
276
+	/**
277
+	 * array in the format:  [ FK alias => full PK ]
278
+	 * where keys are local column name aliases for foreign keys
279
+	 * and values are the fully qualified column name for the primary key they represent
280
+	 *  ex:
281
+	 *      [ 'Event.EVT_wp_user' => 'WP_User.ID' ]
282
+	 *
283
+	 * @var array $foreign_key_aliases
284
+	 */
285
+	protected $foreign_key_aliases = [];
286
+
287
+	/**
288
+	 * Whether or not this model is based off a table in WP core only (CPTs should set
289
+	 * this to FALSE, but if we were to make an EE_WP_Post model, it should set this to true).
290
+	 * This should be true for models that deal with data that should exist independent of EE.
291
+	 * For example, if the model can read and insert data that isn't used by EE, this should be true.
292
+	 * It would be false, however, if you could guarantee the model would only interact with EE data,
293
+	 * even if it uses a WP core table (eg event and venue models set this to false for that reason:
294
+	 * they can only read and insert events and venues custom post types, not arbitrary post types)
295
+	 *
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 = [
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 = ['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 = ['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
+	 *
373
+	 * @var array
374
+	 */
375
+	protected $_like_style_operators = ['LIKE', 'NOT LIKE'];
376
+
377
+	/**
378
+	 * operators that are used for handling NUll and !NULL queries.  Typically used for when checking if a row exists
379
+	 * on a join table.
380
+	 *
381
+	 * @var array
382
+	 */
383
+	protected $_null_style_operators = ['IS NOT NULL', 'IS NULL'];
384
+
385
+	/**
386
+	 * Allowed values for $query_params['order'] for ordering in queries
387
+	 *
388
+	 * @var array
389
+	 */
390
+	protected $_allowed_order_values = ['asc', 'desc', 'ASC', 'DESC'];
391
+
392
+	/**
393
+	 * When these are keys in a WHERE or HAVING clause, they are handled much differently
394
+	 * than regular field names. It is assumed that their values are an array of WHERE conditions
395
+	 *
396
+	 * @var array
397
+	 */
398
+	private $_logic_query_param_keys = ['not', 'and', 'or', 'NOT', 'AND', 'OR'];
399
+
400
+	/**
401
+	 * Allowed keys in $query_params arrays passed into queries. Note that 0 is meant to always be a
402
+	 * 'where', but 'where' clauses are so common that we thought we'd omit it
403
+	 *
404
+	 * @var array
405
+	 */
406
+	private $_allowed_query_params = [
407
+		0,
408
+		'limit',
409
+		'order_by',
410
+		'group_by',
411
+		'having',
412
+		'force_join',
413
+		'order',
414
+		'on_join_limit',
415
+		'default_where_conditions',
416
+		'caps',
417
+		'extra_selects',
418
+		'exclude_protected',
419
+	];
420
+
421
+	/**
422
+	 * All the data types that can be used in $wpdb->prepare statements.
423
+	 *
424
+	 * @var array
425
+	 */
426
+	private $_valid_wpdb_data_types = ['%d', '%s', '%f'];
427
+
428
+	/**
429
+	 * @var EE_Registry $EE
430
+	 */
431
+	protected $EE = null;
432
+
433
+
434
+	/**
435
+	 * Property which, when set, will have this model echo out the next X queries to the page for debugging.
436
+	 *
437
+	 * @var int
438
+	 */
439
+	protected $_show_next_x_db_queries = 0;
440
+
441
+	/**
442
+	 * When using _get_all_wpdb_results, you can specify a custom selection. If you do so,
443
+	 * it gets saved on this property as an instance of CustomSelects so those selections can be used in
444
+	 * WHERE, GROUP_BY, etc.
445
+	 *
446
+	 * @var CustomSelects
447
+	 */
448
+	protected $_custom_selections = [];
449
+
450
+	/**
451
+	 * key => value Entity Map using  array( EEM_Base::$_model_query_blog_id => array( ID => model object ) )
452
+	 * caches every model object we've fetched from the DB on this request
453
+	 *
454
+	 * @var array
455
+	 */
456
+	protected $_entity_map;
457
+
458
+	/**
459
+	 * @var LoaderInterface
460
+	 */
461
+	protected static $loader;
462
+
463
+	/**
464
+	 * @var Mirror
465
+	 */
466
+	private static $mirror;
467
+
468
+
469
+	/**
470
+	 * constant used to show EEM_Base has not yet verified the db on this http request
471
+	 */
472
+	const db_verified_none = 0;
473
+
474
+	/**
475
+	 * constant used to show EEM_Base has verified the EE core db on this http request,
476
+	 * but not the addons' dbs
477
+	 */
478
+	const db_verified_core = 1;
479
+
480
+	/**
481
+	 * constant used to show EEM_Base has verified the addons' dbs (and implicitly
482
+	 * the EE core db too)
483
+	 */
484
+	const db_verified_addons = 2;
485
+
486
+	/**
487
+	 * indicates whether an EEM_Base child has already re-verified the DB
488
+	 * is ok (we don't want to do it repetitively). Should be set to one the constants
489
+	 * looking like EEM_Base::db_verified_*
490
+	 *
491
+	 * @var int - 0 = none, 1 = core, 2 = addons
492
+	 */
493
+	protected static $_db_verification_level = EEM_Base::db_verified_none;
494
+
495
+	/**
496
+	 * @const constant for 'default_where_conditions' to apply default where conditions to ALL queried models
497
+	 *        (eg, if retrieving registrations ordered by their datetimes, this will only return non-trashed
498
+	 *        registrations for non-trashed tickets for non-trashed datetimes)
499
+	 */
500
+	const default_where_conditions_all = 'all';
501
+
502
+	/**
503
+	 * @const constant for 'default_where_conditions' to apply default where conditions to THIS model only, but
504
+	 *        no other models which are joined to (eg, if retrieving registrations ordered by their datetimes, this will
505
+	 *        return non-trashed registrations, regardless of the related datetimes and tickets' statuses).
506
+	 *        It is preferred to use EEM_Base::default_where_conditions_minimum_others because, when joining to
507
+	 *        models which share tables with other models, this can return data for the wrong model.
508
+	 */
509
+	const default_where_conditions_this_only = 'this_model_only';
510
+
511
+	/**
512
+	 * @const constant for 'default_where_conditions' to apply default where conditions to other models queried,
513
+	 *        but not the current model (eg, if retrieving registrations ordered by their datetimes, this will
514
+	 *        return all registrations related to non-trashed tickets and non-trashed datetimes)
515
+	 */
516
+	const default_where_conditions_others_only = 'other_models_only';
517
+
518
+	/**
519
+	 * @const constant for 'default_where_conditions' to apply minimum where conditions to all models queried.
520
+	 *        For most models this the same as EEM_Base::default_where_conditions_none, except for models which share
521
+	 *        their table with other models, like the Event and Venue models. For example, when querying for events
522
+	 *        ordered by their venues' name, this will be sure to only return real events with associated real venues
523
+	 *        (regardless of whether those events and venues are trashed)
524
+	 *        In contrast, using EEM_Base::default_where_conditions_none would could return WP posts other than EE
525
+	 *        events.
526
+	 */
527
+	const default_where_conditions_minimum_all = 'minimum';
528
+
529
+	/**
530
+	 * @const constant for 'default_where_conditions' to apply apply where conditions to other models, and full default
531
+	 *        where conditions for the queried model (eg, when querying events ordered by venues' names, this will
532
+	 *        return non-trashed events for any venues, regardless of whether those associated venues are trashed or
533
+	 *        not)
534
+	 */
535
+	const default_where_conditions_minimum_others = 'full_this_minimum_others';
536
+
537
+	/**
538
+	 * @const constant for 'default_where_conditions' to NOT apply any where conditions. This should very rarely be
539
+	 *        used, because when querying from a model which shares its table with another model (eg Events and Venues)
540
+	 *        it's possible it will return table entries for other models. You should use
541
+	 *        EEM_Base::default_where_conditions_minimum_all instead.
542
+	 */
543
+	const default_where_conditions_none = 'none';
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
+				$table_obj->_construct_finalize_set_table_to_join_with($this->_get_main_table());
596
+			}
597
+		}
598
+		/**
599
+		 * Filters the list of fields on a model. It is best to NOT use this directly and instead just use
600
+		 * EE_Register_Model_Extension
601
+		 *
602
+		 * @param EE_Model_Field_Base[] $_fields
603
+		 */
604
+		$this->_fields = (array) apply_filters('FHEE__' . get_class($this) . '__construct__fields', $this->_fields);
605
+		$this->_invalidate_field_caches();
606
+		foreach ($this->_fields as $table_alias => $fields_for_table) {
607
+			if (! array_key_exists($table_alias, $this->_tables)) {
608
+				throw new EE_Error(
609
+					sprintf(
610
+						esc_html__(
611
+							"Table alias %s does not exist in EEM_Base child's _tables array. Only tables defined are %s",
612
+							'event_espresso'
613
+						),
614
+						$table_alias,
615
+						implode(",", $this->_fields)
616
+					)
617
+				);
618
+			}
619
+			foreach ($fields_for_table as $field_name => $field_obj) {
620
+				/** @var $field_obj EE_Model_Field_Base | EE_Primary_Key_Field_Base */
621
+				// primary key field base has a slightly different _construct_finalize
622
+				/** @var $field_obj EE_Model_Field_Base */
623
+				$field_obj->_construct_finalize($table_alias, $field_name, $this->get_this_model_name());
624
+			}
625
+		}
626
+		// everything is related to Extra_Meta
627
+		if (get_class($this) !== 'EEM_Extra_Meta') {
628
+			// make extra meta related to everything, but don't block deleting things just
629
+			// because they have related extra meta info. For now just orphan those extra meta
630
+			// in the future we should automatically delete them
631
+			$this->_model_relations['Extra_Meta'] = new EE_Has_Many_Any_Relation(false);
632
+		}
633
+		// and change logs
634
+		if (get_class($this) !== 'EEM_Change_Log') {
635
+			$this->_model_relations['Change_Log'] = new EE_Has_Many_Any_Relation(false);
636
+		}
637
+		/**
638
+		 * Filters the list of relations on a model. It is best to NOT use this directly and instead just use
639
+		 * EE_Register_Model_Extension
640
+		 *
641
+		 * @param EE_Model_Relation_Base[] $_model_relations
642
+		 */
643
+		$this->_model_relations = (array) apply_filters(
644
+			'FHEE__' . get_class($this) . '__construct__model_relations',
645
+			$this->_model_relations
646
+		);
647
+		foreach ($this->_model_relations as $model_name => $relation_obj) {
648
+			/** @var $relation_obj EE_Model_Relation_Base */
649
+			$relation_obj->_construct_finalize_set_models($this->get_this_model_name(), $model_name);
650
+		}
651
+		foreach ($this->_indexes as $index_name => $index_obj) {
652
+			$index_obj->_construct_finalize($index_name, $this->get_this_model_name());
653
+		}
654
+		$this->set_timezone($timezone);
655
+		// finalize default where condition strategy, or set default
656
+		if (! $this->_default_where_conditions_strategy) {
657
+			// nothing was set during child constructor, so set default
658
+			$this->_default_where_conditions_strategy = new EE_Default_Where_Conditions();
659
+		}
660
+		$this->_default_where_conditions_strategy->_finalize_construct($this);
661
+		if (! $this->_minimum_where_conditions_strategy) {
662
+			// nothing was set during child constructor, so set default
663
+			$this->_minimum_where_conditions_strategy = new EE_Default_Where_Conditions();
664
+		}
665
+		$this->_minimum_where_conditions_strategy->_finalize_construct($this);
666
+		// if the cap slug hasn't been set, and we haven't set it to false on purpose
667
+		// to indicate to NOT set it, set it to the logical default
668
+		if ($this->_caps_slug === null) {
669
+			$this->_caps_slug = EEH_Inflector::pluralize_and_lower($this->get_this_model_name());
670
+		}
671
+		// initialize the standard cap restriction generators if none were specified by the child constructor
672
+		if (is_array($this->_cap_restriction_generators)) {
673
+			foreach ($this->cap_contexts_to_cap_action_map() as $cap_context => $action) {
674
+				if (! isset($this->_cap_restriction_generators[ $cap_context ])) {
675
+					$this->_cap_restriction_generators[ $cap_context ] = apply_filters(
676
+						'FHEE__EEM_Base___construct__standard_cap_restriction_generator',
677
+						new EE_Restriction_Generator_Protected(),
678
+						$cap_context,
679
+						$this
680
+					);
681
+				}
682
+			}
683
+		}
684
+		// if there are cap restriction generators, use them to make the default cap restrictions
685
+		if (is_array($this->_cap_restriction_generators)) {
686
+			foreach ($this->_cap_restriction_generators as $context => $generator_object) {
687
+				if (! $generator_object) {
688
+					continue;
689
+				}
690
+				if (! $generator_object instanceof EE_Restriction_Generator_Base) {
691
+					throw new EE_Error(
692
+						sprintf(
693
+							esc_html__(
694
+								'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.',
695
+								'event_espresso'
696
+							),
697
+							$context,
698
+							$this->get_this_model_name()
699
+						)
700
+					);
701
+				}
702
+				$action = $this->cap_action_for_context($context);
703
+				if (! $generator_object->construction_finalized()) {
704
+					$generator_object->_construct_finalize($this, $action);
705
+				}
706
+			}
707
+		}
708
+		do_action('AHEE__' . get_class($this) . '__construct__end');
709
+	}
710
+
711
+
712
+	/**
713
+	 * @return LoaderInterface
714
+	 * @throws InvalidArgumentException
715
+	 * @throws InvalidDataTypeException
716
+	 * @throws InvalidInterfaceException
717
+	 */
718
+	protected static function getLoader(): LoaderInterface
719
+	{
720
+		if (! EEM_Base::$loader instanceof LoaderInterface) {
721
+			EEM_Base::$loader = LoaderFactory::getLoader();
722
+		}
723
+		return EEM_Base::$loader;
724
+	}
725
+
726
+
727
+	/**
728
+	 * @return Mirror
729
+	 * @since   $VID:$
730
+	 */
731
+	private static function getMirror(): Mirror
732
+	{
733
+		if (! EEM_Base::$mirror instanceof Mirror) {
734
+			EEM_Base::$mirror = EEM_Base::getLoader()->getShared(Mirror::class);
735
+		}
736
+		return EEM_Base::$mirror;
737
+	}
738
+
739
+
740
+	/**
741
+	 * @param string $model_class_Name
742
+	 * @param string $timezone
743
+	 * @return array
744
+	 * @throws ReflectionException
745
+	 * @since   $VID:$
746
+	 */
747
+	private static function getModelArguments(string $model_class_Name, string $timezone): array
748
+	{
749
+		$arguments = [$timezone];
750
+		$params    = EEM_Base::getMirror()->getParameters($model_class_Name);
751
+		if (count($params) > 1) {
752
+			if ($params[1]->getName() === 'model_field_factory') {
753
+				$arguments = [
754
+					$timezone,
755
+					EEM_Base::getLoader()->getShared(ModelFieldFactory::class),
756
+				];
757
+			} elseif ($model_class_Name === 'EEM_Form_Section') {
758
+				$arguments = [
759
+					EEM_Base::getLoader()->getShared('EventEspresso\core\services\form\meta\FormStatus'),
760
+					$timezone,
761
+				];
762
+			} elseif ($model_class_Name === 'EEM_Form_Element') {
763
+				$arguments = [
764
+					EEM_Base::getLoader()->getShared('EventEspresso\core\services\form\meta\FormStatus'),
765
+					EEM_Base::getLoader()->getShared('EventEspresso\core\services\form\meta\InputTypes'),
766
+					$timezone,
767
+				];
768
+			}
769
+		}
770
+		return $arguments;
771
+	}
772
+
773
+
774
+	/**
775
+	 * This function is a singleton method used to instantiate the Espresso_model object
776
+	 *
777
+	 * @param string|null $timezone   string representing the timezone we want to set for returned Date Time Strings
778
+	 *                                (and any incoming timezone data that gets saved).
779
+	 *                                Note this just sends the timezone info to the date time model field objects.
780
+	 *                                Default is NULL
781
+	 *                                (and will be assumed using the set timezone in the 'timezone_string' wp option)
782
+	 * @return static (as in the concrete child class)
783
+	 * @throws EE_Error
784
+	 * @throws ReflectionException
785
+	 */
786
+	public static function instance($timezone = null)
787
+	{
788
+		// check if instance of Espresso_model already exists
789
+		if (! static::$_instance instanceof static) {
790
+			$arguments = EEM_Base::getModelArguments(static::class, (string) $timezone);
791
+			$model     = new static(...$arguments);
792
+			EEM_Base::getLoader()->share(static::class, $model, $arguments);
793
+			static::$_instance = $model;
794
+		}
795
+		// we might have a timezone set, let set_timezone decide what to do with it
796
+		if ($timezone) {
797
+			static::$_instance->set_timezone($timezone);
798
+		}
799
+		// Espresso_model object
800
+		return static::$_instance;
801
+	}
802
+
803
+
804
+	/**
805
+	 * resets the model and returns it
806
+	 *
807
+	 * @param string|null $timezone
808
+	 * @return EEM_Base|null (if the model was already instantiated, returns it, with
809
+	 * all its properties reset; if it wasn't instantiated, returns null)
810
+	 * @throws EE_Error
811
+	 * @throws ReflectionException
812
+	 * @throws InvalidArgumentException
813
+	 * @throws InvalidDataTypeException
814
+	 * @throws InvalidInterfaceException
815
+	 */
816
+	public static function reset($timezone = null)
817
+	{
818
+		if (! static::$_instance instanceof EEM_Base) {
819
+			return null;
820
+		}
821
+		// Let's NOT swap out the current instance for a new one
822
+		// because if someone has a reference to it, we can't remove their reference.
823
+		// It's best to keep using the same reference but change the original object instead,
824
+		// so reset all its properties to their original values as defined in the class.
825
+		$static_properties = EEM_Base::getMirror()->getStaticProperties(static::class);
826
+		foreach (EEM_Base::getMirror()->getDefaultProperties(static::class) as $property => $value) {
827
+			// don't set instance to null like it was originally,
828
+			// but it's static anyways, and we're ignoring static properties (for now at least)
829
+			if (! isset($static_properties[ $property ])) {
830
+				static::$_instance->{$property} = $value;
831
+			}
832
+		}
833
+		// and then directly call its constructor again, like we would if we were creating a new one
834
+		$arguments = EEM_Base::getModelArguments(static::class, (string) $timezone);
835
+		static::$_instance->__construct(...$arguments);
836
+		return self::instance();
837
+	}
838
+
839
+
840
+	/**
841
+	 * Used to set the $_model_query_blog_id static property.
842
+	 *
843
+	 * @param int $blog_id  If provided then will set the blog_id for the models to this id.  If not provided then the
844
+	 *                      value for get_current_blog_id() will be used.
845
+	 */
846
+	public static function set_model_query_blog_id($blog_id = 0)
847
+	{
848
+		EEM_Base::$_model_query_blog_id = $blog_id > 0
849
+			? (int) $blog_id
850
+			: get_current_blog_id();
851
+	}
852
+
853
+
854
+	/**
855
+	 * Returns whatever is set as the internal $model_query_blog_id.
856
+	 *
857
+	 * @return int
858
+	 */
859
+	public static function get_model_query_blog_id()
860
+	{
861
+		return EEM_Base::$_model_query_blog_id;
862
+	}
863
+
864
+
865
+	/**
866
+	 * retrieve the status details from esp_status table as an array IF this model has the status table as a relation.
867
+	 *
868
+	 * @param boolean $translated return localized strings or JUST the array.
869
+	 * @return array
870
+	 * @throws EE_Error
871
+	 * @throws InvalidArgumentException
872
+	 * @throws InvalidDataTypeException
873
+	 * @throws InvalidInterfaceException
874
+	 * @throws ReflectionException
875
+	 */
876
+	public function status_array($translated = false)
877
+	{
878
+		if (! array_key_exists('Status', $this->_model_relations)) {
879
+			return [];
880
+		}
881
+		$model_name   = $this->get_this_model_name();
882
+		$status_type  = str_replace(' ', '_', strtolower(str_replace('_', ' ', $model_name)));
883
+		$stati        = EEM_Status::instance()->get_all([['STS_type' => $status_type]]);
884
+		$status_array = [];
885
+		foreach ($stati as $status) {
886
+			$status_array[ $status->ID() ] = $status->get('STS_code');
887
+		}
888
+		return $translated
889
+			? EEM_Status::instance()->localized_status($status_array, false, 'sentence')
890
+			: $status_array;
891
+	}
892
+
893
+
894
+	/**
895
+	 * Gets all the EE_Base_Class objects which match the $query_params, by querying the DB.
896
+	 *
897
+	 * @param array $query_params             @see
898
+	 *                                        https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
899
+	 *                                        or if you have the development copy of EE you can view this at the path:
900
+	 *                                        /docs/G--Model-System/model-query-params.md
901
+	 * @return EE_Base_Class[]  *note that there is NO option to pass the output type. If you want results different
902
+	 *                                        from EE_Base_Class[], use get_all_wpdb_results(). Array keys are object
903
+	 *                                        IDs (if there is a primary key on the model. if not, numerically indexed)
904
+	 *                                        Some full examples: get 10 transactions which have Scottish attendees:
905
+	 *                                        EEM_Transaction::instance()->get_all( array( array(
906
+	 *                                        'OR'=>array(
907
+	 *                                        'Registration.Attendee.ATT_fname'=>array('like','Mc%'),
908
+	 *                                        'Registration.Attendee.ATT_fname*other'=>array('like','Mac%')
909
+	 *                                        )
910
+	 *                                        ),
911
+	 *                                        'limit'=>10,
912
+	 *                                        'group_by'=>'TXN_ID'
913
+	 *                                        ));
914
+	 *                                        get all the answers to the question titled "shirt size" for event with id
915
+	 *                                        12, ordered by their answer EEM_Answer::instance()->get_all(array( array(
916
+	 *                                        'Question.QST_display_text'=>'shirt size',
917
+	 *                                        'Registration.Event.EVT_ID'=>12
918
+	 *                                        ),
919
+	 *                                        'order_by'=>array('ANS_value'=>'ASC')
920
+	 *                                        ));
921
+	 * @throws EE_Error
922
+	 * @throws ReflectionException
923
+	 */
924
+	public function get_all($query_params = [])
925
+	{
926
+		if (
927
+			isset($query_params['limit'])
928
+			&& ! isset($query_params['group_by'])
929
+		) {
930
+			$query_params['group_by'] = array_keys($this->get_combined_primary_key_fields());
931
+		}
932
+		return $this->_create_objects($this->_get_all_wpdb_results($query_params));
933
+	}
934
+
935
+
936
+	/**
937
+	 * Modifies the query parameters so we only get back model objects
938
+	 * that "belong" to the current user
939
+	 *
940
+	 * @param array $query_params @see
941
+	 *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
942
+	 * @return array @see
943
+	 *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
944
+	 * @throws ReflectionException
945
+	 * @throws ReflectionException
946
+	 */
947
+	public function alter_query_params_to_only_include_mine($query_params = [])
948
+	{
949
+		$wp_user_field_name = $this->wp_user_field_name();
950
+		if ($wp_user_field_name) {
951
+			$query_params[0][ $wp_user_field_name ] = get_current_user_id();
952
+		}
953
+		return $query_params;
954
+	}
955
+
956
+
957
+	/**
958
+	 * Returns the name of the field's name that points to the WP_User table
959
+	 *  on this model (or follows the _model_chain_to_wp_user and uses that model's
960
+	 * foreign key to the WP_User table)
961
+	 *
962
+	 * @return string|boolean string on success, boolean false when there is no
963
+	 * foreign key to the WP_User table
964
+	 * @throws ReflectionException
965
+	 * @throws ReflectionException
966
+	 */
967
+	public function wp_user_field_name()
968
+	{
969
+		try {
970
+			if (! empty($this->_model_chain_to_wp_user)) {
971
+				$models_to_follow_to_wp_users = explode('.', $this->_model_chain_to_wp_user);
972
+				$last_model_name              = end($models_to_follow_to_wp_users);
973
+				$model_with_fk_to_wp_users    = EE_Registry::instance()->load_model($last_model_name);
974
+				$model_chain_to_wp_user       = $this->_model_chain_to_wp_user . '.';
975
+			} else {
976
+				$model_with_fk_to_wp_users = $this;
977
+				$model_chain_to_wp_user    = '';
978
+			}
979
+			$wp_user_field = $model_with_fk_to_wp_users->get_foreign_key_to('WP_User');
980
+			return $model_chain_to_wp_user . $wp_user_field->get_name();
981
+		} catch (EE_Error $e) {
982
+			return false;
983
+		}
984
+	}
985
+
986
+
987
+	/**
988
+	 * Returns the _model_chain_to_wp_user string, which indicates which related model
989
+	 * (or transiently-related model) has a foreign key to the wp_users table;
990
+	 * useful for finding if model objects of this type are 'owned' by the current user.
991
+	 * This is an empty string when the foreign key is on this model and when it isn't,
992
+	 * but is only non-empty when this model's ownership is indicated by a RELATED model
993
+	 * (or transiently-related model)
994
+	 *
995
+	 * @return string
996
+	 */
997
+	public function model_chain_to_wp_user()
998
+	{
999
+		return $this->_model_chain_to_wp_user;
1000
+	}
1001
+
1002
+
1003
+	/**
1004
+	 * Whether this model is 'owned' by a specific wordpress user (even indirectly,
1005
+	 * like how registrations don't have a foreign key to wp_users, but the
1006
+	 * events they are for are), or is unrelated to wp users.
1007
+	 * generally available
1008
+	 *
1009
+	 * @return boolean
1010
+	 */
1011
+	public function is_owned()
1012
+	{
1013
+		if ($this->model_chain_to_wp_user()) {
1014
+			return true;
1015
+		}
1016
+		try {
1017
+			$this->get_foreign_key_to('WP_User');
1018
+			return true;
1019
+		} catch (EE_Error $e) {
1020
+			return false;
1021
+		}
1022
+	}
1023
+
1024
+
1025
+	/**
1026
+	 * Used internally to get WPDB results, because other functions, besides get_all, may want to do some queries, but
1027
+	 * may want to preserve the WPDB results (eg, update, which first queries to make sure we have all the tables on
1028
+	 * the model)
1029
+	 *
1030
+	 * @param array  $query_params      @see
1031
+	 *                                  https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1032
+	 * @param string $output            ARRAY_A, OBJECT_K, etc. Just like
1033
+	 * @param mixed  $columns_to_select , What columns to select. By default, we select all columns specified by the
1034
+	 *                                  fields on the model, and the models we joined to in the query. However, you can
1035
+	 *                                  override this and set the select to "*", or a specific column name, like
1036
+	 *                                  "ATT_ID", etc. If you would like to use these custom selections in WHERE,
1037
+	 *                                  GROUP_BY, or HAVING clauses, you must instead provide an array. Array keys are
1038
+	 *                                  the aliases used to refer to this selection, and values are to be
1039
+	 *                                  numerically-indexed arrays, where 0 is the selection and 1 is the data type.
1040
+	 *                                  Eg, array('count'=>array('COUNT(REG_ID)','%d'))
1041
+	 * @return array | stdClass[] like results of $wpdb->get_results($sql,OBJECT), (ie, output type is OBJECT)
1042
+	 * @throws EE_Error
1043
+	 * @throws InvalidArgumentException
1044
+	 */
1045
+	protected function _get_all_wpdb_results($query_params = [], $output = ARRAY_A, $columns_to_select = null)
1046
+	{
1047
+		$this->_custom_selections = $this->getCustomSelection($query_params, $columns_to_select);
1048
+		$model_query_info         = $this->_create_model_query_info_carrier($query_params);
1049
+		$select_expressions       = $columns_to_select === null
1050
+			? $this->_construct_default_select_sql($model_query_info)
1051
+			: '';
1052
+		if ($this->_custom_selections instanceof CustomSelects) {
1053
+			$custom_expressions = $this->_custom_selections->columnsToSelectExpression();
1054
+			$select_expressions .= $select_expressions
1055
+				? ', ' . $custom_expressions
1056
+				: $custom_expressions;
1057
+		}
1058
+
1059
+		$SQL = "SELECT $select_expressions " . $this->_construct_2nd_half_of_select_query($model_query_info);
1060
+		return $this->_do_wpdb_query('get_results', [$SQL, $output]);
1061
+	}
1062
+
1063
+
1064
+	/**
1065
+	 * Get a CustomSelects object if the $query_params or $columns_to_select allows for it.
1066
+	 * Note: $query_params['extra_selects'] will always override any $columns_to_select values. It is the preferred
1067
+	 * method of including extra select information.
1068
+	 *
1069
+	 * @param array             $query_params
1070
+	 * @param null|array|string $columns_to_select
1071
+	 * @return null|CustomSelects
1072
+	 * @throws InvalidArgumentException
1073
+	 */
1074
+	protected function getCustomSelection(array $query_params, $columns_to_select = null)
1075
+	{
1076
+		if (! isset($query_params['extra_selects']) && $columns_to_select === null) {
1077
+			return null;
1078
+		}
1079
+		$selects = isset($query_params['extra_selects']) ? $query_params['extra_selects'] : $columns_to_select;
1080
+		$selects = is_string($selects) ? explode(',', $selects) : $selects;
1081
+		return new CustomSelects($selects);
1082
+	}
1083
+
1084
+
1085
+	/**
1086
+	 * Gets an array of rows from the database just like $wpdb->get_results would,
1087
+	 * but you can use the model query params to more easily
1088
+	 * take care of joins, field preparation etc.
1089
+	 *
1090
+	 * @param array  $query_params      @see
1091
+	 *                                  https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1092
+	 * @param string $output            ARRAY_A, OBJECT_K, etc. Just like
1093
+	 * @param mixed  $columns_to_select , What columns to select. By default, we select all columns specified by the
1094
+	 *                                  fields on the model, and the models we joined to in the query. However, you can
1095
+	 *                                  override this and set the select to "*", or a specific column name, like
1096
+	 *                                  "ATT_ID", etc. If you would like to use these custom selections in WHERE,
1097
+	 *                                  GROUP_BY, or HAVING clauses, you must instead provide an array. Array keys are
1098
+	 *                                  the aliases used to refer to this selection, and values are to be
1099
+	 *                                  numerically-indexed arrays, where 0 is the selection and 1 is the data type.
1100
+	 *                                  Eg, array('count'=>array('COUNT(REG_ID)','%d'))
1101
+	 * @return array|stdClass[] like results of $wpdb->get_results($sql,OBJECT), (ie, output type is OBJECT)
1102
+	 * @throws EE_Error
1103
+	 */
1104
+	public function get_all_wpdb_results($query_params = [], $output = ARRAY_A, $columns_to_select = null)
1105
+	{
1106
+		return $this->_get_all_wpdb_results($query_params, $output, $columns_to_select);
1107
+	}
1108
+
1109
+
1110
+	/**
1111
+	 * For creating a custom select statement
1112
+	 *
1113
+	 * @param mixed $columns_to_select either a string to be inserted directly as the select statement,
1114
+	 *                                 or an array where keys are aliases, and values are arrays where 0=>the selection
1115
+	 *                                 SQL, and 1=>is the datatype
1116
+	 * @return string
1117
+	 * @throws EE_Error
1118
+	 */
1119
+	private function _construct_select_from_input($columns_to_select)
1120
+	{
1121
+		if (is_array($columns_to_select)) {
1122
+			$select_sql_array = [];
1123
+			foreach ($columns_to_select as $alias => $selection_and_datatype) {
1124
+				if (! is_array($selection_and_datatype) || ! isset($selection_and_datatype[1])) {
1125
+					throw new EE_Error(
1126
+						sprintf(
1127
+							esc_html__(
1128
+								"Custom selection %s (alias %s) needs to be an array like array('COUNT(REG_ID)','%%d')",
1129
+								'event_espresso'
1130
+							),
1131
+							$selection_and_datatype,
1132
+							$alias
1133
+						)
1134
+					);
1135
+				}
1136
+				if (! in_array($selection_and_datatype[1], $this->_valid_wpdb_data_types, true)) {
1137
+					throw new EE_Error(
1138
+						sprintf(
1139
+							esc_html__(
1140
+								"Datatype %s (for selection '%s' and alias '%s') is not a valid wpdb datatype (eg %%s)",
1141
+								'event_espresso'
1142
+							),
1143
+							$selection_and_datatype[1],
1144
+							$selection_and_datatype[0],
1145
+							$alias,
1146
+							implode(', ', $this->_valid_wpdb_data_types)
1147
+						)
1148
+					);
1149
+				}
1150
+				$select_sql_array[] = "{$selection_and_datatype[0]} AS $alias";
1151
+			}
1152
+			$columns_to_select_string = implode(', ', $select_sql_array);
1153
+		} else {
1154
+			$columns_to_select_string = $columns_to_select;
1155
+		}
1156
+		return $columns_to_select_string;
1157
+	}
1158
+
1159
+
1160
+	/**
1161
+	 * Convenient wrapper for getting the primary key field's name. Eg, on Registration, this would be 'REG_ID'
1162
+	 *
1163
+	 * @return string
1164
+	 * @throws EE_Error
1165
+	 */
1166
+	public function primary_key_name()
1167
+	{
1168
+		return $this->get_primary_key_field()->get_name();
1169
+	}
1170
+
1171
+
1172
+	/**
1173
+	 * Gets a single item for this model from the DB, given only its ID (or null if none is found).
1174
+	 * If there is no primary key on this model, $id is treated as primary key string
1175
+	 *
1176
+	 * @param mixed $id int or string, depending on the type of the model's primary key
1177
+	 * @return EE_Base_Class|mixed|null
1178
+	 * @throws EE_Error
1179
+	 * @throws ReflectionException
1180
+	 */
1181
+	public function get_one_by_ID($id)
1182
+	{
1183
+		if ($this->get_from_entity_map($id)) {
1184
+			return $this->get_from_entity_map($id);
1185
+		}
1186
+		$model_object = $this->get_one(
1187
+			$this->alter_query_params_to_restrict_by_ID(
1188
+				$id,
1189
+				['default_where_conditions' => EEM_Base::default_where_conditions_minimum_all]
1190
+			)
1191
+		);
1192
+		$className    = $this->_get_class_name();
1193
+		if ($model_object instanceof $className) {
1194
+			// make sure valid objects get added to the entity map
1195
+			// so that the next call to this method doesn't trigger another trip to the db
1196
+			$this->add_to_entity_map($model_object);
1197
+		}
1198
+		return $model_object;
1199
+	}
1200
+
1201
+
1202
+	/**
1203
+	 * Alters query parameters to only get items with this ID are returned.
1204
+	 * Takes into account that the ID might be a string produced by EEM_Base::get_index_primary_key_string(),
1205
+	 * or could just be a simple primary key ID
1206
+	 *
1207
+	 * @param int   $id
1208
+	 * @param array $query_params
1209
+	 * @return array of normal query params, @see
1210
+	 *               https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1211
+	 * @throws EE_Error
1212
+	 */
1213
+	public function alter_query_params_to_restrict_by_ID($id, $query_params = [])
1214
+	{
1215
+		if (! isset($query_params[0])) {
1216
+			$query_params[0] = [];
1217
+		}
1218
+		$conditions_from_id = $this->parse_index_primary_key_string($id);
1219
+		if ($conditions_from_id === null) {
1220
+			$query_params[0][ $this->primary_key_name() ] = $id;
1221
+		} else {
1222
+			// no primary key, so the $id must be from the get_index_primary_key_string()
1223
+			$query_params[0] = array_replace_recursive($query_params[0], $this->parse_index_primary_key_string($id));
1224
+		}
1225
+		return $query_params;
1226
+	}
1227
+
1228
+
1229
+	/**
1230
+	 * Gets a single item for this model from the DB, given the $query_params. Only returns a single class, not an
1231
+	 * array. If no item is found, null is returned.
1232
+	 *
1233
+	 * @param array $query_params like EEM_Base's $query_params variable.
1234
+	 * @return EE_Base_Class|EE_Soft_Delete_Base_Class|NULL
1235
+	 * @throws EE_Error
1236
+	 */
1237
+	public function get_one($query_params = [])
1238
+	{
1239
+		if (! is_array($query_params)) {
1240
+			EE_Error::doing_it_wrong(
1241
+				'EEM_Base::get_one',
1242
+				sprintf(
1243
+					esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1244
+					gettype($query_params)
1245
+				),
1246
+				'4.6.0'
1247
+			);
1248
+			$query_params = [];
1249
+		}
1250
+		$query_params['limit'] = 1;
1251
+		$items                 = $this->get_all($query_params);
1252
+		if (empty($items)) {
1253
+			return null;
1254
+		}
1255
+		return array_shift($items);
1256
+	}
1257
+
1258
+
1259
+	/**
1260
+	 * Returns the next x number of items in sequence from the given value as
1261
+	 * found in the database matching the given query conditions.
1262
+	 *
1263
+	 * @param mixed $current_field_value    Value used for the reference point.
1264
+	 * @param null  $field_to_order_by      What field is used for the
1265
+	 *                                      reference point.
1266
+	 * @param int   $limit                  How many to return.
1267
+	 * @param array $query_params           Extra conditions on the query.
1268
+	 * @param null  $columns_to_select      If left null, then an array of
1269
+	 *                                      EE_Base_Class objects is returned,
1270
+	 *                                      otherwise you can indicate just the
1271
+	 *                                      columns you want returned.
1272
+	 * @return EE_Base_Class[]|array
1273
+	 * @throws EE_Error
1274
+	 */
1275
+	public function next_x(
1276
+		$current_field_value,
1277
+		$field_to_order_by = null,
1278
+		$limit = 1,
1279
+		$query_params = [],
1280
+		$columns_to_select = null
1281
+	) {
1282
+		return $this->_get_consecutive(
1283
+			$current_field_value,
1284
+			'>',
1285
+			$field_to_order_by,
1286
+			$limit,
1287
+			$query_params,
1288
+			$columns_to_select
1289
+		);
1290
+	}
1291
+
1292
+
1293
+	/**
1294
+	 * Returns the previous x number of items in sequence from the given value
1295
+	 * as found in the database matching the given query conditions.
1296
+	 *
1297
+	 * @param mixed $current_field_value    Value used for the reference point.
1298
+	 * @param null  $field_to_order_by      What field is used for the
1299
+	 *                                      reference point.
1300
+	 * @param int   $limit                  How many to return.
1301
+	 * @param array $query_params           Extra conditions on the query.
1302
+	 * @param null  $columns_to_select      If left null, then an array of
1303
+	 *                                      EE_Base_Class objects is returned,
1304
+	 *                                      otherwise you can indicate just the
1305
+	 *                                      columns you want returned.
1306
+	 * @return EE_Base_Class[]|array
1307
+	 * @throws EE_Error
1308
+	 */
1309
+	public function previous_x(
1310
+		$current_field_value,
1311
+		$field_to_order_by = null,
1312
+		$limit = 1,
1313
+		$query_params = [],
1314
+		$columns_to_select = null
1315
+	) {
1316
+		return $this->_get_consecutive(
1317
+			$current_field_value,
1318
+			'<',
1319
+			$field_to_order_by,
1320
+			$limit,
1321
+			$query_params,
1322
+			$columns_to_select
1323
+		);
1324
+	}
1325
+
1326
+
1327
+	/**
1328
+	 * Returns the next item in sequence from the given value as found in the
1329
+	 * database matching the given query conditions.
1330
+	 *
1331
+	 * @param mixed $current_field_value    Value used for the reference point.
1332
+	 * @param null  $field_to_order_by      What field is used for the
1333
+	 *                                      reference point.
1334
+	 * @param array $query_params           Extra conditions on the query.
1335
+	 * @param null  $columns_to_select      If left null, then an EE_Base_Class
1336
+	 *                                      object is returned, otherwise you
1337
+	 *                                      can indicate just the columns you
1338
+	 *                                      want and a single array indexed by
1339
+	 *                                      the columns will be returned.
1340
+	 * @return EE_Base_Class|null|array()
1341
+	 * @throws EE_Error
1342
+	 */
1343
+	public function next(
1344
+		$current_field_value,
1345
+		$field_to_order_by = null,
1346
+		$query_params = [],
1347
+		$columns_to_select = null
1348
+	) {
1349
+		$results = $this->_get_consecutive(
1350
+			$current_field_value,
1351
+			'>',
1352
+			$field_to_order_by,
1353
+			1,
1354
+			$query_params,
1355
+			$columns_to_select
1356
+		);
1357
+		return empty($results) ? null : reset($results);
1358
+	}
1359
+
1360
+
1361
+	/**
1362
+	 * Returns the previous item in sequence from the given value as found in
1363
+	 * the database matching the given query conditions.
1364
+	 *
1365
+	 * @param mixed $current_field_value    Value used for the reference point.
1366
+	 * @param null  $field_to_order_by      What field is used for the
1367
+	 *                                      reference point.
1368
+	 * @param array $query_params           Extra conditions on the query.
1369
+	 * @param null  $columns_to_select      If left null, then an EE_Base_Class
1370
+	 *                                      object is returned, otherwise you
1371
+	 *                                      can indicate just the columns you
1372
+	 *                                      want and a single array indexed by
1373
+	 *                                      the columns will be returned.
1374
+	 * @return EE_Base_Class|null|array()
1375
+	 * @throws EE_Error
1376
+	 */
1377
+	public function previous(
1378
+		$current_field_value,
1379
+		$field_to_order_by = null,
1380
+		$query_params = [],
1381
+		$columns_to_select = null
1382
+	) {
1383
+		$results = $this->_get_consecutive(
1384
+			$current_field_value,
1385
+			'<',
1386
+			$field_to_order_by,
1387
+			1,
1388
+			$query_params,
1389
+			$columns_to_select
1390
+		);
1391
+		return empty($results) ? null : reset($results);
1392
+	}
1393
+
1394
+
1395
+	/**
1396
+	 * Returns the a consecutive number of items in sequence from the given
1397
+	 * value as found in the database matching the given query conditions.
1398
+	 *
1399
+	 * @param mixed  $current_field_value   Value used for the reference point.
1400
+	 * @param string $operand               What operand is used for the sequence.
1401
+	 * @param string $field_to_order_by     What field is used for the reference point.
1402
+	 * @param int    $limit                 How many to return.
1403
+	 * @param array  $query_params          Extra conditions on the query.
1404
+	 * @param null   $columns_to_select     If left null, then an array of EE_Base_Class objects is returned,
1405
+	 *                                      otherwise you can indicate just the columns you want returned.
1406
+	 * @return EE_Base_Class[]|array
1407
+	 * @throws EE_Error
1408
+	 */
1409
+	protected function _get_consecutive(
1410
+		$current_field_value,
1411
+		$operand = '>',
1412
+		$field_to_order_by = null,
1413
+		$limit = 1,
1414
+		$query_params = [],
1415
+		$columns_to_select = null
1416
+	) {
1417
+		// if $field_to_order_by is empty then let's assume we're ordering by the primary key.
1418
+		if (empty($field_to_order_by)) {
1419
+			if ($this->has_primary_key_field()) {
1420
+				$field_to_order_by = $this->get_primary_key_field()->get_name();
1421
+			} else {
1422
+				if (WP_DEBUG) {
1423
+					throw new EE_Error(
1424
+						esc_html__(
1425
+							'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).',
1426
+							'event_espresso'
1427
+						)
1428
+					);
1429
+				}
1430
+				EE_Error::add_error(
1431
+					esc_html__('There was an error with the query.', 'event_espresso'),
1432
+					__FILE__,
1433
+					__FUNCTION__,
1434
+					__LINE__
1435
+				);
1436
+				return [];
1437
+			}
1438
+		}
1439
+		if (! is_array($query_params)) {
1440
+			EE_Error::doing_it_wrong(
1441
+				'EEM_Base::_get_consecutive',
1442
+				sprintf(
1443
+					esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1444
+					gettype($query_params)
1445
+				),
1446
+				'4.6.0'
1447
+			);
1448
+			$query_params = [];
1449
+		}
1450
+		// let's add the where query param for consecutive look up.
1451
+		$query_params[0][ $field_to_order_by ] = [$operand, $current_field_value];
1452
+		$query_params['limit']                 = $limit;
1453
+		// set direction
1454
+		$incoming_orderby         = isset($query_params['order_by']) ? (array) $query_params['order_by'] : [];
1455
+		$query_params['order_by'] = $operand === '>'
1456
+			? [$field_to_order_by => 'ASC'] + $incoming_orderby
1457
+			: [$field_to_order_by => 'DESC'] + $incoming_orderby;
1458
+		// if $columns_to_select is empty then that means we're returning EE_Base_Class objects
1459
+		if (empty($columns_to_select)) {
1460
+			return $this->get_all($query_params);
1461
+		}
1462
+		// getting just the fields
1463
+		return $this->_get_all_wpdb_results($query_params, ARRAY_A, $columns_to_select);
1464
+	}
1465
+
1466
+
1467
+	/**
1468
+	 * This sets the _timezone property after model object has been instantiated.
1469
+	 *
1470
+	 * @param null | string $timezone valid PHP DateTimeZone timezone string
1471
+	 */
1472
+	public function set_timezone($timezone)
1473
+	{
1474
+		if ($timezone !== null) {
1475
+			$this->_timezone = $timezone;
1476
+		}
1477
+		// note we need to loop through relations and set the timezone on those objects as well.
1478
+		foreach ($this->_model_relations as $relation) {
1479
+			$relation->set_timezone($timezone);
1480
+		}
1481
+		// and finally we do the same for any datetime fields
1482
+		foreach ($this->_fields as $field) {
1483
+			if ($field instanceof EE_Datetime_Field) {
1484
+				$field->set_timezone($timezone);
1485
+			}
1486
+		}
1487
+	}
1488
+
1489
+
1490
+	/**
1491
+	 * This just returns whatever is set for the current timezone.
1492
+	 *
1493
+	 * @access public
1494
+	 * @return string
1495
+	 */
1496
+	public function get_timezone()
1497
+	{
1498
+		// first validate if timezone is set.  If not, then let's set it be whatever is set on the model fields.
1499
+		if (empty($this->_timezone)) {
1500
+			foreach ($this->_fields as $field) {
1501
+				if ($field instanceof EE_Datetime_Field) {
1502
+					$this->set_timezone($field->get_timezone());
1503
+					break;
1504
+				}
1505
+			}
1506
+		}
1507
+		// if timezone STILL empty then return the default timezone for the site.
1508
+		if (empty($this->_timezone)) {
1509
+			$this->set_timezone(EEH_DTT_Helper::get_timezone());
1510
+		}
1511
+		return $this->_timezone;
1512
+	}
1513
+
1514
+
1515
+	/**
1516
+	 * This returns the date formats set for the given field name and also ensures that
1517
+	 * $this->_timezone property is set correctly.
1518
+	 *
1519
+	 * @param string $field_name The name of the field the formats are being retrieved for.
1520
+	 * @param bool   $pretty     Whether to return the pretty formats (true) or not (false).
1521
+	 * @return array formats in an array with the date format first, and the time format last.
1522
+	 * @throws EE_Error   If the given field_name is not of the EE_Datetime_Field type.
1523
+	 * @since 4.6.x
1524
+	 */
1525
+	public function get_formats_for($field_name, $pretty = false)
1526
+	{
1527
+		$field_settings = $this->field_settings_for($field_name);
1528
+		// if not a valid EE_Datetime_Field then throw error
1529
+		if (! $field_settings instanceof EE_Datetime_Field) {
1530
+			throw new EE_Error(
1531
+				sprintf(
1532
+					esc_html__(
1533
+						'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.',
1534
+						'event_espresso'
1535
+					),
1536
+					$field_name
1537
+				)
1538
+			);
1539
+		}
1540
+		// while we are here, let's make sure the timezone internally in EEM_Base matches what is stored on
1541
+		// the field.
1542
+		$this->_timezone = $field_settings->get_timezone();
1543
+		return [$field_settings->get_date_format($pretty), $field_settings->get_time_format($pretty)];
1544
+	}
1545
+
1546
+
1547
+	/**
1548
+	 * This returns the current time in a format setup for a query on this model.
1549
+	 * Usage of this method makes it easier to setup queries against EE_Datetime_Field columns because
1550
+	 * it will return:
1551
+	 *  - a formatted string in the timezone and format currently set on the EE_Datetime_Field for the given field for
1552
+	 *  NOW
1553
+	 *  - or a unix timestamp (equivalent to time())
1554
+	 * Note: When requesting a formatted string, if the date or time format doesn't include seconds, for example,
1555
+	 * the time returned, because it uses that format, will also NOT include seconds. For this reason, if you want
1556
+	 * the time returned to be the current time down to the exact second, set $timestamp to true.
1557
+	 *
1558
+	 * @param string $field_name       The field the current time is needed for.
1559
+	 * @param bool   $timestamp        True means to return a unix timestamp. Otherwise a
1560
+	 *                                 formatted string matching the set format for the field in the set timezone will
1561
+	 *                                 be returned.
1562
+	 * @param string $what             Whether to return the string in just the time format, the date format, or both.
1563
+	 * @return int|string  If the given field_name is not of the EE_Datetime_Field type, then an EE_Error
1564
+	 *                                 exception is triggered.
1565
+	 * @throws EE_Error    If the given field_name is not of the EE_Datetime_Field type.
1566
+	 * @throws Exception
1567
+	 * @since 4.6.x
1568
+	 */
1569
+	public function current_time_for_query($field_name, $timestamp = false, $what = 'both')
1570
+	{
1571
+		$formats  = $this->get_formats_for($field_name);
1572
+		$DateTime = new DateTime("now", new DateTimeZone($this->_timezone));
1573
+		if ($timestamp) {
1574
+			return $DateTime->format('U');
1575
+		}
1576
+		// not returning timestamp, so return formatted string in timezone.
1577
+		switch ($what) {
1578
+			case 'time':
1579
+				return $DateTime->format($formats[1]);
1580
+			case 'date':
1581
+				return $DateTime->format($formats[0]);
1582
+			default:
1583
+				return $DateTime->format(implode(' ', $formats));
1584
+		}
1585
+	}
1586
+
1587
+
1588
+	/**
1589
+	 * This receives a time string for a given field and ensures that it is setup to match what the internal settings
1590
+	 * for the model are.  Returns a DateTime object.
1591
+	 * Note: a gotcha for when you send in unix timestamp.  Remember a unix timestamp is already timezone agnostic,
1592
+	 * (functionally the equivalent of UTC+0).  So when you send it in, whatever timezone string you include is
1593
+	 * ignored.
1594
+	 *
1595
+	 * @param string $field_name      The field being setup.
1596
+	 * @param string $timestring      The date time string being used.
1597
+	 * @param string $incoming_format The format for the time string.
1598
+	 * @param string $timezone        By default, it is assumed the incoming time string is in timezone for
1599
+	 *                                the blog.  If this is not the case, then it can be specified here.  If incoming
1600
+	 *                                format is
1601
+	 *                                'U', this is ignored.
1602
+	 * @return DateTime
1603
+	 * @throws EE_Error
1604
+	 */
1605
+	public function convert_datetime_for_query($field_name, $timestring, $incoming_format, $timezone = '')
1606
+	{
1607
+		// just using this to ensure the timezone is set correctly internally
1608
+		$this->get_formats_for($field_name);
1609
+		// load EEH_DTT_Helper
1610
+		$set_timezone     = empty($timezone) ? EEH_DTT_Helper::get_timezone() : $timezone;
1611
+		$incomingDateTime = date_create_from_format($incoming_format, $timestring, new DateTimeZone($set_timezone));
1612
+		EEH_DTT_Helper::setTimezone($incomingDateTime, new DateTimeZone($this->_timezone));
1613
+		return \EventEspresso\core\domain\entities\DbSafeDateTime::createFromDateTime($incomingDateTime);
1614
+	}
1615
+
1616
+
1617
+	/**
1618
+	 * Gets all the tables comprising this model. Array keys are the table aliases, and values are EE_Table objects
1619
+	 *
1620
+	 * @return EE_Table_Base[]
1621
+	 */
1622
+	public function get_tables()
1623
+	{
1624
+		return $this->_tables;
1625
+	}
1626
+
1627
+
1628
+	/**
1629
+	 * Updates all the database entries (in each table for this model) according to $fields_n_values and optionally
1630
+	 * also updates all the model objects, where the criteria expressed in $query_params are met..
1631
+	 * Also note: if this model has multiple tables, this update verifies all the secondary tables have an entry for
1632
+	 * each row (in the primary table) we're trying to update; if not, it inserts an entry in the secondary table. Eg:
1633
+	 * if our model has 2 tables: wp_posts (primary), and wp_esp_event (secondary). Let's say we are trying to update a
1634
+	 * model object with EVT_ID = 1
1635
+	 * (which means where wp_posts has ID = 1, because wp_posts.ID is the primary key's column), which exists, but
1636
+	 * there is no entry in wp_esp_event for this entry in wp_posts. So, this update script will insert a row into
1637
+	 * wp_esp_event, using any available parameters from $fields_n_values (eg, if "EVT_limit" => 40 is in
1638
+	 * $fields_n_values, the new entry in wp_esp_event will set EVT_limit = 40, and use default for other columns which
1639
+	 * are not specified)
1640
+	 *
1641
+	 * @param array   $fields_n_values         keys are model fields (exactly like keys in EEM_Base::_fields, NOT db
1642
+	 *                                         columns!), values are strings, ints, floats, and maybe arrays if they
1643
+	 *                                         are to be serialized. Basically, the values are what you'd expect to be
1644
+	 *                                         values on the model, NOT necessarily what's in the DB. For example, if
1645
+	 *                                         we wanted to update only the TXN_details on any Transactions where its
1646
+	 *                                         ID=34, we'd use this method as follows:
1647
+	 *                                         EEM_Transaction::instance()->update(
1648
+	 *                                         array('TXN_details'=>array('detail1'=>'monkey','detail2'=>'banana'),
1649
+	 *                                         array(array('TXN_ID'=>34)));
1650
+	 * @param array   $query_params            @see
1651
+	 *                                         https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1652
+	 *                                         Eg, consider updating Question's QST_admin_label field is of type
1653
+	 *                                         Simple_HTML. If you use this function to update that field to $new_value
1654
+	 *                                         = (note replace 8's with appropriate opening and closing tags in the
1655
+	 *                                         following example)"8script8alert('I hack all');8/script88b8boom
1656
+	 *                                         baby8/b8", then if you set $values_already_prepared_by_model_object to
1657
+	 *                                         TRUE, it is assumed that you've already called
1658
+	 *                                         EE_Simple_HTML_Field->prepare_for_set($new_value), which removes the
1659
+	 *                                         malicious javascript. However, if
1660
+	 *                                         $values_already_prepared_by_model_object is left as FALSE, then
1661
+	 *                                         EE_Simple_HTML_Field->prepare_for_set($new_value) will be called on it,
1662
+	 *                                         and every other field, before insertion. We provide this parameter
1663
+	 *                                         because model objects perform their prepare_for_set function on all
1664
+	 *                                         their values, and so don't need to be called again (and in many cases,
1665
+	 *                                         shouldn't be called again. Eg: if we escape HTML characters in the
1666
+	 *                                         prepare_for_set method...)
1667
+	 * @param boolean $keep_model_objs_in_sync if TRUE, makes sure we ALSO update model objects
1668
+	 *                                         in this model's entity map according to $fields_n_values that match
1669
+	 *                                         $query_params. This obviously has some overhead, so you can disable it
1670
+	 *                                         by setting this to FALSE, but be aware that model objects being used
1671
+	 *                                         could get out-of-sync with the database
1672
+	 * @return int how many rows got updated or FALSE if something went wrong with the query (wp returns FALSE or num
1673
+	 *                                         rows affected which *could* include 0 which DOES NOT mean the query was
1674
+	 *                                         bad)
1675
+	 * @throws EE_Error
1676
+	 * @throws ReflectionException
1677
+	 */
1678
+	public function update($fields_n_values, $query_params, $keep_model_objs_in_sync = true)
1679
+	{
1680
+		if (! is_array($query_params)) {
1681
+			EE_Error::doing_it_wrong(
1682
+				'EEM_Base::update',
1683
+				sprintf(
1684
+					esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
1685
+					gettype($query_params)
1686
+				),
1687
+				'4.6.0'
1688
+			);
1689
+			$query_params = [];
1690
+		}
1691
+		/**
1692
+		 * Action called before a model update call has been made.
1693
+		 *
1694
+		 * @param EEM_Base $model
1695
+		 * @param array    $fields_n_values the updated fields and their new values
1696
+		 * @param array    $query_params    @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1697
+		 */
1698
+		do_action('AHEE__EEM_Base__update__begin', $this, $fields_n_values, $query_params);
1699
+		/**
1700
+		 * Filters the fields about to be updated given the query parameters. You can provide the
1701
+		 * $query_params to $this->get_all() to find exactly which records will be updated
1702
+		 *
1703
+		 * @param array    $fields_n_values fields and their new values
1704
+		 * @param EEM_Base $model           the model being queried
1705
+		 * @param array    $query_params    @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1706
+		 */
1707
+		$fields_n_values = (array) apply_filters(
1708
+			'FHEE__EEM_Base__update__fields_n_values',
1709
+			$fields_n_values,
1710
+			$this,
1711
+			$query_params
1712
+		);
1713
+		// need to verify that, for any entry we want to update, there are entries in each secondary table.
1714
+		// to do that, for each table, verify that it's PK isn't null.
1715
+		$tables = $this->get_tables();
1716
+		// 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
1717
+		// NOTE: we should make this code more efficient by NOT querying twice
1718
+		// before the real update, but that needs to first go through ALPHA testing
1719
+		// as it's dangerous. says Mike August 8 2014
1720
+		// we want to make sure the default_where strategy is ignored
1721
+		$this->_ignore_where_strategy = true;
1722
+		$wpdb_select_results          = $this->_get_all_wpdb_results($query_params);
1723
+		foreach ($wpdb_select_results as $wpdb_result) {
1724
+			// type cast stdClass as array
1725
+			$wpdb_result = (array) $wpdb_result;
1726
+			// get the model object's PK, as we'll want this if we need to insert a row into secondary tables
1727
+			if ($this->has_primary_key_field()) {
1728
+				$main_table_pk_value = $wpdb_result[ $this->get_primary_key_field()->get_qualified_column() ];
1729
+			} else {
1730
+				// 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)
1731
+				$main_table_pk_value = null;
1732
+			}
1733
+			// 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
1734
+			// 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
1735
+			if (count($tables) > 1) {
1736
+				// foreach matching row in the DB, ensure that each table's PK isn't null. If so, there must not be an entry
1737
+				// in that table, and so we'll want to insert one
1738
+				foreach ($tables as $table_obj) {
1739
+					$this_table_pk_column = $table_obj->get_fully_qualified_pk_column();
1740
+					// if there is no private key for this table on the results, it means there's no entry
1741
+					// in this table, right? so insert a row in the current table, using any fields available
1742
+					if (
1743
+						! (array_key_exists($this_table_pk_column, $wpdb_result)
1744
+						   && $wpdb_result[ $this_table_pk_column ])
1745
+					) {
1746
+						$success = $this->_insert_into_specific_table(
1747
+							$table_obj,
1748
+							$fields_n_values,
1749
+							$main_table_pk_value
1750
+						);
1751
+						// if we died here, report the error
1752
+						if (! $success) {
1753
+							return false;
1754
+						}
1755
+					}
1756
+				}
1757
+			}
1758
+			//              //and now check that if we have cached any models by that ID on the model, that
1759
+			//              //they also get updated properly
1760
+			//              $model_object = $this->get_from_entity_map( $main_table_pk_value );
1761
+			//              if( $model_object ){
1762
+			//                  foreach( $fields_n_values as $field => $value ){
1763
+			//                      $model_object->set($field, $value);
1764
+			// let's make sure default_where strategy is followed now
1765
+			$this->_ignore_where_strategy = false;
1766
+		}
1767
+		// if we want to keep model objects in sync, AND
1768
+		// if this wasn't called from a model object (to update itself)
1769
+		// then we want to make sure we keep all the existing
1770
+		// model objects in sync with the db
1771
+		if ($keep_model_objs_in_sync && ! $this->_values_already_prepared_by_model_object) {
1772
+			if ($this->has_primary_key_field()) {
1773
+				$model_objs_affected_ids = $this->get_col($query_params);
1774
+			} else {
1775
+				// we need to select a bunch of columns and then combine them into the the "index primary key string"s
1776
+				$models_affected_key_columns = $this->_get_all_wpdb_results($query_params, ARRAY_A);
1777
+				$model_objs_affected_ids     = [];
1778
+				foreach ($models_affected_key_columns as $row) {
1779
+					$combined_index_key                             = $this->get_index_primary_key_string($row);
1780
+					$model_objs_affected_ids[ $combined_index_key ] = $combined_index_key;
1781
+				}
1782
+			}
1783
+			if (! $model_objs_affected_ids) {
1784
+				// wait wait wait- if nothing was affected let's stop here
1785
+				return 0;
1786
+			}
1787
+			foreach ($model_objs_affected_ids as $id) {
1788
+				$model_obj_in_entity_map = $this->get_from_entity_map($id);
1789
+				if ($model_obj_in_entity_map) {
1790
+					foreach ($fields_n_values as $field => $new_value) {
1791
+						$model_obj_in_entity_map->set($field, $new_value);
1792
+					}
1793
+				}
1794
+			}
1795
+			// if there is a primary key on this model, we can now do a slight optimization
1796
+			if ($this->has_primary_key_field()) {
1797
+				// we already know what we want to update. So let's make the query simpler so it's a little more efficient
1798
+				$query_params = [
1799
+					[$this->primary_key_name() => ['IN', $model_objs_affected_ids]],
1800
+					'limit'                    => count($model_objs_affected_ids),
1801
+					'default_where_conditions' => EEM_Base::default_where_conditions_none,
1802
+				];
1803
+			}
1804
+		}
1805
+		$model_query_info = $this->_create_model_query_info_carrier($query_params);
1806
+		$SQL              = "UPDATE "
1807
+							. $model_query_info->get_full_join_sql()
1808
+							. " SET "
1809
+							. $this->_construct_update_sql($fields_n_values)
1810
+							. $model_query_info->get_where_sql(
1811
+							);// note: doesn't use _construct_2nd_half_of_select_query() because doesn't accept LIMIT, ORDER BY, etc.
1812
+		$rows_affected    = $this->_do_wpdb_query('query', [$SQL]);
1813
+		/**
1814
+		 * Action called after a model update call has been made.
1815
+		 *
1816
+		 * @param EEM_Base $model
1817
+		 * @param array    $fields_n_values the updated fields and their new values
1818
+		 * @param array    $query_params    @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1819
+		 * @param int      $rows_affected
1820
+		 */
1821
+		do_action('AHEE__EEM_Base__update__end', $this, $fields_n_values, $query_params, $rows_affected);
1822
+		return $rows_affected;// how many supposedly got updated
1823
+	}
1824
+
1825
+
1826
+	/**
1827
+	 * Analogous to $wpdb->get_col, returns a 1-dimensional array where teh values
1828
+	 * are teh values of the field specified (or by default the primary key field)
1829
+	 * that matched the query params. Note that you should pass the name of the
1830
+	 * model FIELD, not the database table's column name.
1831
+	 *
1832
+	 * @param array  $query_params @see
1833
+	 *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1834
+	 * @param string $field_to_select
1835
+	 * @return array just like $wpdb->get_col()
1836
+	 * @throws EE_Error
1837
+	 */
1838
+	public function get_col($query_params = [], $field_to_select = null)
1839
+	{
1840
+		if ($field_to_select) {
1841
+			$field = $this->field_settings_for($field_to_select);
1842
+		} elseif ($this->has_primary_key_field()) {
1843
+			$field = $this->get_primary_key_field();
1844
+		} else {
1845
+			$field_settings = $this->field_settings();
1846
+			// no primary key, just grab the first column
1847
+			$field = reset($field_settings);
1848
+			// don't need this array now
1849
+			unset($field_settings);
1850
+		}
1851
+		$model_query_info   = $this->_create_model_query_info_carrier($query_params);
1852
+		$select_expressions = $field->get_qualified_column();
1853
+		$SQL                =
1854
+			"SELECT $select_expressions " . $this->_construct_2nd_half_of_select_query($model_query_info);
1855
+		return $this->_do_wpdb_query('get_col', [$SQL]);
1856
+	}
1857
+
1858
+
1859
+	/**
1860
+	 * Returns a single column value for a single row from the database
1861
+	 *
1862
+	 * @param array  $query_params    @see
1863
+	 *                                https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1864
+	 * @param string $field_to_select @see EEM_Base::get_col()
1865
+	 * @return string
1866
+	 * @throws EE_Error
1867
+	 */
1868
+	public function get_var($query_params = [], $field_to_select = null)
1869
+	{
1870
+		$query_params['limit'] = 1;
1871
+		$col                   = $this->get_col($query_params, $field_to_select);
1872
+		if (! empty($col)) {
1873
+			return reset($col);
1874
+		}
1875
+		return null;
1876
+	}
1877
+
1878
+
1879
+	/**
1880
+	 * Makes the SQL for after "UPDATE table_X inner join table_Y..." and before "...WHERE". Eg "Question.name='party
1881
+	 * time?', Question.desc='what do you think?',..." Values are filtered through wpdb->prepare to avoid against SQL
1882
+	 * injection, but currently no further filtering is done
1883
+	 *
1884
+	 * @param array $fields_n_values array keys are field names on this model, and values are what those fields should
1885
+	 *                               be updated to in the DB
1886
+	 * @return string of SQL
1887
+	 * @throws EE_Error
1888
+	 * @global      $wpdb
1889
+	 */
1890
+	public function _construct_update_sql($fields_n_values)
1891
+	{
1892
+		/** @type WPDB $wpdb */
1893
+		global $wpdb;
1894
+		$cols_n_values = [];
1895
+		foreach ($fields_n_values as $field_name => $value) {
1896
+			$field_obj = $this->field_settings_for($field_name);
1897
+			// if the value is NULL, we want to assign the value to that.
1898
+			// wpdb->prepare doesn't really handle that properly
1899
+			$prepared_value  = $this->_prepare_value_or_use_default($field_obj, $fields_n_values);
1900
+			$value_sql       = $prepared_value === null ? 'NULL'
1901
+				: $wpdb->prepare($field_obj->get_wpdb_data_type(), $prepared_value);
1902
+			$cols_n_values[] = $field_obj->get_qualified_column() . "=" . $value_sql;
1903
+		}
1904
+		return implode(",", $cols_n_values);
1905
+	}
1906
+
1907
+
1908
+	/**
1909
+	 * Deletes a single row from the DB given the model object's primary key value. (eg, EE_Attendee->ID()'s value).
1910
+	 * Performs a HARD delete, meaning the database row should always be removed,
1911
+	 * not just have a flag field on it switched
1912
+	 * Wrapper for EEM_Base::delete_permanently()
1913
+	 *
1914
+	 * @param mixed   $id
1915
+	 * @param boolean $allow_blocking
1916
+	 * @return int the number of rows deleted
1917
+	 * @throws EE_Error
1918
+	 * @throws ReflectionException
1919
+	 */
1920
+	public function delete_permanently_by_ID($id, $allow_blocking = true)
1921
+	{
1922
+		return $this->delete_permanently(
1923
+			[
1924
+				[$this->get_primary_key_field()->get_name() => $id],
1925
+				'limit' => 1,
1926
+			],
1927
+			$allow_blocking
1928
+		);
1929
+	}
1930
+
1931
+
1932
+	/**
1933
+	 * Deletes a single row from the DB given the model object's primary key value. (eg, EE_Attendee->ID()'s value).
1934
+	 * Wrapper for EEM_Base::delete()
1935
+	 *
1936
+	 * @param mixed   $id
1937
+	 * @param boolean $allow_blocking
1938
+	 * @return int the number of rows deleted
1939
+	 * @throws EE_Error
1940
+	 */
1941
+	public function delete_by_ID($id, $allow_blocking = true)
1942
+	{
1943
+		return $this->delete(
1944
+			[
1945
+				[$this->get_primary_key_field()->get_name() => $id],
1946
+				'limit' => 1,
1947
+			],
1948
+			$allow_blocking
1949
+		);
1950
+	}
1951
+
1952
+
1953
+	/**
1954
+	 * Identical to delete_permanently, but does a "soft" delete if possible,
1955
+	 * meaning if the model has a field that indicates its been "trashed" or
1956
+	 * "soft deleted", we will just set that instead of actually deleting the rows.
1957
+	 *
1958
+	 * @param array   $query_params
1959
+	 * @param boolean $allow_blocking
1960
+	 * @return int how many rows got deleted
1961
+	 * @throws EE_Error
1962
+	 * @throws ReflectionException
1963
+	 * @see EEM_Base::delete_permanently
1964
+	 */
1965
+	public function delete($query_params, $allow_blocking = true)
1966
+	{
1967
+		return $this->delete_permanently($query_params, $allow_blocking);
1968
+	}
1969
+
1970
+
1971
+	/**
1972
+	 * Deletes the model objects that meet the query params. Note: this method is overridden
1973
+	 * in EEM_Soft_Delete_Base so that soft-deleted model objects are instead only flagged
1974
+	 * as archived, not actually deleted
1975
+	 *
1976
+	 * @param array   $query_params   @see
1977
+	 *                                https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1978
+	 * @param boolean $allow_blocking if TRUE, matched objects will only be deleted if there is no related model info
1979
+	 *                                that blocks it (ie, there' sno other data that depends on this data); if false,
1980
+	 *                                deletes regardless of other objects which may depend on it. Its generally
1981
+	 *                                advisable to always leave this as TRUE, otherwise you could easily corrupt your
1982
+	 *                                DB
1983
+	 * @return int how many rows got deleted
1984
+	 * @throws EE_Error
1985
+	 * @throws ReflectionException
1986
+	 */
1987
+	public function delete_permanently($query_params, $allow_blocking = true)
1988
+	{
1989
+		/**
1990
+		 * Action called just before performing a real deletion query. You can use the
1991
+		 * model and its $query_params to find exactly which items will be deleted
1992
+		 *
1993
+		 * @param EEM_Base $model
1994
+		 * @param array    $query_params   @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
1995
+		 * @param boolean  $allow_blocking whether or not to allow related model objects
1996
+		 *                                 to block (prevent) this deletion
1997
+		 */
1998
+		do_action('AHEE__EEM_Base__delete__begin', $this, $query_params, $allow_blocking);
1999
+		// some MySQL databases may be running safe mode, which may restrict
2000
+		// deletion if there is no KEY column used in the WHERE statement of a deletion.
2001
+		// to get around this, we first do a SELECT, get all the IDs, and then run another query
2002
+		// to delete them
2003
+		$items_for_deletion           = $this->_get_all_wpdb_results($query_params);
2004
+		$columns_and_ids_for_deleting = $this->_get_ids_for_delete($items_for_deletion, $allow_blocking);
2005
+		$deletion_where_query_part    = $this->_build_query_part_for_deleting_from_columns_and_values(
2006
+			$columns_and_ids_for_deleting
2007
+		);
2008
+		/**
2009
+		 * Allows client code to act on the items being deleted before the query is actually executed.
2010
+		 *
2011
+		 * @param EEM_Base $this                            The model instance being acted on.
2012
+		 * @param array    $query_params                    The incoming array of query parameters influencing what gets deleted.
2013
+		 * @param bool     $allow_blocking                  @see param description in method phpdoc block.
2014
+		 * @param array    $columns_and_ids_for_deleting    An array indicating what entities will get removed as
2015
+		 *                                                  derived from the incoming query parameters.
2016
+		 * @see details on the structure of this array in the phpdocs
2017
+		 *                                                  for the `_get_ids_for_delete_method`
2018
+		 *
2019
+		 */
2020
+		do_action(
2021
+			'AHEE__EEM_Base__delete__before_query',
2022
+			$this,
2023
+			$query_params,
2024
+			$allow_blocking,
2025
+			$columns_and_ids_for_deleting
2026
+		);
2027
+		if ($deletion_where_query_part) {
2028
+			$model_query_info = $this->_create_model_query_info_carrier($query_params);
2029
+			$table_aliases    = array_keys($this->_tables);
2030
+			$SQL              = "DELETE "
2031
+								. implode(", ", $table_aliases)
2032
+								. " FROM "
2033
+								. $model_query_info->get_full_join_sql()
2034
+								. " WHERE "
2035
+								. $deletion_where_query_part;
2036
+			$rows_deleted     = $this->_do_wpdb_query('query', [$SQL]);
2037
+		} else {
2038
+			$rows_deleted = 0;
2039
+		}
2040
+
2041
+		// Next, make sure those items are removed from the entity map; if they could be put into it at all; and if
2042
+		// there was no error with the delete query.
2043
+		if (
2044
+			$this->has_primary_key_field()
2045
+			&& $rows_deleted !== false
2046
+			&& isset($columns_and_ids_for_deleting[ $this->get_primary_key_field()->get_qualified_column() ])
2047
+		) {
2048
+			$ids_for_removal = $columns_and_ids_for_deleting[ $this->get_primary_key_field()->get_qualified_column() ];
2049
+			foreach ($ids_for_removal as $id) {
2050
+				if (isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])) {
2051
+					unset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ]);
2052
+				}
2053
+			}
2054
+
2055
+			// delete any extra meta attached to the deleted entities but ONLY if this model is not an instance of
2056
+			// `EEM_Extra_Meta`.  In other words we want to prevent recursion on EEM_Extra_Meta::delete_permanently calls
2057
+			// unnecessarily.  It's very unlikely that users will have assigned Extra Meta to Extra Meta
2058
+			// (although it is possible).
2059
+			// Note this can be skipped by using the provided filter and returning false.
2060
+			if (
2061
+				apply_filters(
2062
+					'FHEE__EEM_Base__delete_permanently__dont_delete_extra_meta_for_extra_meta',
2063
+					! $this instanceof EEM_Extra_Meta,
2064
+					$this
2065
+				)
2066
+			) {
2067
+				EEM_Extra_Meta::instance()->delete_permanently([
2068
+																   0 => [
2069
+																	   'EXM_type' => $this->get_this_model_name(),
2070
+																	   'OBJ_ID'   => [
2071
+																		   'IN',
2072
+																		   $ids_for_removal,
2073
+																	   ],
2074
+																   ],
2075
+															   ]);
2076
+			}
2077
+		}
2078
+
2079
+		/**
2080
+		 * Action called just after performing a real deletion query. Although at this point the
2081
+		 * items should have been deleted
2082
+		 *
2083
+		 * @param EEM_Base $model
2084
+		 * @param array    $query_params @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2085
+		 * @param int      $rows_deleted
2086
+		 */
2087
+		do_action('AHEE__EEM_Base__delete__end', $this, $query_params, $rows_deleted, $columns_and_ids_for_deleting);
2088
+		return $rows_deleted;// how many supposedly got deleted
2089
+	}
2090
+
2091
+
2092
+	/**
2093
+	 * Checks all the relations that throw error messages when there are blocking related objects
2094
+	 * for related model objects. If there are any related model objects on those relations,
2095
+	 * adds an EE_Error, and return true
2096
+	 *
2097
+	 * @param EE_Base_Class|int $this_model_obj_or_id
2098
+	 * @param EE_Base_Class     $ignore_this_model_obj a model object like 'EE_Event', or 'EE_Term_Taxonomy', which
2099
+	 *                                                 should be ignored when determining whether there are related
2100
+	 *                                                 model objects which block this model object's deletion. Useful
2101
+	 *                                                 if you know A is related to B and are considering deleting A,
2102
+	 *                                                 but want to see if A has any other objects blocking its deletion
2103
+	 *                                                 before removing the relation between A and B
2104
+	 * @return boolean
2105
+	 * @throws EE_Error
2106
+	 * @throws ReflectionException
2107
+	 */
2108
+	public function delete_is_blocked_by_related_models($this_model_obj_or_id, $ignore_this_model_obj = null)
2109
+	{
2110
+		// first, if $ignore_this_model_obj was supplied, get its model
2111
+		if ($ignore_this_model_obj && $ignore_this_model_obj instanceof EE_Base_Class) {
2112
+			$ignored_model = $ignore_this_model_obj->get_model();
2113
+		} else {
2114
+			$ignored_model = null;
2115
+		}
2116
+		// now check all the relations of $this_model_obj_or_id and see if there
2117
+		// are any related model objects blocking it?
2118
+		$is_blocked = false;
2119
+		foreach ($this->_model_relations as $relation_name => $relation_obj) {
2120
+			if ($relation_obj->block_delete_if_related_models_exist()) {
2121
+				// if $ignore_this_model_obj was supplied, then for the query
2122
+				// on that model needs to be told to ignore $ignore_this_model_obj
2123
+				if ($ignored_model && $relation_name === $ignored_model->get_this_model_name()) {
2124
+					$related_model_objects = $relation_obj->get_all_related($this_model_obj_or_id, [
2125
+						[
2126
+							$ignored_model->get_primary_key_field()->get_name() => [
2127
+								'!=',
2128
+								$ignore_this_model_obj->ID(),
2129
+							],
2130
+						],
2131
+					]);
2132
+				} else {
2133
+					$related_model_objects = $relation_obj->get_all_related($this_model_obj_or_id);
2134
+				}
2135
+				if ($related_model_objects) {
2136
+					EE_Error::add_error($relation_obj->get_deletion_error_message(), __FILE__, __FUNCTION__, __LINE__);
2137
+					$is_blocked = true;
2138
+				}
2139
+			}
2140
+		}
2141
+		return $is_blocked;
2142
+	}
2143
+
2144
+
2145
+	/**
2146
+	 * Builds the columns and values for items to delete from the incoming $row_results_for_deleting array.
2147
+	 *
2148
+	 * @param array $row_results_for_deleting
2149
+	 * @param bool  $allow_blocking
2150
+	 * @return array   The shape of this array depends on whether the model `has_primary_key_field` or not.  If the
2151
+	 *                              model DOES have a primary_key_field, then the array will be a simple single
2152
+	 *                              dimension array where the key is the fully qualified primary key column and the
2153
+	 *                              value is an array of ids that will be deleted. Example: array('Event.EVT_ID' =>
2154
+	 *                              array( 1,2,3)) If the model DOES NOT have a primary_key_field, then the array will
2155
+	 *                              be a two dimensional array where each element is a group of columns and values that
2156
+	 *                              get deleted. Example: array(
2157
+	 *                              0 => array(
2158
+	 *                              'Term_Relationship.object_id' => 1
2159
+	 *                              'Term_Relationship.term_taxonomy_id' => 5
2160
+	 *                              ),
2161
+	 *                              1 => array(
2162
+	 *                              'Term_Relationship.object_id' => 1
2163
+	 *                              'Term_Relationship.term_taxonomy_id' => 6
2164
+	 *                              )
2165
+	 *                              )
2166
+	 * @throws EE_Error
2167
+	 * @throws ReflectionException
2168
+	 */
2169
+	protected function _get_ids_for_delete(array $row_results_for_deleting, $allow_blocking = true)
2170
+	{
2171
+		$ids_to_delete_indexed_by_column = [];
2172
+		if ($this->has_primary_key_field()) {
2173
+			$primary_table                   = $this->_get_main_table();
2174
+			$primary_table_pk_field          =
2175
+				$this->get_field_by_column($primary_table->get_fully_qualified_pk_column());
2176
+			$other_tables                    = $this->_get_other_tables();
2177
+			$ids_to_delete_indexed_by_column = $query = [];
2178
+			foreach ($row_results_for_deleting as $item_to_delete) {
2179
+				// before we mark this item for deletion,
2180
+				// make sure there's no related entities blocking its deletion (if we're checking)
2181
+				if (
2182
+					$allow_blocking
2183
+					&& $this->delete_is_blocked_by_related_models(
2184
+						$item_to_delete[ $primary_table->get_fully_qualified_pk_column() ]
2185
+					)
2186
+				) {
2187
+					continue;
2188
+				}
2189
+				// primary table deletes
2190
+				if (isset($item_to_delete[ $primary_table->get_fully_qualified_pk_column() ])) {
2191
+					$ids_to_delete_indexed_by_column[ $primary_table->get_fully_qualified_pk_column() ][] =
2192
+						$item_to_delete[ $primary_table->get_fully_qualified_pk_column() ];
2193
+				}
2194
+			}
2195
+		} elseif (count($this->get_combined_primary_key_fields()) > 1) {
2196
+			$fields = $this->get_combined_primary_key_fields();
2197
+			foreach ($row_results_for_deleting as $item_to_delete) {
2198
+				$ids_to_delete_indexed_by_column_for_row = [];
2199
+				foreach ($fields as $cpk_field) {
2200
+					if ($cpk_field instanceof EE_Model_Field_Base) {
2201
+						$ids_to_delete_indexed_by_column_for_row[ $cpk_field->get_qualified_column() ] =
2202
+							$item_to_delete[ $cpk_field->get_qualified_column() ];
2203
+					}
2204
+				}
2205
+				$ids_to_delete_indexed_by_column[] = $ids_to_delete_indexed_by_column_for_row;
2206
+			}
2207
+		} else {
2208
+			// so there's no primary key and no combined key...
2209
+			// sorry, can't help you
2210
+			throw new EE_Error(
2211
+				sprintf(
2212
+					esc_html__(
2213
+						"Cannot delete objects of type %s because there is no primary key NOR combined key",
2214
+						"event_espresso"
2215
+					),
2216
+					get_class($this)
2217
+				)
2218
+			);
2219
+		}
2220
+		return $ids_to_delete_indexed_by_column;
2221
+	}
2222
+
2223
+
2224
+	/**
2225
+	 * This receives an array of columns and values set to be deleted (as prepared by _get_ids_for_delete) and prepares
2226
+	 * the corresponding query_part for the query performing the delete.
2227
+	 *
2228
+	 * @param array $ids_to_delete_indexed_by_column @see _get_ids_for_delete for how this array might be shaped.
2229
+	 * @return string
2230
+	 * @throws EE_Error
2231
+	 */
2232
+	protected function _build_query_part_for_deleting_from_columns_and_values(array $ids_to_delete_indexed_by_column)
2233
+	{
2234
+		$query_part = '';
2235
+		if (empty($ids_to_delete_indexed_by_column)) {
2236
+			return $query_part;
2237
+		} elseif ($this->has_primary_key_field()) {
2238
+			$query = [];
2239
+			foreach ($ids_to_delete_indexed_by_column as $column => $ids) {
2240
+				$query[] = $column . ' IN' . $this->_construct_in_value($ids, $this->_primary_key_field);
2241
+			}
2242
+			$query_part = ! empty($query) ? implode(' AND ', $query) : $query_part;
2243
+		} elseif (count($this->get_combined_primary_key_fields()) > 1) {
2244
+			$ways_to_identify_a_row = [];
2245
+			foreach ($ids_to_delete_indexed_by_column as $ids_to_delete_indexed_by_column_for_each_row) {
2246
+				$values_for_each_combined_primary_key_for_a_row = [];
2247
+				foreach ($ids_to_delete_indexed_by_column_for_each_row as $column => $id) {
2248
+					$values_for_each_combined_primary_key_for_a_row[] = $column . '=' . $id;
2249
+				}
2250
+				$ways_to_identify_a_row[] = '('
2251
+											. implode(' AND ', $values_for_each_combined_primary_key_for_a_row)
2252
+											. ')';
2253
+			}
2254
+			$query_part = implode(' OR ', $ways_to_identify_a_row);
2255
+		}
2256
+		return $query_part;
2257
+	}
2258
+
2259
+
2260
+	/**
2261
+	 * Gets the model field by the fully qualified name
2262
+	 *
2263
+	 * @param string $qualified_column_name eg 'Event_CPT.post_name' or $field_obj->get_qualified_column()
2264
+	 * @return EE_Model_Field_Base
2265
+	 * @throws EE_Error
2266
+	 * @throws EE_Error
2267
+	 */
2268
+	public function get_field_by_column($qualified_column_name)
2269
+	{
2270
+		foreach ($this->field_settings(true) as $field_name => $field_obj) {
2271
+			if ($field_obj->get_qualified_column() === $qualified_column_name) {
2272
+				return $field_obj;
2273
+			}
2274
+		}
2275
+		throw new EE_Error(
2276
+			sprintf(
2277
+				esc_html__('Could not find a field on the model "%1$s" for qualified column "%2$s"', 'event_espresso'),
2278
+				$this->get_this_model_name(),
2279
+				$qualified_column_name
2280
+			)
2281
+		);
2282
+	}
2283
+
2284
+
2285
+	/**
2286
+	 * Count all the rows that match criteria the model query params.
2287
+	 * If $field_to_count isn't provided, the model's primary key is used. Otherwise, we count by field_to_count's
2288
+	 * column
2289
+	 *
2290
+	 * @param array  $query_params   @see
2291
+	 *                               https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2292
+	 * @param string $field_to_count field on model to count by (not column name)
2293
+	 * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2294
+	 *                               that by the setting $distinct to TRUE;
2295
+	 * @return int
2296
+	 * @throws EE_Error
2297
+	 */
2298
+	public function count($query_params = [], $field_to_count = null, $distinct = false)
2299
+	{
2300
+		$model_query_info = $this->_create_model_query_info_carrier($query_params);
2301
+		if ($field_to_count) {
2302
+			$field_obj       = $this->field_settings_for($field_to_count);
2303
+			$column_to_count = $field_obj->get_qualified_column();
2304
+		} elseif ($this->has_primary_key_field()) {
2305
+			$pk_field_obj    = $this->get_primary_key_field();
2306
+			$column_to_count = $pk_field_obj->get_qualified_column();
2307
+		} else {
2308
+			// there's no primary key
2309
+			// if we're counting distinct items, and there's no primary key,
2310
+			// we need to list out the columns for distinction;
2311
+			// otherwise we can just use star
2312
+			if ($distinct) {
2313
+				$columns_to_use = [];
2314
+				foreach ($this->get_combined_primary_key_fields() as $field_obj) {
2315
+					$columns_to_use[] = $field_obj->get_qualified_column();
2316
+				}
2317
+				$column_to_count = implode(',', $columns_to_use);
2318
+			} else {
2319
+				$column_to_count = '*';
2320
+			}
2321
+		}
2322
+		$column_to_count = $distinct ? "DISTINCT " . $column_to_count : $column_to_count;
2323
+		$SQL             =
2324
+			"SELECT COUNT(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2325
+		return (int) $this->_do_wpdb_query('get_var', [$SQL]);
2326
+	}
2327
+
2328
+
2329
+	/**
2330
+	 * Sums up the value of the $field_to_sum (defaults to the primary key, which isn't terribly useful)
2331
+	 *
2332
+	 * @param array  $query_params @see
2333
+	 *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2334
+	 * @param string $field_to_sum name of field (array key in $_fields array)
2335
+	 * @return float
2336
+	 * @throws EE_Error
2337
+	 */
2338
+	public function sum($query_params, $field_to_sum = null)
2339
+	{
2340
+		$model_query_info = $this->_create_model_query_info_carrier($query_params);
2341
+		if ($field_to_sum) {
2342
+			$field_obj = $this->field_settings_for($field_to_sum);
2343
+		} else {
2344
+			$field_obj = $this->get_primary_key_field();
2345
+		}
2346
+		$column_to_count = $field_obj->get_qualified_column();
2347
+		$SQL             =
2348
+			"SELECT SUM(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2349
+		$return_value    = $this->_do_wpdb_query('get_var', [$SQL]);
2350
+		$data_type       = $field_obj->get_wpdb_data_type();
2351
+		if ($data_type === '%d' || $data_type === '%s') {
2352
+			return (float) $return_value;
2353
+		}
2354
+		// must be %f
2355
+		return (float) $return_value;
2356
+	}
2357
+
2358
+
2359
+	/**
2360
+	 * Just calls the specified method on $wpdb with the given arguments
2361
+	 * Consolidates a little extra error handling code
2362
+	 *
2363
+	 * @param string $wpdb_method
2364
+	 * @param array  $arguments_to_provide
2365
+	 * @return mixed
2366
+	 * @throws EE_Error
2367
+	 * @global wpdb  $wpdb
2368
+	 */
2369
+	protected function _do_wpdb_query($wpdb_method, $arguments_to_provide)
2370
+	{
2371
+		// if we're in maintenance mode level 2, DON'T run any queries
2372
+		// because level 2 indicates the database needs updating and
2373
+		// is probably out of sync with the code
2374
+		if (! EE_Maintenance_Mode::instance()->models_can_query()) {
2375
+			throw new EE_Error(
2376
+				sprintf(
2377
+					esc_html__(
2378
+						"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.",
2379
+						"event_espresso"
2380
+					)
2381
+				)
2382
+			);
2383
+		}
2384
+		/** @type WPDB $wpdb */
2385
+		global $wpdb;
2386
+		if (! method_exists($wpdb, $wpdb_method)) {
2387
+			throw new EE_Error(
2388
+				sprintf(
2389
+					esc_html__(
2390
+						'There is no method named "%s" on Wordpress\' $wpdb object',
2391
+						'event_espresso'
2392
+					),
2393
+					$wpdb_method
2394
+				)
2395
+			);
2396
+		}
2397
+		if (WP_DEBUG) {
2398
+			$old_show_errors_value = $wpdb->show_errors;
2399
+			$wpdb->show_errors(false);
2400
+		}
2401
+		$result = $this->_process_wpdb_query($wpdb_method, $arguments_to_provide);
2402
+		$this->show_db_query_if_previously_requested($wpdb->last_query);
2403
+		if (WP_DEBUG) {
2404
+			$wpdb->show_errors($old_show_errors_value);
2405
+			if (! empty($wpdb->last_error)) {
2406
+				throw new EE_Error(sprintf(esc_html__('WPDB Error: "%s"', 'event_espresso'), $wpdb->last_error));
2407
+			}
2408
+			if ($result === false) {
2409
+				throw new EE_Error(
2410
+					sprintf(
2411
+						esc_html__(
2412
+							'WPDB Error occurred, but no error message was logged by wpdb! The wpdb method called was "%1$s" and the arguments were "%2$s"',
2413
+							'event_espresso'
2414
+						),
2415
+						$wpdb_method,
2416
+						var_export($arguments_to_provide, true)
2417
+					)
2418
+				);
2419
+			}
2420
+		} elseif ($result === false) {
2421
+			EE_Error::add_error(
2422
+				sprintf(
2423
+					esc_html__(
2424
+						'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"',
2425
+						'event_espresso'
2426
+					),
2427
+					$wpdb_method,
2428
+					var_export($arguments_to_provide, true),
2429
+					$wpdb->last_error
2430
+				),
2431
+				__FILE__,
2432
+				__FUNCTION__,
2433
+				__LINE__
2434
+			);
2435
+		}
2436
+		return $result;
2437
+	}
2438
+
2439
+
2440
+	/**
2441
+	 * Attempts to run the indicated WPDB method with the provided arguments,
2442
+	 * and if there's an error tries to verify the DB is correct. Uses
2443
+	 * the static property EEM_Base::$_db_verification_level to determine whether
2444
+	 * we should try to fix the EE core db, the addons, or just give up
2445
+	 *
2446
+	 * @param string $wpdb_method
2447
+	 * @param array  $arguments_to_provide
2448
+	 * @return mixed
2449
+	 */
2450
+	private function _process_wpdb_query($wpdb_method, $arguments_to_provide)
2451
+	{
2452
+		/** @type WPDB $wpdb */
2453
+		global $wpdb;
2454
+		$wpdb->last_error = null;
2455
+		$result           = call_user_func_array([$wpdb, $wpdb_method], $arguments_to_provide);
2456
+		// was there an error running the query? but we don't care on new activations
2457
+		// (we're going to setup the DB anyway on new activations)
2458
+		if (
2459
+			($result === false || ! empty($wpdb->last_error))
2460
+			&& EE_System::instance()->detect_req_type() !== EE_System::req_type_new_activation
2461
+		) {
2462
+			switch (EEM_Base::$_db_verification_level) {
2463
+				case EEM_Base::db_verified_none:
2464
+					// let's double-check core's DB
2465
+					$error_message = $this->_verify_core_db($wpdb_method, $arguments_to_provide);
2466
+					break;
2467
+				case EEM_Base::db_verified_core:
2468
+					// STILL NO LOVE?? verify all the addons too. Maybe they need to be fixed
2469
+					$error_message = $this->_verify_addons_db($wpdb_method, $arguments_to_provide);
2470
+					break;
2471
+				case EEM_Base::db_verified_addons:
2472
+					// ummmm... you in trouble
2473
+					return $result;
2474
+			}
2475
+			if (! empty($error_message)) {
2476
+				EE_Log::instance()->log(__FILE__, __FUNCTION__, $error_message, 'error');
2477
+				trigger_error($error_message);
2478
+			}
2479
+			return $this->_process_wpdb_query($wpdb_method, $arguments_to_provide);
2480
+		}
2481
+		return $result;
2482
+	}
2483
+
2484
+
2485
+	/**
2486
+	 * Verifies the EE core database is up-to-date and records that we've done it on
2487
+	 * EEM_Base::$_db_verification_level
2488
+	 *
2489
+	 * @param string $wpdb_method
2490
+	 * @param array  $arguments_to_provide
2491
+	 * @return string
2492
+	 */
2493
+	private function _verify_core_db($wpdb_method, $arguments_to_provide)
2494
+	{
2495
+		/** @type WPDB $wpdb */
2496
+		global $wpdb;
2497
+		// ok remember that we've already attempted fixing the core db, in case the problem persists
2498
+		EEM_Base::$_db_verification_level = EEM_Base::db_verified_core;
2499
+		$error_message                    = sprintf(
2500
+			esc_html__(
2501
+				'WPDB Error "%1$s" while running wpdb method "%2$s" with arguments %3$s. Automatically attempting to fix EE Core DB',
2502
+				'event_espresso'
2503
+			),
2504
+			$wpdb->last_error,
2505
+			$wpdb_method,
2506
+			wp_json_encode($arguments_to_provide)
2507
+		);
2508
+		EE_System::instance()->initialize_db_if_no_migrations_required(false, true);
2509
+		return $error_message;
2510
+	}
2511
+
2512
+
2513
+	/**
2514
+	 * Verifies the EE addons' database is up-to-date and records that we've done it on
2515
+	 * EEM_Base::$_db_verification_level
2516
+	 *
2517
+	 * @param $wpdb_method
2518
+	 * @param $arguments_to_provide
2519
+	 * @return string
2520
+	 */
2521
+	private function _verify_addons_db($wpdb_method, $arguments_to_provide)
2522
+	{
2523
+		/** @type WPDB $wpdb */
2524
+		global $wpdb;
2525
+		// ok remember that we've already attempted fixing the addons dbs, in case the problem persists
2526
+		EEM_Base::$_db_verification_level = EEM_Base::db_verified_addons;
2527
+		$error_message                    = sprintf(
2528
+			esc_html__(
2529
+				'WPDB AGAIN: Error "%1$s" while running the same method and arguments as before. Automatically attempting to fix EE Addons DB',
2530
+				'event_espresso'
2531
+			),
2532
+			$wpdb->last_error,
2533
+			$wpdb_method,
2534
+			wp_json_encode($arguments_to_provide)
2535
+		);
2536
+		EE_System::instance()->initialize_addons();
2537
+		return $error_message;
2538
+	}
2539
+
2540
+
2541
+	/**
2542
+	 * In order to avoid repeating this code for the get_all, sum, and count functions, put the code parts
2543
+	 * that are identical in here. Returns a string of SQL of everything in a SELECT query except the beginning
2544
+	 * SELECT clause, eg " FROM wp_posts AS Event INNER JOIN ... WHERE ... ORDER BY ... LIMIT ... GROUP BY ... HAVING
2545
+	 * ..."
2546
+	 *
2547
+	 * @param EE_Model_Query_Info_Carrier $model_query_info
2548
+	 * @return string
2549
+	 */
2550
+	private function _construct_2nd_half_of_select_query(EE_Model_Query_Info_Carrier $model_query_info)
2551
+	{
2552
+		return " FROM " . $model_query_info->get_full_join_sql() .
2553
+			   $model_query_info->get_where_sql() .
2554
+			   $model_query_info->get_group_by_sql() .
2555
+			   $model_query_info->get_having_sql() .
2556
+			   $model_query_info->get_order_by_sql() .
2557
+			   $model_query_info->get_limit_sql();
2558
+	}
2559
+
2560
+
2561
+	/**
2562
+	 * Set to easily debug the next X queries ran from this model.
2563
+	 *
2564
+	 * @param int $count
2565
+	 */
2566
+	public function show_next_x_db_queries($count = 1)
2567
+	{
2568
+		$this->_show_next_x_db_queries = $count;
2569
+	}
2570
+
2571
+
2572
+	/**
2573
+	 * @param $sql_query
2574
+	 */
2575
+	public function show_db_query_if_previously_requested($sql_query)
2576
+	{
2577
+		if ($this->_show_next_x_db_queries > 0) {
2578
+			echo esc_html($sql_query);
2579
+			$this->_show_next_x_db_queries--;
2580
+		}
2581
+	}
2582
+
2583
+
2584
+	/**
2585
+	 * Adds a relationship of the correct type between $modelObject and $otherModelObject.
2586
+	 * There are the 3 cases:
2587
+	 * 'belongsTo' relationship: sets $id_or_obj's foreign_key to be $other_model_id_or_obj's primary_key. If
2588
+	 * $otherModelObject has no ID, it is first saved.
2589
+	 * 'hasMany' relationship: sets $other_model_id_or_obj's foreign_key to be $id_or_obj's primary_key. If $id_or_obj
2590
+	 * has no ID, it is first saved.
2591
+	 * 'hasAndBelongsToMany' relationships: checks that there isn't already an entry in the join table, and adds one.
2592
+	 * If one of the model Objects has not yet been saved to the database, it is saved before adding the entry in the
2593
+	 * join table
2594
+	 *
2595
+	 * @param EE_Base_Class                     /int $thisModelObject
2596
+	 * @param EE_Base_Class                     /int $id_or_obj EE_base_Class or ID of other Model Object
2597
+	 * @param string $relationName                     , key in EEM_Base::_relations
2598
+	 *                                                 an attendee to a group, you also want to specify which role they
2599
+	 *                                                 will have in that group. So you would use this parameter to
2600
+	 *                                                 specify array('role-column-name'=>'role-id')
2601
+	 * @param array  $extra_join_model_fields_n_values This allows you to enter further query params for the relation
2602
+	 *                                                 to for relation to methods that allow you to further specify
2603
+	 *                                                 extra columns to join by (such as HABTM).  Keep in mind that the
2604
+	 *                                                 only acceptable query_params is strict "col" => "value" pairs
2605
+	 *                                                 because these will be inserted in any new rows created as well.
2606
+	 * @return EE_Base_Class which was added as a relation. Object referred to by $other_model_id_or_obj
2607
+	 * @throws EE_Error
2608
+	 */
2609
+	public function add_relationship_to(
2610
+		$id_or_obj,
2611
+		$other_model_id_or_obj,
2612
+		$relationName,
2613
+		$extra_join_model_fields_n_values = []
2614
+	) {
2615
+		$relation_obj = $this->related_settings_for($relationName);
2616
+		return $relation_obj->add_relation_to($id_or_obj, $other_model_id_or_obj, $extra_join_model_fields_n_values);
2617
+	}
2618
+
2619
+
2620
+	/**
2621
+	 * Removes a relationship of the correct type between $modelObject and $otherModelObject.
2622
+	 * There are the 3 cases:
2623
+	 * 'belongsTo' relationship: sets $modelObject's foreign_key to null, if that field is nullable.Otherwise throws an
2624
+	 * error
2625
+	 * 'hasMany' relationship: sets $otherModelObject's foreign_key to null,if that field is nullable.Otherwise throws
2626
+	 * an error
2627
+	 * 'hasAndBelongsToMany' relationships:removes any existing entry in the join table between the two models.
2628
+	 *
2629
+	 * @param EE_Base_Class /int $id_or_obj
2630
+	 * @param EE_Base_Class /int $other_model_id_or_obj EE_Base_Class or ID of other Model Object
2631
+	 * @param string $relationName key in EEM_Base::_relations
2632
+	 * @param array  $where_query  This allows you to enter further query params for the relation to for relation to
2633
+	 *                             methods that allow you to further specify extra columns to join by (such as HABTM).
2634
+	 *                             Keep in mind that the only acceptable query_params is strict "col" => "value" pairs
2635
+	 *                             because these will be inserted in any new rows created as well.
2636
+	 * @return boolean of success
2637
+	 * @throws EE_Error
2638
+	 */
2639
+	public function remove_relationship_to($id_or_obj, $other_model_id_or_obj, $relationName, $where_query = [])
2640
+	{
2641
+		$relation_obj = $this->related_settings_for($relationName);
2642
+		return $relation_obj->remove_relation_to($id_or_obj, $other_model_id_or_obj, $where_query);
2643
+	}
2644
+
2645
+
2646
+	/**
2647
+	 * @param mixed  $id_or_obj
2648
+	 * @param string $relationName
2649
+	 * @param array  $where_query_params
2650
+	 * @param EE_Base_Class[] objects to which relations were removed
2651
+	 * @return EE_Base_Class[]
2652
+	 * @throws EE_Error
2653
+	 */
2654
+	public function remove_relations($id_or_obj, $relationName, $where_query_params = [])
2655
+	{
2656
+		$relation_obj = $this->related_settings_for($relationName);
2657
+		return $relation_obj->remove_relations($id_or_obj, $where_query_params);
2658
+	}
2659
+
2660
+
2661
+	/**
2662
+	 * Gets all the related items of the specified $model_name, using $query_params.
2663
+	 * Note: by default, we remove the "default query params"
2664
+	 * because we want to get even deleted items etc.
2665
+	 *
2666
+	 * @param mixed  $id_or_obj    EE_Base_Class child or its ID
2667
+	 * @param string $model_name   like 'Event', 'Registration', etc. always singular
2668
+	 * @param array  $query_params @see
2669
+	 *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2670
+	 * @return EE_Base_Class[]
2671
+	 * @throws EE_Error
2672
+	 * @throws ReflectionException
2673
+	 */
2674
+	public function get_all_related($id_or_obj, $model_name, $query_params = null)
2675
+	{
2676
+		$model_obj         = $this->ensure_is_obj($id_or_obj);
2677
+		$relation_settings = $this->related_settings_for($model_name);
2678
+		return $relation_settings->get_all_related($model_obj, $query_params);
2679
+	}
2680
+
2681
+
2682
+	/**
2683
+	 * Deletes all the model objects across the relation indicated by $model_name
2684
+	 * which are related to $id_or_obj which meet the criteria set in $query_params.
2685
+	 * However, if the model objects can't be deleted because of blocking related model objects, then
2686
+	 * they aren't deleted. (Unless the thing that would have been deleted can be soft-deleted, that still happens).
2687
+	 *
2688
+	 * @param EE_Base_Class|int|string $id_or_obj
2689
+	 * @param string                   $model_name
2690
+	 * @param array                    $query_params
2691
+	 * @return int how many deleted
2692
+	 * @throws EE_Error
2693
+	 * @throws ReflectionException
2694
+	 */
2695
+	public function delete_related($id_or_obj, $model_name, $query_params = [])
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_all_related($model_obj, $query_params);
2700
+	}
2701
+
2702
+
2703
+	/**
2704
+	 * Hard deletes all the model objects across the relation indicated by $model_name
2705
+	 * which are related to $id_or_obj which meet the criteria set in $query_params. If
2706
+	 * the model objects can't be hard deleted because of blocking related model objects,
2707
+	 * just does a soft-delete on them instead.
2708
+	 *
2709
+	 * @param EE_Base_Class|int|string $id_or_obj
2710
+	 * @param string                   $model_name
2711
+	 * @param array                    $query_params
2712
+	 * @return int how many deleted
2713
+	 * @throws EE_Error
2714
+	 * @throws ReflectionException
2715
+	 */
2716
+	public function delete_related_permanently($id_or_obj, $model_name, $query_params = [])
2717
+	{
2718
+		$model_obj         = $this->ensure_is_obj($id_or_obj);
2719
+		$relation_settings = $this->related_settings_for($model_name);
2720
+		return $relation_settings->delete_related_permanently($model_obj, $query_params);
2721
+	}
2722
+
2723
+
2724
+	/**
2725
+	 * Instead of getting the related model objects, simply counts them. Ignores default_where_conditions by default,
2726
+	 * unless otherwise specified in the $query_params
2727
+	 *
2728
+	 * @param int             /EE_Base_Class $id_or_obj
2729
+	 * @param string $model_name     like 'Event', or 'Registration'
2730
+	 * @param array  $query_params   @see
2731
+	 *                               https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2732
+	 * @param string $field_to_count name of field to count by. By default, uses primary key
2733
+	 * @param bool   $distinct       if we want to only count the distinct values for the column then you can trigger
2734
+	 *                               that by the setting $distinct to TRUE;
2735
+	 * @return int
2736
+	 * @throws EE_Error
2737
+	 */
2738
+	public function count_related(
2739
+		$id_or_obj,
2740
+		$model_name,
2741
+		$query_params = [],
2742
+		$field_to_count = null,
2743
+		$distinct = false
2744
+	) {
2745
+		$related_model = $this->get_related_model_obj($model_name);
2746
+		// we're just going to use the query params on the related model's normal get_all query,
2747
+		// except add a condition to say to match the current mod
2748
+		if (! isset($query_params['default_where_conditions'])) {
2749
+			$query_params['default_where_conditions'] = EEM_Base::default_where_conditions_none;
2750
+		}
2751
+		$this_model_name                                                 = $this->get_this_model_name();
2752
+		$this_pk_field_name                                              = $this->get_primary_key_field()->get_name();
2753
+		$query_params[0][ $this_model_name . "." . $this_pk_field_name ] = $id_or_obj;
2754
+		return $related_model->count($query_params, $field_to_count, $distinct);
2755
+	}
2756
+
2757
+
2758
+	/**
2759
+	 * Instead of getting the related model objects, simply sums up the values of the specified field.
2760
+	 * Note: ignores default_where_conditions by default, unless otherwise specified in the $query_params
2761
+	 *
2762
+	 * @param int           /EE_Base_Class $id_or_obj
2763
+	 * @param string $model_name   like 'Event', or 'Registration'
2764
+	 * @param array  $query_params @see
2765
+	 *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2766
+	 * @param string $field_to_sum name of field to count by. By default, uses primary key
2767
+	 * @return float
2768
+	 * @throws EE_Error
2769
+	 */
2770
+	public function sum_related($id_or_obj, $model_name, $query_params, $field_to_sum = null)
2771
+	{
2772
+		$related_model = $this->get_related_model_obj($model_name);
2773
+		if (! is_array($query_params)) {
2774
+			EE_Error::doing_it_wrong(
2775
+				'EEM_Base::sum_related',
2776
+				sprintf(
2777
+					esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
2778
+					gettype($query_params)
2779
+				),
2780
+				'4.6.0'
2781
+			);
2782
+			$query_params = [];
2783
+		}
2784
+		// we're just going to use the query params on the related model's normal get_all query,
2785
+		// except add a condition to say to match the current mod
2786
+		if (! isset($query_params['default_where_conditions'])) {
2787
+			$query_params['default_where_conditions'] = EEM_Base::default_where_conditions_none;
2788
+		}
2789
+		$this_model_name                                                 = $this->get_this_model_name();
2790
+		$this_pk_field_name                                              = $this->get_primary_key_field()->get_name();
2791
+		$query_params[0][ $this_model_name . "." . $this_pk_field_name ] = $id_or_obj;
2792
+		return $related_model->sum($query_params, $field_to_sum);
2793
+	}
2794
+
2795
+
2796
+	/**
2797
+	 * Uses $this->_relatedModels info to find the first related model object of relation $relationName to the given
2798
+	 * $modelObject
2799
+	 *
2800
+	 * @param int | EE_Base_Class $id_or_obj        EE_Base_Class child or its ID
2801
+	 * @param string              $other_model_name , key in $this->_relatedModels, eg 'Registration', or 'Events'
2802
+	 * @param array               $query_params     @see
2803
+	 *                                              https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
2804
+	 * @return EE_Base_Class
2805
+	 * @throws EE_Error
2806
+	 */
2807
+	public function get_first_related(EE_Base_Class $id_or_obj, $other_model_name, $query_params)
2808
+	{
2809
+		$query_params['limit'] = 1;
2810
+		$results               = $this->get_all_related($id_or_obj, $other_model_name, $query_params);
2811
+		if ($results) {
2812
+			return array_shift($results);
2813
+		}
2814
+		return null;
2815
+	}
2816
+
2817
+
2818
+	/**
2819
+	 * Gets the model's name as it's expected in queries. For example, if this is EEM_Event model, that would be Event
2820
+	 *
2821
+	 * @return string
2822
+	 */
2823
+	public function get_this_model_name()
2824
+	{
2825
+		return str_replace("EEM_", "", get_class($this));
2826
+	}
2827
+
2828
+
2829
+	/**
2830
+	 * Gets the model field on this model which is of type EE_Any_Foreign_Model_Name_Field
2831
+	 *
2832
+	 * @return EE_Any_Foreign_Model_Name_Field
2833
+	 * @throws EE_Error
2834
+	 */
2835
+	public function get_field_containing_related_model_name()
2836
+	{
2837
+		foreach ($this->field_settings(true) as $field) {
2838
+			if ($field instanceof EE_Any_Foreign_Model_Name_Field) {
2839
+				$field_with_model_name = $field;
2840
+			}
2841
+		}
2842
+		if (! isset($field_with_model_name) || ! $field_with_model_name) {
2843
+			throw new EE_Error(
2844
+				sprintf(
2845
+					esc_html__("There is no EE_Any_Foreign_Model_Name field on model %s", "event_espresso"),
2846
+					$this->get_this_model_name()
2847
+				)
2848
+			);
2849
+		}
2850
+		return $field_with_model_name;
2851
+	}
2852
+
2853
+
2854
+	/**
2855
+	 * Inserts a new entry into the database, for each table.
2856
+	 * Note: does not add the item to the entity map because that is done by EE_Base_Class::save() right after this.
2857
+	 * If client code uses EEM_Base::insert() directly, then although the item isn't in the entity map,
2858
+	 * we also know there is no model object with the newly inserted item's ID at the moment (because
2859
+	 * if there were, then they would already be in the DB and this would fail); and in the future if someone
2860
+	 * creates a model object with this ID (or grabs it from the DB) then it will be added to the
2861
+	 * entity map at that time anyways. SO, no need for EEM_Base::insert ot add to the entity map
2862
+	 *
2863
+	 * @param array $field_n_values keys are field names, values are their values (in the client code's domain if
2864
+	 *                              $values_already_prepared_by_model_object is false, in the model object's domain if
2865
+	 *                              $values_already_prepared_by_model_object is true. See comment about this at the top
2866
+	 *                              of EEM_Base)
2867
+	 * @return int|string new primary key on main table that got inserted
2868
+	 * @throws EE_Error
2869
+	 */
2870
+	public function insert($field_n_values)
2871
+	{
2872
+		/**
2873
+		 * Filters the fields and their values before inserting an item using the models
2874
+		 *
2875
+		 * @param array    $fields_n_values keys are the fields and values are their new values
2876
+		 * @param EEM_Base $model           the model used
2877
+		 */
2878
+		$field_n_values = (array) apply_filters('FHEE__EEM_Base__insert__fields_n_values', $field_n_values, $this);
2879
+		if ($this->_satisfies_unique_indexes($field_n_values)) {
2880
+			$main_table = $this->_get_main_table();
2881
+			$new_id     = $this->_insert_into_specific_table($main_table, $field_n_values, false);
2882
+			if ($new_id !== false) {
2883
+				foreach ($this->_get_other_tables() as $other_table) {
2884
+					$this->_insert_into_specific_table($other_table, $field_n_values, $new_id);
2885
+				}
2886
+			}
2887
+			/**
2888
+			 * Done just after attempting to insert a new model object
2889
+			 *
2890
+			 * @param EEM_Base $model           used
2891
+			 * @param array    $fields_n_values fields and their values
2892
+			 * @param int|string the              ID of the newly-inserted model object
2893
+			 */
2894
+			do_action('AHEE__EEM_Base__insert__end', $this, $field_n_values, $new_id);
2895
+			return $new_id;
2896
+		}
2897
+		return false;
2898
+	}
2899
+
2900
+
2901
+	/**
2902
+	 * Checks that the result would satisfy the unique indexes on this model
2903
+	 *
2904
+	 * @param array  $field_n_values
2905
+	 * @param string $action
2906
+	 * @return boolean
2907
+	 * @throws EE_Error
2908
+	 */
2909
+	protected function _satisfies_unique_indexes($field_n_values, $action = 'insert')
2910
+	{
2911
+		foreach ($this->unique_indexes() as $index_name => $index) {
2912
+			$uniqueness_where_params = array_intersect_key($field_n_values, $index->fields());
2913
+			if ($this->exists([$uniqueness_where_params])) {
2914
+				EE_Error::add_error(
2915
+					sprintf(
2916
+						esc_html__(
2917
+							"Could not %s %s. %s uniqueness index failed. Fields %s must form a unique set, but an entry already exists with values %s.",
2918
+							"event_espresso"
2919
+						),
2920
+						$action,
2921
+						$this->_get_class_name(),
2922
+						$index_name,
2923
+						implode(",", $index->field_names()),
2924
+						http_build_query($uniqueness_where_params)
2925
+					),
2926
+					__FILE__,
2927
+					__FUNCTION__,
2928
+					__LINE__
2929
+				);
2930
+				return false;
2931
+			}
2932
+		}
2933
+		return true;
2934
+	}
2935
+
2936
+
2937
+	/**
2938
+	 * Checks the database for an item that conflicts (ie, if this item were
2939
+	 * saved to the DB would break some uniqueness requirement, like a primary key
2940
+	 * or an index primary key set) with the item specified. $id_obj_or_fields_array
2941
+	 * can be either an EE_Base_Class or an array of fields n values
2942
+	 *
2943
+	 * @param EE_Base_Class|array $obj_or_fields_array
2944
+	 * @param boolean             $include_primary_key whether to use the model object's primary key
2945
+	 *                                                 when looking for conflicts
2946
+	 *                                                 (ie, if false, we ignore the model object's primary key
2947
+	 *                                                 when finding "conflicts". If true, it's also considered).
2948
+	 *                                                 Only works for INT primary key,
2949
+	 *                                                 STRING primary keys cannot be ignored
2950
+	 * @return EE_Base_Class|array
2951
+	 * @throws EE_Error
2952
+	 * @throws ReflectionException
2953
+	 */
2954
+	public function get_one_conflicting($obj_or_fields_array, $include_primary_key = true)
2955
+	{
2956
+		if ($obj_or_fields_array instanceof EE_Base_Class) {
2957
+			$fields_n_values = $obj_or_fields_array->model_field_array();
2958
+		} elseif (is_array($obj_or_fields_array)) {
2959
+			$fields_n_values = $obj_or_fields_array;
2960
+		} else {
2961
+			throw new EE_Error(
2962
+				sprintf(
2963
+					esc_html__(
2964
+						"%s get_all_conflicting should be called with a model object or an array of field names and values, you provided %d",
2965
+						"event_espresso"
2966
+					),
2967
+					get_class($this),
2968
+					$obj_or_fields_array
2969
+				)
2970
+			);
2971
+		}
2972
+		$query_params = [];
2973
+		if (
2974
+			$this->has_primary_key_field()
2975
+			&& ($include_primary_key
2976
+				|| $this->get_primary_key_field()
2977
+				   instanceof
2978
+				   EE_Primary_Key_String_Field)
2979
+			&& isset($fields_n_values[ $this->primary_key_name() ])
2980
+		) {
2981
+			$query_params[0]['OR'][ $this->primary_key_name() ] = $fields_n_values[ $this->primary_key_name() ];
2982
+		}
2983
+		foreach ($this->unique_indexes() as $unique_index_name => $unique_index) {
2984
+			$uniqueness_where_params                              =
2985
+				array_intersect_key($fields_n_values, $unique_index->fields());
2986
+			$query_params[0]['OR'][ 'AND*' . $unique_index_name ] = $uniqueness_where_params;
2987
+		}
2988
+		// if there is nothing to base this search on, then we shouldn't find anything
2989
+		if (empty($query_params)) {
2990
+			return [];
2991
+		}
2992
+		return $this->get_one($query_params);
2993
+	}
2994
+
2995
+
2996
+	/**
2997
+	 * Like count, but is optimized and returns a boolean instead of an int
2998
+	 *
2999
+	 * @param array $query_params
3000
+	 * @return boolean
3001
+	 * @throws EE_Error
3002
+	 */
3003
+	public function exists($query_params)
3004
+	{
3005
+		$query_params['limit'] = 1;
3006
+		return $this->count($query_params) > 0;
3007
+	}
3008
+
3009
+
3010
+	/**
3011
+	 * Wrapper for exists, except ignores default query parameters so we're only considering ID
3012
+	 *
3013
+	 * @param int|string $id
3014
+	 * @return boolean
3015
+	 * @throws EE_Error
3016
+	 */
3017
+	public function exists_by_ID($id)
3018
+	{
3019
+		return $this->exists(
3020
+			[
3021
+				'default_where_conditions' => EEM_Base::default_where_conditions_none,
3022
+				[
3023
+					$this->primary_key_name() => $id,
3024
+				],
3025
+			]
3026
+		);
3027
+	}
3028
+
3029
+
3030
+	/**
3031
+	 * Inserts a new row in $table, using the $cols_n_values which apply to that table.
3032
+	 * If a $new_id is supplied and if $table is an EE_Other_Table, we assume
3033
+	 * we need to add a foreign key column to point to $new_id (which should be the primary key's value
3034
+	 * on the main table)
3035
+	 * This is protected rather than private because private is not accessible to any child methods and there MAY be
3036
+	 * cases where we want to call it directly rather than via insert().
3037
+	 *
3038
+	 * @access   protected
3039
+	 * @param EE_Table_Base $table
3040
+	 * @param array         $fields_n_values each key should be in field's keys, and value should be an int, string or
3041
+	 *                                       float
3042
+	 * @param int           $new_id          for now we assume only int keys
3043
+	 * @return int ID of new row inserted, or FALSE on failure
3044
+	 * @throws EE_Error
3045
+	 * @global WPDB         $wpdb            only used to get the $wpdb->insert_id after performing an insert
3046
+	 */
3047
+	protected function _insert_into_specific_table(EE_Table_Base $table, $fields_n_values, $new_id = 0)
3048
+	{
3049
+		global $wpdb;
3050
+		$insertion_col_n_values = [];
3051
+		$format_for_insertion   = [];
3052
+		$fields_on_table        = $this->_get_fields_for_table($table->get_table_alias());
3053
+		foreach ($fields_on_table as $field_name => $field_obj) {
3054
+			// check if its an auto-incrementing column, in which case we should just leave it to do its autoincrement thing
3055
+			if ($field_obj->is_auto_increment()) {
3056
+				continue;
3057
+			}
3058
+			$prepared_value = $this->_prepare_value_or_use_default($field_obj, $fields_n_values);
3059
+			// if the value we want to assign it to is NULL, just don't mention it for the insertion
3060
+			if ($prepared_value !== null) {
3061
+				$insertion_col_n_values[ $field_obj->get_table_column() ] = $prepared_value;
3062
+				$format_for_insertion[]                                   = $field_obj->get_wpdb_data_type();
3063
+			}
3064
+		}
3065
+		if ($table instanceof EE_Secondary_Table && $new_id) {
3066
+			// its not the main table, so we should have already saved the main table's PK which we just inserted
3067
+			// so add the fk to the main table as a column
3068
+			$insertion_col_n_values[ $table->get_fk_on_table() ] = $new_id;
3069
+			$format_for_insertion[]                              =
3070
+				'%d';// yes right now we're only allowing these foreign keys to be INTs
3071
+		}
3072
+
3073
+		// insert the new entry
3074
+		$result = $this->_do_wpdb_query(
3075
+			'insert',
3076
+			[$table->get_table_name(), $insertion_col_n_values, $format_for_insertion]
3077
+		);
3078
+		if ($result === false) {
3079
+			return false;
3080
+		}
3081
+		// ok, now what do we return for the ID of the newly-inserted thing?
3082
+		if ($this->has_primary_key_field()) {
3083
+			if ($this->get_primary_key_field()->is_auto_increment()) {
3084
+				return $wpdb->insert_id;
3085
+			}
3086
+			// it's not an auto-increment primary key, so
3087
+			// it must have been supplied
3088
+			return $fields_n_values[ $this->get_primary_key_field()->get_name() ];
3089
+		}
3090
+		// we can't return a  primary key because there is none. instead return
3091
+		// a unique string indicating this model
3092
+		return $this->get_index_primary_key_string($fields_n_values);
3093
+	}
3094
+
3095
+
3096
+	/**
3097
+	 * Prepare the $field_obj 's value in $fields_n_values for use in the database.
3098
+	 * If the field doesn't allow NULL, try to use its default. (If it doesn't allow NULL,
3099
+	 * and there is no default, we pass it along. WPDB will take care of it)
3100
+	 *
3101
+	 * @param EE_Model_Field_Base $field_obj
3102
+	 * @param array               $fields_n_values
3103
+	 * @return mixed string|int|float depending on what the table column will be expecting
3104
+	 * @throws EE_Error
3105
+	 */
3106
+	protected function _prepare_value_or_use_default($field_obj, $fields_n_values)
3107
+	{
3108
+		$field_name = $field_obj->get_name();
3109
+		// if this field doesn't allow nullable, don't allow it
3110
+		if (! $field_obj->is_nullable() && ! isset($fields_n_values[ $field_name ])) {
3111
+			$fields_n_values[ $field_name ] = $field_obj->get_default_value();
3112
+		}
3113
+		$unprepared_value = $fields_n_values[ $field_name ] ?? null;
3114
+		return $this->_prepare_value_for_use_in_db($unprepared_value, $field_obj);
3115
+	}
3116
+
3117
+
3118
+	/**
3119
+	 * Consolidates code for preparing  a value supplied to the model for use int eh db. Calls the field's
3120
+	 * prepare_for_use_in_db method on the value, and depending on $value_already_prepare_by_model_obj, may also call
3121
+	 * the field's prepare_for_set() method.
3122
+	 *
3123
+	 * @param mixed               $value value in the client code domain if $value_already_prepared_by_model_object is
3124
+	 *                                   false, otherwise a value in the model object's domain (see lengthy comment at
3125
+	 *                                   top of file)
3126
+	 * @param EE_Model_Field_Base $field field which will be doing the preparing of the value. If null, we assume
3127
+	 *                                   $value is a custom selection
3128
+	 * @return mixed a value ready for use in the database for insertions, updating, or in a where clause
3129
+	 */
3130
+	private function _prepare_value_for_use_in_db($value, $field)
3131
+	{
3132
+		if ($field instanceof EE_Model_Field_Base) {
3133
+			// phpcs:disable PSR2.ControlStructures.SwitchDeclaration.TerminatingComment
3134
+			switch ($this->_values_already_prepared_by_model_object) {
3135
+				/** @noinspection PhpMissingBreakStatementInspection */
3136
+				case self::not_prepared_by_model_object:
3137
+					$value = $field->prepare_for_set($value);
3138
+				// purposefully left out "return"
3139
+				// no break
3140
+				case self::prepared_by_model_object:
3141
+					/** @noinspection SuspiciousAssignmentsInspection */
3142
+					$value = $field->prepare_for_use_in_db($value);
3143
+				// no break
3144
+				case self::prepared_for_use_in_db:
3145
+					// leave the value alone
3146
+			}
3147
+			// phpcs:enable
3148
+		}
3149
+		return $value;
3150
+	}
3151
+
3152
+
3153
+	/**
3154
+	 * Returns the main table on this model
3155
+	 *
3156
+	 * @return EE_Primary_Table
3157
+	 * @throws EE_Error
3158
+	 */
3159
+	protected function _get_main_table()
3160
+	{
3161
+		foreach ($this->_tables as $table) {
3162
+			if ($table instanceof EE_Primary_Table) {
3163
+				return $table;
3164
+			}
3165
+		}
3166
+		throw new EE_Error(
3167
+			sprintf(
3168
+				esc_html__(
3169
+					'There are no main tables on %s. They should be added to _tables array in the constructor',
3170
+					'event_espresso'
3171
+				),
3172
+				get_class($this)
3173
+			)
3174
+		);
3175
+	}
3176
+
3177
+
3178
+	/**
3179
+	 * table
3180
+	 * returns EE_Primary_Table table name
3181
+	 *
3182
+	 * @return string
3183
+	 * @throws EE_Error
3184
+	 */
3185
+	public function table()
3186
+	{
3187
+		return $this->_get_main_table()->get_table_name();
3188
+	}
3189
+
3190
+
3191
+	/**
3192
+	 * table
3193
+	 * returns first EE_Secondary_Table table name
3194
+	 *
3195
+	 * @return string
3196
+	 */
3197
+	public function second_table()
3198
+	{
3199
+		// grab second table from tables array
3200
+		$second_table = end($this->_tables);
3201
+		return $second_table instanceof EE_Secondary_Table ? $second_table->get_table_name() : null;
3202
+	}
3203
+
3204
+
3205
+	/**
3206
+	 * get_table_obj_by_alias
3207
+	 * returns table name given it's alias
3208
+	 *
3209
+	 * @param string $table_alias
3210
+	 * @return EE_Primary_Table | EE_Secondary_Table
3211
+	 */
3212
+	public function get_table_obj_by_alias($table_alias = '')
3213
+	{
3214
+		return isset($this->_tables[ $table_alias ]) ? $this->_tables[ $table_alias ] : null;
3215
+	}
3216
+
3217
+
3218
+	/**
3219
+	 * Gets all the tables of type EE_Other_Table from EEM_CPT_Basel_Model::_tables
3220
+	 *
3221
+	 * @return EE_Secondary_Table[]
3222
+	 */
3223
+	protected function _get_other_tables()
3224
+	{
3225
+		$other_tables = [];
3226
+		foreach ($this->_tables as $table_alias => $table) {
3227
+			if ($table instanceof EE_Secondary_Table) {
3228
+				$other_tables[ $table_alias ] = $table;
3229
+			}
3230
+		}
3231
+		return $other_tables;
3232
+	}
3233
+
3234
+
3235
+	/**
3236
+	 * Finds all the fields that correspond to the given table
3237
+	 *
3238
+	 * @param string $table_alias , array key in EEM_Base::_tables
3239
+	 * @return EE_Model_Field_Base[]
3240
+	 */
3241
+	public function _get_fields_for_table($table_alias)
3242
+	{
3243
+		return $this->_fields[ $table_alias ];
3244
+	}
3245
+
3246
+
3247
+	/**
3248
+	 * Recurses through all the where parameters, and finds all the related models we'll need
3249
+	 * to complete this query. Eg, given where parameters like array('EVT_ID'=>3) from within Event model, we won't
3250
+	 * need any related models. But if the array were array('Registrations.REG_ID'=>3), we'd need the related
3251
+	 * Registration model. If it were array('Registrations.Transactions.Payments.PAY_ID'=>3), then we'd need the
3252
+	 * related Registration, Transaction, and Payment models.
3253
+	 *
3254
+	 * @param array $query_params @see
3255
+	 *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
3256
+	 * @return EE_Model_Query_Info_Carrier
3257
+	 * @throws EE_Error
3258
+	 */
3259
+	public function _extract_related_models_from_query($query_params)
3260
+	{
3261
+		$query_info_carrier = new EE_Model_Query_Info_Carrier();
3262
+		if (array_key_exists(0, $query_params)) {
3263
+			$this->_extract_related_models_from_sub_params_array_keys($query_params[0], $query_info_carrier, 0);
3264
+		}
3265
+		if (array_key_exists('group_by', $query_params)) {
3266
+			if (is_array($query_params['group_by'])) {
3267
+				$this->_extract_related_models_from_sub_params_array_values(
3268
+					$query_params['group_by'],
3269
+					$query_info_carrier,
3270
+					'group_by'
3271
+				);
3272
+			} elseif (! empty($query_params['group_by'])) {
3273
+				$this->_extract_related_model_info_from_query_param(
3274
+					$query_params['group_by'],
3275
+					$query_info_carrier,
3276
+					'group_by'
3277
+				);
3278
+			}
3279
+		}
3280
+		if (array_key_exists('having', $query_params)) {
3281
+			$this->_extract_related_models_from_sub_params_array_keys(
3282
+				$query_params[0],
3283
+				$query_info_carrier,
3284
+				'having'
3285
+			);
3286
+		}
3287
+		if (array_key_exists('order_by', $query_params)) {
3288
+			if (is_array($query_params['order_by'])) {
3289
+				$this->_extract_related_models_from_sub_params_array_keys(
3290
+					$query_params['order_by'],
3291
+					$query_info_carrier,
3292
+					'order_by'
3293
+				);
3294
+			} elseif (! empty($query_params['order_by'])) {
3295
+				$this->_extract_related_model_info_from_query_param(
3296
+					$query_params['order_by'],
3297
+					$query_info_carrier,
3298
+					'order_by'
3299
+				);
3300
+			}
3301
+		}
3302
+		if (array_key_exists('force_join', $query_params)) {
3303
+			$this->_extract_related_models_from_sub_params_array_values(
3304
+				$query_params['force_join'],
3305
+				$query_info_carrier,
3306
+				'force_join'
3307
+			);
3308
+		}
3309
+		$this->extractRelatedModelsFromCustomSelects($query_info_carrier);
3310
+		return $query_info_carrier;
3311
+	}
3312
+
3313
+
3314
+	/**
3315
+	 * For extracting related models from WHERE (0), HAVING (having), ORDER BY (order_by) or forced joins (force_join)
3316
+	 *
3317
+	 * @param array                       $sub_query_params @see
3318
+	 *                                                      https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#-0-where-conditions
3319
+	 * @param EE_Model_Query_Info_Carrier $model_query_info_carrier
3320
+	 * @param string                      $query_param_type one of $this->_allowed_query_params
3321
+	 * @return EE_Model_Query_Info_Carrier
3322
+	 * @throws EE_Error
3323
+	 */
3324
+	private function _extract_related_models_from_sub_params_array_keys(
3325
+		$sub_query_params,
3326
+		EE_Model_Query_Info_Carrier $model_query_info_carrier,
3327
+		$query_param_type
3328
+	) {
3329
+		if (! empty($sub_query_params)) {
3330
+			$sub_query_params = (array) $sub_query_params;
3331
+			foreach ($sub_query_params as $param => $possibly_array_of_params) {
3332
+				// $param could be simply 'EVT_ID', or it could be 'Registrations.REG_ID', or even 'Registrations.Transactions.Payments.PAY_amount'
3333
+				$this->_extract_related_model_info_from_query_param(
3334
+					$param,
3335
+					$model_query_info_carrier,
3336
+					$query_param_type
3337
+				);
3338
+				// if $possibly_array_of_params is an array, try recursing into it, searching for keys which
3339
+				// indicate needed joins. Eg, array('NOT'=>array('Registration.TXN_ID'=>23)). In this case, we tried
3340
+				// extracting models out of the 'NOT', which obviously wasn't successful, and then we recurse into the value
3341
+				// of array('Registration.TXN_ID'=>23)
3342
+				$query_param_sans_stars =
3343
+					$this->_remove_stars_and_anything_after_from_condition_query_param_key($param);
3344
+				if (in_array($query_param_sans_stars, $this->_logic_query_param_keys, true)) {
3345
+					if (! is_array($possibly_array_of_params)) {
3346
+						throw new EE_Error(
3347
+							sprintf(
3348
+								esc_html__(
3349
+									"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'))",
3350
+									"event_espresso"
3351
+								),
3352
+								$param,
3353
+								$possibly_array_of_params
3354
+							)
3355
+						);
3356
+					}
3357
+					$this->_extract_related_models_from_sub_params_array_keys(
3358
+						$possibly_array_of_params,
3359
+						$model_query_info_carrier,
3360
+						$query_param_type
3361
+					);
3362
+				} elseif (
3363
+					$query_param_type === 0 // ie WHERE
3364
+					&& is_array($possibly_array_of_params)
3365
+					&& isset($possibly_array_of_params[2])
3366
+					&& $possibly_array_of_params[2] == true
3367
+				) {
3368
+					// then $possible_array_of_params looks something like array('<','DTT_sold',true)
3369
+					// indicating that $possible_array_of_params[1] is actually a field name,
3370
+					// from which we should extract query parameters!
3371
+					if (! isset($possibly_array_of_params[0], $possibly_array_of_params[1])) {
3372
+						throw new EE_Error(
3373
+							sprintf(
3374
+								esc_html__(
3375
+									"Improperly formed query parameter %s. It should be numerically indexed like array('<','DTT_sold',true); but you provided %s",
3376
+									"event_espresso"
3377
+								),
3378
+								$query_param_type,
3379
+								implode(",", $possibly_array_of_params)
3380
+							)
3381
+						);
3382
+					}
3383
+					$this->_extract_related_model_info_from_query_param(
3384
+						$possibly_array_of_params[1],
3385
+						$model_query_info_carrier,
3386
+						$query_param_type
3387
+					);
3388
+				}
3389
+			}
3390
+		}
3391
+		return $model_query_info_carrier;
3392
+	}
3393
+
3394
+
3395
+	/**
3396
+	 * For extracting related models from forced_joins, where the array values contain the info about what
3397
+	 * models to join with. Eg an array like array('Attendee','Price.Price_Type');
3398
+	 *
3399
+	 * @param array                       $sub_query_params @see
3400
+	 *                                                      https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3401
+	 * @param EE_Model_Query_Info_Carrier $model_query_info_carrier
3402
+	 * @param string                      $query_param_type one of $this->_allowed_query_params
3403
+	 * @return EE_Model_Query_Info_Carrier
3404
+	 * @throws EE_Error
3405
+	 */
3406
+	private function _extract_related_models_from_sub_params_array_values(
3407
+		$sub_query_params,
3408
+		EE_Model_Query_Info_Carrier $model_query_info_carrier,
3409
+		$query_param_type
3410
+	) {
3411
+		if (! empty($sub_query_params)) {
3412
+			if (! is_array($sub_query_params)) {
3413
+				throw new EE_Error(
3414
+					sprintf(
3415
+						esc_html__("Query parameter %s should be an array, but it isn't.", "event_espresso"),
3416
+						$sub_query_params
3417
+					)
3418
+				);
3419
+			}
3420
+			foreach ($sub_query_params as $param) {
3421
+				// $param could be simply 'EVT_ID', or it could be 'Registrations.REG_ID', or even 'Registrations.Transactions.Payments.PAY_amount'
3422
+				$this->_extract_related_model_info_from_query_param(
3423
+					$param,
3424
+					$model_query_info_carrier,
3425
+					$query_param_type
3426
+				);
3427
+			}
3428
+		}
3429
+		return $model_query_info_carrier;
3430
+	}
3431
+
3432
+
3433
+	/**
3434
+	 * Extract all the query parts from  model query params
3435
+	 * and put into a EEM_Related_Model_Info_Carrier for easy extraction into a query. We create this object
3436
+	 * instead of directly constructing the SQL because often we need to extract info from the $query_params
3437
+	 * but use them in a different order. Eg, we need to know what models we are querying
3438
+	 * before we know what joins to perform. However, we need to know what data types correspond to which fields on
3439
+	 * other models before we can finalize the where clause SQL.
3440
+	 *
3441
+	 * @param array $query_params @see
3442
+	 *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
3443
+	 * @return EE_Model_Query_Info_Carrier
3444
+	 * @throws EE_Error
3445
+	 * @throws ModelConfigurationException*@throws ReflectionException
3446
+	 * @throws ReflectionException
3447
+	 */
3448
+	public function _create_model_query_info_carrier($query_params)
3449
+	{
3450
+		if (! is_array($query_params)) {
3451
+			EE_Error::doing_it_wrong(
3452
+				'EEM_Base::_create_model_query_info_carrier',
3453
+				sprintf(
3454
+					esc_html__(
3455
+						'$query_params should be an array, you passed a variable of type %s',
3456
+						'event_espresso'
3457
+					),
3458
+					gettype($query_params)
3459
+				),
3460
+				'4.6.0'
3461
+			);
3462
+			$query_params = [];
3463
+		}
3464
+		$query_params[0] = isset($query_params[0]) ? $query_params[0] : [];
3465
+		// first check if we should alter the query to account for caps or not
3466
+		// because the caps might require us to do extra joins
3467
+		if (isset($query_params['caps']) && $query_params['caps'] !== 'none') {
3468
+			$query_params[0] = array_replace_recursive(
3469
+				$query_params[0],
3470
+				$this->caps_where_conditions($query_params['caps'])
3471
+			);
3472
+		}
3473
+
3474
+		// check if we should alter the query to remove data related to protected
3475
+		// custom post types
3476
+		if (isset($query_params['exclude_protected']) && $query_params['exclude_protected'] === true) {
3477
+			$where_param_key_for_password = $this->modelChainAndPassword();
3478
+			// only include if related to a cpt where no password has been set
3479
+			$query_params[0]['OR*nopassword'] = [
3480
+				$where_param_key_for_password       => '',
3481
+				$where_param_key_for_password . '*' => ['IS_NULL'],
3482
+			];
3483
+		}
3484
+		$query_object = $this->_extract_related_models_from_query($query_params);
3485
+		// verify where_query_params has NO numeric indexes.... that's simply not how you use it!
3486
+		foreach ($query_params[0] as $key => $value) {
3487
+			if (is_int($key)) {
3488
+				throw new EE_Error(
3489
+					sprintf(
3490
+						esc_html__(
3491
+							"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.",
3492
+							"event_espresso"
3493
+						),
3494
+						$key,
3495
+						var_export($value, true),
3496
+						var_export($query_params, true),
3497
+						get_class($this)
3498
+					)
3499
+				);
3500
+			}
3501
+		}
3502
+		if (
3503
+			array_key_exists('default_where_conditions', $query_params)
3504
+			&& ! empty($query_params['default_where_conditions'])
3505
+		) {
3506
+			$use_default_where_conditions = $query_params['default_where_conditions'];
3507
+		} else {
3508
+			$use_default_where_conditions = EEM_Base::default_where_conditions_all;
3509
+		}
3510
+		$query_params[0] = array_merge(
3511
+			$this->_get_default_where_conditions_for_models_in_query(
3512
+				$query_object,
3513
+				$use_default_where_conditions,
3514
+				$query_params[0]
3515
+			),
3516
+			$query_params[0]
3517
+		);
3518
+		$query_object->set_where_sql($this->_construct_where_clause($query_params[0]));
3519
+		// if this is a "on_join_limit" then we are limiting on on a specific table in a multi_table join.
3520
+		// So we need to setup a subquery and use that for the main join.
3521
+		// Note for now this only works on the primary table for the model.
3522
+		// So for instance, you could set the limit array like this:
3523
+		// array( 'on_join_limit' => array('Primary_Table_Alias', array(1,10) ) )
3524
+		if (array_key_exists('on_join_limit', $query_params) && ! empty($query_params['on_join_limit'])) {
3525
+			$query_object->set_main_model_join_sql(
3526
+				$this->_construct_limit_join_select(
3527
+					$query_params['on_join_limit'][0],
3528
+					$query_params['on_join_limit'][1]
3529
+				)
3530
+			);
3531
+		}
3532
+		// set limit
3533
+		if (array_key_exists('limit', $query_params)) {
3534
+			if (is_array($query_params['limit'])) {
3535
+				if (! isset($query_params['limit'][0], $query_params['limit'][1])) {
3536
+					$e = sprintf(
3537
+						esc_html__(
3538
+							"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)",
3539
+							"event_espresso"
3540
+						),
3541
+						http_build_query($query_params['limit'])
3542
+					);
3543
+					throw new EE_Error($e . "|" . $e);
3544
+				}
3545
+				// they passed us an array for the limit. Assume it's like array(50,25), meaning offset by 50, and get 25
3546
+				$query_object->set_limit_sql(" LIMIT " . $query_params['limit'][0] . "," . $query_params['limit'][1]);
3547
+			} elseif (! empty($query_params['limit'])) {
3548
+				$query_object->set_limit_sql(" LIMIT " . $query_params['limit']);
3549
+			}
3550
+		}
3551
+		// set order by
3552
+		if (array_key_exists('order_by', $query_params)) {
3553
+			if (is_array($query_params['order_by'])) {
3554
+				// if they're using 'order_by' as an array, they can't use 'order' (because 'order_by' must
3555
+				// specify whether to ascend or descend on each field. Eg 'order_by'=>array('EVT_ID'=>'ASC'). So
3556
+				// including 'order' wouldn't make any sense if 'order_by' has already specified which way to order!
3557
+				if (array_key_exists('order', $query_params)) {
3558
+					throw new EE_Error(
3559
+						sprintf(
3560
+							esc_html__(
3561
+								"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 ",
3562
+								"event_espresso"
3563
+							),
3564
+							get_class($this),
3565
+							implode(", ", array_keys($query_params['order_by'])),
3566
+							implode(", ", $query_params['order_by']),
3567
+							$query_params['order']
3568
+						)
3569
+					);
3570
+				}
3571
+				$this->_extract_related_models_from_sub_params_array_keys(
3572
+					$query_params['order_by'],
3573
+					$query_object,
3574
+					'order_by'
3575
+				);
3576
+				// assume it's an array of fields to order by
3577
+				$order_array = [];
3578
+				foreach ($query_params['order_by'] as $field_name_to_order_by => $order) {
3579
+					$order         = $this->_extract_order($order);
3580
+					$order_array[] = $this->_deduce_column_name_from_query_param($field_name_to_order_by) . SP . $order;
3581
+				}
3582
+				$query_object->set_order_by_sql(" ORDER BY " . implode(",", $order_array));
3583
+			} elseif (! empty($query_params['order_by'])) {
3584
+				$this->_extract_related_model_info_from_query_param(
3585
+					$query_params['order_by'],
3586
+					$query_object,
3587
+					'order',
3588
+					$query_params['order_by']
3589
+				);
3590
+				$order = isset($query_params['order'])
3591
+					? $this->_extract_order($query_params['order'])
3592
+					: 'DESC';
3593
+				$query_object->set_order_by_sql(
3594
+					" ORDER BY " . $this->_deduce_column_name_from_query_param($query_params['order_by']) . SP . $order
3595
+				);
3596
+			}
3597
+		}
3598
+		// if 'order_by' wasn't set, maybe they are just using 'order' on its own?
3599
+		if (
3600
+			! array_key_exists('order_by', $query_params)
3601
+			&& array_key_exists('order', $query_params)
3602
+			&& ! empty($query_params['order'])
3603
+		) {
3604
+			$pk_field = $this->get_primary_key_field();
3605
+			$order    = $this->_extract_order($query_params['order']);
3606
+			$query_object->set_order_by_sql(" ORDER BY " . $pk_field->get_qualified_column() . SP . $order);
3607
+		}
3608
+		// set group by
3609
+		if (array_key_exists('group_by', $query_params)) {
3610
+			if (is_array($query_params['group_by'])) {
3611
+				// it's an array, so assume we'll be grouping by a bunch of stuff
3612
+				$group_by_array = [];
3613
+				foreach ($query_params['group_by'] as $field_name_to_group_by) {
3614
+					$group_by_array[] = $this->_deduce_column_name_from_query_param($field_name_to_group_by);
3615
+				}
3616
+				$query_object->set_group_by_sql(" GROUP BY " . implode(", ", $group_by_array));
3617
+			} elseif (! empty($query_params['group_by'])) {
3618
+				$query_object->set_group_by_sql(
3619
+					" GROUP BY " . $this->_deduce_column_name_from_query_param($query_params['group_by'])
3620
+				);
3621
+			}
3622
+		}
3623
+		// set having
3624
+		if (array_key_exists('having', $query_params) && $query_params['having']) {
3625
+			$query_object->set_having_sql($this->_construct_having_clause($query_params['having']));
3626
+		}
3627
+		// now, just verify they didn't pass anything wack
3628
+		foreach ($query_params as $query_key => $query_value) {
3629
+			if (! in_array($query_key, $this->_allowed_query_params, true)) {
3630
+				throw new EE_Error(
3631
+					sprintf(
3632
+						esc_html__(
3633
+							"You passed %s as a query parameter to %s, which is illegal! The allowed query parameters are %s",
3634
+							'event_espresso'
3635
+						),
3636
+						$query_key,
3637
+						get_class($this),
3638
+						//                      print_r( $this->_allowed_query_params, TRUE )
3639
+						implode(',', $this->_allowed_query_params)
3640
+					)
3641
+				);
3642
+			}
3643
+		}
3644
+		$main_model_join_sql = $query_object->get_main_model_join_sql();
3645
+		if (empty($main_model_join_sql)) {
3646
+			$query_object->set_main_model_join_sql($this->_construct_internal_join());
3647
+		}
3648
+		return $query_object;
3649
+	}
3650
+
3651
+
3652
+	/**
3653
+	 * Gets the where conditions that should be imposed on the query based on the
3654
+	 * context (eg reading frontend, backend, edit or delete).
3655
+	 *
3656
+	 * @param string $context one of EEM_Base::valid_cap_contexts()
3657
+	 * @return array @see
3658
+	 *               https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3659
+	 * @throws EE_Error
3660
+	 */
3661
+	public function caps_where_conditions($context = self::caps_read)
3662
+	{
3663
+		EEM_Base::verify_is_valid_cap_context($context);
3664
+		$cap_where_conditions = [];
3665
+		$cap_restrictions     = $this->caps_missing($context);
3666
+		/**
3667
+		 * @var $cap_restrictions EE_Default_Where_Conditions[]
3668
+		 */
3669
+		foreach ($cap_restrictions as $cap => $restriction_if_no_cap) {
3670
+			$cap_where_conditions = array_replace_recursive(
3671
+				$cap_where_conditions,
3672
+				$restriction_if_no_cap->get_default_where_conditions()
3673
+			);
3674
+		}
3675
+		return apply_filters(
3676
+			'FHEE__EEM_Base__caps_where_conditions__return',
3677
+			$cap_where_conditions,
3678
+			$this,
3679
+			$context,
3680
+			$cap_restrictions
3681
+		);
3682
+	}
3683
+
3684
+
3685
+	/**
3686
+	 * Verifies that $should_be_order_string is in $this->_allowed_order_values,
3687
+	 * otherwise throws an exception
3688
+	 *
3689
+	 * @param string $should_be_order_string
3690
+	 * @return string either ASC, asc, DESC or desc
3691
+	 * @throws EE_Error
3692
+	 */
3693
+	private function _extract_order($should_be_order_string)
3694
+	{
3695
+		if (in_array($should_be_order_string, $this->_allowed_order_values)) {
3696
+			return $should_be_order_string;
3697
+		}
3698
+		throw new EE_Error(
3699
+			sprintf(
3700
+				esc_html__(
3701
+					"While performing a query on '%s', tried to use '%s' as an order parameter. ",
3702
+					"event_espresso"
3703
+				),
3704
+				get_class($this),
3705
+				$should_be_order_string
3706
+			)
3707
+		);
3708
+	}
3709
+
3710
+
3711
+	/**
3712
+	 * Looks at all the models which are included in this query, and asks each
3713
+	 * for their universal_where_params, and returns them in the same format as $query_params[0] (where),
3714
+	 * so they can be merged
3715
+	 *
3716
+	 * @param EE_Model_Query_Info_Carrier $query_info_carrier
3717
+	 * @param string                      $use_default_where_conditions can be 'none','other_models_only', or 'all'.
3718
+	 *                                                                  'none' means NO default where conditions will
3719
+	 *                                                                  be used AT ALL during this query.
3720
+	 *                                                                  'other_models_only' means default where
3721
+	 *                                                                  conditions from other models will be used, but
3722
+	 *                                                                  not for this primary model. 'all', the default,
3723
+	 *                                                                  means default where conditions will apply as
3724
+	 *                                                                  normal
3725
+	 * @param array                       $where_query_params           @see
3726
+	 *                                                                  https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3727
+	 * @throws EE_Error
3728
+	 */
3729
+	private function _get_default_where_conditions_for_models_in_query(
3730
+		EE_Model_Query_Info_Carrier $query_info_carrier,
3731
+		$use_default_where_conditions = EEM_Base::default_where_conditions_all,
3732
+		$where_query_params = []
3733
+	) {
3734
+		$allowed_used_default_where_conditions_values = EEM_Base::valid_default_where_conditions();
3735
+		if (! in_array($use_default_where_conditions, $allowed_used_default_where_conditions_values)) {
3736
+			throw new EE_Error(
3737
+				sprintf(
3738
+					esc_html__(
3739
+						"You passed an invalid value to the query parameter 'default_where_conditions' of '%s'. Allowed values are %s",
3740
+						"event_espresso"
3741
+					),
3742
+					$use_default_where_conditions,
3743
+					implode(", ", $allowed_used_default_where_conditions_values)
3744
+				)
3745
+			);
3746
+		}
3747
+		$universal_query_params = [];
3748
+		if ($this->_should_use_default_where_conditions($use_default_where_conditions, true)) {
3749
+			$universal_query_params = $this->_get_default_where_conditions();
3750
+		} elseif ($this->_should_use_minimum_where_conditions($use_default_where_conditions, true)) {
3751
+			$universal_query_params = $this->_get_minimum_where_conditions();
3752
+		}
3753
+		foreach ($query_info_carrier->get_model_names_included() as $model_relation_path => $model_name) {
3754
+			$related_model = $this->get_related_model_obj($model_name);
3755
+			if ($this->_should_use_default_where_conditions($use_default_where_conditions, false)) {
3756
+				$related_model_universal_where_params =
3757
+					$related_model->_get_default_where_conditions($model_relation_path);
3758
+			} elseif ($this->_should_use_minimum_where_conditions($use_default_where_conditions, false)) {
3759
+				$related_model_universal_where_params =
3760
+					$related_model->_get_minimum_where_conditions($model_relation_path);
3761
+			} else {
3762
+				// we don't want to add full or even minimum default where conditions from this model, so just continue
3763
+				continue;
3764
+			}
3765
+			$overrides              = $this->_override_defaults_or_make_null_friendly(
3766
+				$related_model_universal_where_params,
3767
+				$where_query_params,
3768
+				$related_model,
3769
+				$model_relation_path
3770
+			);
3771
+			$universal_query_params = EEH_Array::merge_arrays_and_overwrite_keys(
3772
+				$universal_query_params,
3773
+				$overrides
3774
+			);
3775
+		}
3776
+		return $universal_query_params;
3777
+	}
3778
+
3779
+
3780
+	/**
3781
+	 * Determines whether or not we should use default where conditions for the model in question
3782
+	 * (this model, or other related models).
3783
+	 * Basically, we should use default where conditions on this model if they have requested to use them on all models,
3784
+	 * this model only, or to use minimum where conditions on all other models and normal where conditions on this one.
3785
+	 * We should use default where conditions on related models when they requested to use default where conditions
3786
+	 * on all models, or specifically just on other related models
3787
+	 *
3788
+	 * @param      $default_where_conditions_value
3789
+	 * @param bool $for_this_model false means this is for OTHER related models
3790
+	 * @return bool
3791
+	 */
3792
+	private function _should_use_default_where_conditions($default_where_conditions_value, $for_this_model = true)
3793
+	{
3794
+		return (
3795
+				   $for_this_model
3796
+				   && in_array(
3797
+					   $default_where_conditions_value,
3798
+					   [
3799
+						   EEM_Base::default_where_conditions_all,
3800
+						   EEM_Base::default_where_conditions_this_only,
3801
+						   EEM_Base::default_where_conditions_minimum_others,
3802
+					   ],
3803
+					   true
3804
+				   )
3805
+			   )
3806
+			   || (
3807
+				   ! $for_this_model
3808
+				   && in_array(
3809
+					   $default_where_conditions_value,
3810
+					   [
3811
+						   EEM_Base::default_where_conditions_all,
3812
+						   EEM_Base::default_where_conditions_others_only,
3813
+					   ],
3814
+					   true
3815
+				   )
3816
+			   );
3817
+	}
3818
+
3819
+
3820
+	/**
3821
+	 * Determines whether or not we should use default minimum conditions for the model in question
3822
+	 * (this model, or other related models).
3823
+	 * Basically, we should use minimum where conditions on this model only if they requested all models to use minimum
3824
+	 * where conditions.
3825
+	 * We should use minimum where conditions on related models if they requested to use minimum where conditions
3826
+	 * on this model or others
3827
+	 *
3828
+	 * @param      $default_where_conditions_value
3829
+	 * @param bool $for_this_model false means this is for OTHER related models
3830
+	 * @return bool
3831
+	 */
3832
+	private function _should_use_minimum_where_conditions($default_where_conditions_value, $for_this_model = true)
3833
+	{
3834
+		return (
3835
+				   $for_this_model
3836
+				   && $default_where_conditions_value === EEM_Base::default_where_conditions_minimum_all
3837
+			   )
3838
+			   || (
3839
+				   ! $for_this_model
3840
+				   && in_array(
3841
+					   $default_where_conditions_value,
3842
+					   [
3843
+						   EEM_Base::default_where_conditions_minimum_others,
3844
+						   EEM_Base::default_where_conditions_minimum_all,
3845
+					   ],
3846
+					   true
3847
+				   )
3848
+			   );
3849
+	}
3850
+
3851
+
3852
+	/**
3853
+	 * Checks if any of the defaults have been overridden. If there are any that AREN'T overridden,
3854
+	 * then we also add a special where condition which allows for that model's primary key
3855
+	 * to be null (which is important for JOINs. Eg, if you want to see all Events ordered by Venue's name,
3856
+	 * then Event's with NO Venue won't appear unless you allow VNU_ID to be NULL)
3857
+	 *
3858
+	 * @param array    $default_where_conditions
3859
+	 * @param array    $provided_where_conditions
3860
+	 * @param EEM_Base $model
3861
+	 * @param string   $model_relation_path like 'Transaction.Payment.'
3862
+	 * @return array @see
3863
+	 *               https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3864
+	 * @throws EE_Error
3865
+	 */
3866
+	private function _override_defaults_or_make_null_friendly(
3867
+		$default_where_conditions,
3868
+		$provided_where_conditions,
3869
+		$model,
3870
+		$model_relation_path
3871
+	) {
3872
+		$null_friendly_where_conditions = [];
3873
+		$none_overridden                = true;
3874
+		$or_condition_key_for_defaults  = 'OR*' . get_class($model);
3875
+		foreach ($default_where_conditions as $key => $val) {
3876
+			if (isset($provided_where_conditions[ $key ])) {
3877
+				$none_overridden = false;
3878
+			} else {
3879
+				$null_friendly_where_conditions[ $or_condition_key_for_defaults ]['AND'][ $key ] = $val;
3880
+			}
3881
+		}
3882
+		if ($none_overridden && $default_where_conditions) {
3883
+			if ($model->has_primary_key_field()) {
3884
+				$null_friendly_where_conditions[ $or_condition_key_for_defaults ][ $model_relation_path
3885
+																				   . "."
3886
+																				   . $model->primary_key_name() ] =
3887
+					['IS NULL'];
3888
+			}/*else{
3889 3889
                 //@todo NO PK, use other defaults
3890 3890
             }*/
3891
-        }
3892
-        return $null_friendly_where_conditions;
3893
-    }
3894
-
3895
-
3896
-    /**
3897
-     * Uses the _default_where_conditions_strategy set during __construct() to get
3898
-     * default where conditions on all get_all, update, and delete queries done by this model.
3899
-     * Use the same syntax as client code. Eg on the Event model, use array('Event.EVT_post_type'=>'esp_event'),
3900
-     * NOT array('Event_CPT.post_type'=>'esp_event').
3901
-     *
3902
-     * @param string $model_relation_path eg, path from Event to Payment is "Registration.Transaction.Payment."
3903
-     * @return array @see
3904
-     *                                    https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3905
-     * @throws EE_Error
3906
-     * @throws EE_Error
3907
-     */
3908
-    private function _get_default_where_conditions($model_relation_path = '')
3909
-    {
3910
-        if ($this->_ignore_where_strategy) {
3911
-            return [];
3912
-        }
3913
-        return $this->_default_where_conditions_strategy->get_default_where_conditions($model_relation_path);
3914
-    }
3915
-
3916
-
3917
-    /**
3918
-     * Uses the _minimum_where_conditions_strategy set during __construct() to get
3919
-     * minimum where conditions on all get_all, update, and delete queries done by this model.
3920
-     * Use the same syntax as client code. Eg on the Event model, use array('Event.EVT_post_type'=>'esp_event'),
3921
-     * NOT array('Event_CPT.post_type'=>'esp_event').
3922
-     * Similar to _get_default_where_conditions
3923
-     *
3924
-     * @param string $model_relation_path eg, path from Event to Payment is "Registration.Transaction.Payment."
3925
-     * @return array @see
3926
-     *                                    https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3927
-     * @throws EE_Error
3928
-     * @throws EE_Error
3929
-     */
3930
-    protected function _get_minimum_where_conditions($model_relation_path = '')
3931
-    {
3932
-        if ($this->_ignore_where_strategy) {
3933
-            return [];
3934
-        }
3935
-        return $this->_minimum_where_conditions_strategy->get_default_where_conditions($model_relation_path);
3936
-    }
3937
-
3938
-
3939
-    /**
3940
-     * Creates the string of SQL for the select part of a select query, everything behind SELECT and before FROM.
3941
-     * Eg, "Event.post_id, Event.post_name,Event_Detail.EVT_ID..."
3942
-     *
3943
-     * @param EE_Model_Query_Info_Carrier $model_query_info
3944
-     * @return string
3945
-     * @throws EE_Error
3946
-     */
3947
-    private function _construct_default_select_sql(EE_Model_Query_Info_Carrier $model_query_info)
3948
-    {
3949
-        $selects = $this->_get_columns_to_select_for_this_model();
3950
-        foreach (
3951
-            $model_query_info->get_model_names_included() as $model_relation_chain => $name_of_other_model_included
3952
-        ) {
3953
-            $other_model_included = $this->get_related_model_obj($name_of_other_model_included);
3954
-            $other_model_selects  = $other_model_included->_get_columns_to_select_for_this_model($model_relation_chain);
3955
-            foreach ($other_model_selects as $key => $value) {
3956
-                $selects[] = $value;
3957
-            }
3958
-        }
3959
-        return implode(", ", $selects);
3960
-    }
3961
-
3962
-
3963
-    /**
3964
-     * Gets an array of columns to select for this model, which are necessary for it to create its objects.
3965
-     * So that's going to be the columns for all the fields on the model
3966
-     *
3967
-     * @param string $model_relation_chain like 'Question.Question_Group.Event'
3968
-     * @return array numerically indexed, values are columns to select and rename, eg "Event.ID AS 'Event.ID'"
3969
-     */
3970
-    public function _get_columns_to_select_for_this_model($model_relation_chain = '')
3971
-    {
3972
-        $fields                                       = $this->field_settings();
3973
-        $selects                                      = [];
3974
-        $table_alias_with_model_relation_chain_prefix =
3975
-            EE_Model_Parser::extract_table_alias_model_relation_chain_prefix(
3976
-                $model_relation_chain,
3977
-                $this->get_this_model_name()
3978
-            );
3979
-        foreach ($fields as $field_obj) {
3980
-            $selects[] = $table_alias_with_model_relation_chain_prefix
3981
-                         . $field_obj->get_table_alias()
3982
-                         . "."
3983
-                         . $field_obj->get_table_column()
3984
-                         . " AS '"
3985
-                         . $table_alias_with_model_relation_chain_prefix
3986
-                         . $field_obj->get_table_alias()
3987
-                         . "."
3988
-                         . $field_obj->get_table_column()
3989
-                         . "'";
3990
-        }
3991
-        // make sure we are also getting the PKs of each table
3992
-        $tables = $this->get_tables();
3993
-        if (count($tables) > 1) {
3994
-            foreach ($tables as $table_obj) {
3995
-                $qualified_pk_column = $table_alias_with_model_relation_chain_prefix
3996
-                                       . $table_obj->get_fully_qualified_pk_column();
3997
-                if (! in_array($qualified_pk_column, $selects)) {
3998
-                    $selects[] = "$qualified_pk_column AS '$qualified_pk_column'";
3999
-                }
4000
-            }
4001
-        }
4002
-        return $selects;
4003
-    }
4004
-
4005
-
4006
-    /**
4007
-     * Given a $query_param like 'Registration.Transaction.TXN_ID', pops off 'Registration.',
4008
-     * gets the join statement for it; gets the data types for it; and passes the remaining 'Transaction.TXN_ID'
4009
-     * onto its related Transaction object to do the same. Returns an EE_Join_And_Data_Types object which contains the
4010
-     * SQL for joining, and the data types
4011
-     *
4012
-     * @param null|string                 $original_query_param
4013
-     * @param string                      $query_param          like Registration.Transaction.TXN_ID
4014
-     * @param EE_Model_Query_Info_Carrier $passed_in_query_info
4015
-     * @param string                      $query_param_type     like Registration.Transaction.TXN_ID
4016
-     *                                                          or 'PAY_ID'. Otherwise, we don't expect there to be a
4017
-     *                                                          column name. We only want model names, eg 'Event.Venue'
4018
-     *                                                          or 'Registration's
4019
-     * @param string                      $original_query_param what it originally was (eg
4020
-     *                                                          Registration.Transaction.TXN_ID). If null, we assume it
4021
-     *                                                          matches $query_param
4022
-     * @return void only modifies the EEM_Related_Model_Info_Carrier passed into it
4023
-     * @throws EE_Error
4024
-     */
4025
-    private function _extract_related_model_info_from_query_param(
4026
-        $query_param,
4027
-        EE_Model_Query_Info_Carrier $passed_in_query_info,
4028
-        $query_param_type,
4029
-        $original_query_param = null
4030
-    ) {
4031
-        if ($original_query_param === null) {
4032
-            $original_query_param = $query_param;
4033
-        }
4034
-        $query_param = $this->_remove_stars_and_anything_after_from_condition_query_param_key($query_param);
4035
-        // check to see if we have a field on this model
4036
-        $this_model_fields = $this->field_settings(true);
4037
-        if (array_key_exists($query_param, $this_model_fields)) {
4038
-            $field_is_allowed = in_array(
4039
-                $query_param_type,
4040
-                [0, 'where', 'having', 'order_by', 'group_by', 'order', 'custom_selects'],
4041
-                true
4042
-            );
4043
-            if ($field_is_allowed) {
4044
-                return;
4045
-            }
4046
-            throw new EE_Error(
4047
-                sprintf(
4048
-                    esc_html__(
4049
-                        "Using a field name (%s) on model %s is not allowed on this query param type '%s'. Original query param was %s",
4050
-                        "event_espresso"
4051
-                    ),
4052
-                    $query_param,
4053
-                    get_class($this),
4054
-                    $query_param_type,
4055
-                    $original_query_param
4056
-                )
4057
-            );
4058
-        }
4059
-        // check if this is a special logic query param
4060
-        if (in_array($query_param, $this->_logic_query_param_keys, true)) {
4061
-            $operator_is_allowed = in_array($query_param_type, ['where', 'having', 0, 'custom_selects'], true);
4062
-            if ($operator_is_allowed) {
4063
-                return;
4064
-            }
4065
-            throw new EE_Error(
4066
-                sprintf(
4067
-                    esc_html__(
4068
-                        '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',
4069
-                        'event_espresso'
4070
-                    ),
4071
-                    implode('", "', $this->_logic_query_param_keys),
4072
-                    $query_param,
4073
-                    get_class($this),
4074
-                    '<br />',
4075
-                    "\t"
4076
-                    . ' $passed_in_query_info = <pre>'
4077
-                    . print_r($passed_in_query_info, true)
4078
-                    . '</pre>'
4079
-                    . "\n\t"
4080
-                    . ' $query_param_type = '
4081
-                    . $query_param_type
4082
-                    . "\n\t"
4083
-                    . ' $original_query_param = '
4084
-                    . $original_query_param
4085
-                )
4086
-            );
4087
-        }
4088
-        // check if it's a custom selection
4089
-        if (
4090
-            $this->_custom_selections instanceof CustomSelects
4091
-            && in_array($query_param, $this->_custom_selections->columnAliases(), true)
4092
-        ) {
4093
-            return;
4094
-        }
4095
-        // check if has a model name at the beginning
4096
-        // and
4097
-        // check if it's a field on a related model
4098
-        if (
4099
-            $this->extractJoinModelFromQueryParams(
4100
-                $passed_in_query_info,
4101
-                $query_param,
4102
-                $original_query_param,
4103
-                $query_param_type
4104
-            )
4105
-        ) {
4106
-            return;
4107
-        }
4108
-
4109
-        // ok so $query_param didn't start with a model name
4110
-        // and we previously confirmed it wasn't a logic query param or field on the current model
4111
-        // it's wack, that's what it is
4112
-        throw new EE_Error(
4113
-            sprintf(
4114
-                esc_html__(
4115
-                    "There is no model named '%s' related to %s. Query param type is %s and original query param is %s",
4116
-                    "event_espresso"
4117
-                ),
4118
-                $query_param,
4119
-                get_class($this),
4120
-                $query_param_type,
4121
-                $original_query_param
4122
-            )
4123
-        );
4124
-    }
4125
-
4126
-
4127
-    /**
4128
-     * Extracts any possible join model information from the provided possible_join_string.
4129
-     * This method will read the provided $possible_join_string value and determine if there are any possible model
4130
-     * join
4131
-     * parts that should be added to the query.
4132
-     *
4133
-     * @param EE_Model_Query_Info_Carrier $query_info_carrier
4134
-     * @param string                      $possible_join_string  Such as Registration.REG_ID, or Registration
4135
-     * @param null|string                 $original_query_param
4136
-     * @param string                      $query_parameter_type  The type for the source of the $possible_join_string
4137
-     *                                                           ('where', 'order_by', 'group_by', 'custom_selects'
4138
-     *                                                           etc.)
4139
-     * @return bool  returns true if a join was added and false if not.
4140
-     * @throws EE_Error
4141
-     */
4142
-    private function extractJoinModelFromQueryParams(
4143
-        EE_Model_Query_Info_Carrier $query_info_carrier,
4144
-        $possible_join_string,
4145
-        $original_query_param,
4146
-        $query_parameter_type
4147
-    ) {
4148
-        foreach ($this->_model_relations as $valid_related_model_name => $relation_obj) {
4149
-            if (strpos($possible_join_string, $valid_related_model_name . ".") === 0) {
4150
-                $this->_add_join_to_model($valid_related_model_name, $query_info_carrier, $original_query_param);
4151
-                $possible_join_string = substr($possible_join_string, strlen($valid_related_model_name . "."));
4152
-                if ($possible_join_string === '') {
4153
-                    // nothing left to $query_param
4154
-                    // we should actually end in a field name, not a model like this!
4155
-                    throw new EE_Error(
4156
-                        sprintf(
4157
-                            esc_html__(
4158
-                                "Query param '%s' (of type %s on model %s) shouldn't end on a period (.) ",
4159
-                                "event_espresso"
4160
-                            ),
4161
-                            $possible_join_string,
4162
-                            $query_parameter_type,
4163
-                            get_class($this),
4164
-                            $valid_related_model_name
4165
-                        )
4166
-                    );
4167
-                }
4168
-                $related_model_obj = $this->get_related_model_obj($valid_related_model_name);
4169
-                $related_model_obj->_extract_related_model_info_from_query_param(
4170
-                    $possible_join_string,
4171
-                    $query_info_carrier,
4172
-                    $query_parameter_type,
4173
-                    $original_query_param
4174
-                );
4175
-                return true;
4176
-            }
4177
-            if ($possible_join_string === $valid_related_model_name) {
4178
-                $this->_add_join_to_model(
4179
-                    $valid_related_model_name,
4180
-                    $query_info_carrier,
4181
-                    $original_query_param
4182
-                );
4183
-                return true;
4184
-            }
4185
-        }
4186
-        return false;
4187
-    }
4188
-
4189
-
4190
-    /**
4191
-     * Extracts related models from Custom Selects and sets up any joins for those related models.
4192
-     *
4193
-     * @param EE_Model_Query_Info_Carrier $query_info_carrier
4194
-     * @throws EE_Error
4195
-     */
4196
-    private function extractRelatedModelsFromCustomSelects(EE_Model_Query_Info_Carrier $query_info_carrier)
4197
-    {
4198
-        if (
4199
-            $this->_custom_selections instanceof CustomSelects
4200
-            && (
4201
-                $this->_custom_selections->type() === CustomSelects::TYPE_STRUCTURED
4202
-                || $this->_custom_selections->type() == CustomSelects::TYPE_COMPLEX
4203
-            )
4204
-        ) {
4205
-            $original_selects = $this->_custom_selections->originalSelects();
4206
-            foreach ($original_selects as $alias => $select_configuration) {
4207
-                $this->extractJoinModelFromQueryParams(
4208
-                    $query_info_carrier,
4209
-                    $select_configuration[0],
4210
-                    $select_configuration[0],
4211
-                    'custom_selects'
4212
-                );
4213
-            }
4214
-        }
4215
-    }
4216
-
4217
-
4218
-    /**
4219
-     * Privately used by _extract_related_model_info_from_query_param to add a join to $model_name
4220
-     * and store it on $passed_in_query_info
4221
-     *
4222
-     * @param string                      $model_name
4223
-     * @param EE_Model_Query_Info_Carrier $passed_in_query_info
4224
-     * @param string                      $original_query_param used to extract the relation chain between the queried
4225
-     *                                                          model and $model_name. Eg, if we are querying Event,
4226
-     *                                                          and are adding a join to 'Payment' with the original
4227
-     *                                                          query param key
4228
-     *                                                          'Registration.Transaction.Payment.PAY_amount', we want
4229
-     *                                                          to extract 'Registration.Transaction.Payment', in case
4230
-     *                                                          Payment wants to add default query params so that it
4231
-     *                                                          will know what models to prepend onto its default query
4232
-     *                                                          params or in case it wants to rename tables (in case
4233
-     *                                                          there are multiple joins to the same table)
4234
-     * @return void
4235
-     * @throws EE_Error
4236
-     */
4237
-    private function _add_join_to_model(
4238
-        $model_name,
4239
-        EE_Model_Query_Info_Carrier $passed_in_query_info,
4240
-        $original_query_param
4241
-    ) {
4242
-        $relation_obj         = $this->related_settings_for($model_name);
4243
-        $model_relation_chain = EE_Model_Parser::extract_model_relation_chain($model_name, $original_query_param);
4244
-        // check if the relation is HABTM, because then we're essentially doing two joins
4245
-        // If so, join first to the JOIN table, and add its data types, and then continue as normal
4246
-        if ($relation_obj instanceof EE_HABTM_Relation) {
4247
-            $join_model_obj = $relation_obj->get_join_model();
4248
-            // replace the model specified with the join model for this relation chain, whi
4249
-            $relation_chain_to_join_model =
4250
-                EE_Model_Parser::replace_model_name_with_join_model_name_in_model_relation_chain(
4251
-                    $model_name,
4252
-                    $join_model_obj->get_this_model_name(),
4253
-                    $model_relation_chain
4254
-                );
4255
-            $passed_in_query_info->merge(
4256
-                new EE_Model_Query_Info_Carrier(
4257
-                    [$relation_chain_to_join_model => $join_model_obj->get_this_model_name()],
4258
-                    $relation_obj->get_join_to_intermediate_model_statement($relation_chain_to_join_model)
4259
-                )
4260
-            );
4261
-        }
4262
-        // now just join to the other table pointed to by the relation object, and add its data types
4263
-        $passed_in_query_info->merge(
4264
-            new EE_Model_Query_Info_Carrier(
4265
-                [$model_relation_chain => $model_name],
4266
-                $relation_obj->get_join_statement($model_relation_chain)
4267
-            )
4268
-        );
4269
-    }
4270
-
4271
-
4272
-    /**
4273
-     * Constructs SQL for where clause, like "WHERE Event.ID = 23 AND Transaction.amount > 100" etc.
4274
-     *
4275
-     * @param array $where_params @see
4276
-     *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
4277
-     * @return string of SQL
4278
-     * @throws EE_Error
4279
-     */
4280
-    private function _construct_where_clause($where_params)
4281
-    {
4282
-        $SQL = $this->_construct_condition_clause_recursive($where_params, ' AND ');
4283
-        if ($SQL) {
4284
-            return " WHERE " . $SQL;
4285
-        }
4286
-        return '';
4287
-    }
4288
-
4289
-
4290
-    /**
4291
-     * Just like the _construct_where_clause, except prepends 'HAVING' instead of 'WHERE',
4292
-     * and should be passed HAVING parameters, not WHERE parameters
4293
-     *
4294
-     * @param array $having_params
4295
-     * @return string
4296
-     * @throws EE_Error
4297
-     */
4298
-    private function _construct_having_clause($having_params)
4299
-    {
4300
-        $SQL = $this->_construct_condition_clause_recursive($having_params, ' AND ');
4301
-        if ($SQL) {
4302
-            return " HAVING " . $SQL;
4303
-        }
4304
-        return '';
4305
-    }
4306
-
4307
-
4308
-    /**
4309
-     * Used for creating nested WHERE conditions. Eg "WHERE ! (Event.ID = 3 OR ( Event_Meta.meta_key = 'bob' AND
4310
-     * Event_Meta.meta_value = 'foo'))"
4311
-     *
4312
-     * @param array  $where_params @see
4313
-     *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
4314
-     * @param string $glue         joins each subclause together. Should really only be " AND " or " OR "...
4315
-     * @return string of SQL
4316
-     * @throws EE_Error
4317
-     */
4318
-    private function _construct_condition_clause_recursive($where_params, $glue = ' AND')
4319
-    {
4320
-        $where_clauses = [];
4321
-        foreach ($where_params as $query_param => $op_and_value_or_sub_condition) {
4322
-            $query_param = $this->_remove_stars_and_anything_after_from_condition_query_param_key($query_param);
4323
-            if (in_array($query_param, $this->_logic_query_param_keys, true)) {
4324
-                switch ($query_param) {
4325
-                    case 'not':
4326
-                    case 'NOT':
4327
-                        $where_clauses[] = "! ("
4328
-                                           . $this->_construct_condition_clause_recursive(
4329
-                                               $op_and_value_or_sub_condition,
4330
-                                               $glue
4331
-                                           )
4332
-                                           . ")";
4333
-                        break;
4334
-                    case 'and':
4335
-                    case 'AND':
4336
-                        $where_clauses[] = " ("
4337
-                                           . $this->_construct_condition_clause_recursive(
4338
-                                               $op_and_value_or_sub_condition,
4339
-                                               ' AND '
4340
-                                           )
4341
-                                           . ")";
4342
-                        break;
4343
-                    case 'or':
4344
-                    case 'OR':
4345
-                        $where_clauses[] = " ("
4346
-                                           . $this->_construct_condition_clause_recursive(
4347
-                                               $op_and_value_or_sub_condition,
4348
-                                               ' OR '
4349
-                                           )
4350
-                                           . ")";
4351
-                        break;
4352
-                }
4353
-            } else {
4354
-                $field_obj = $this->_deduce_field_from_query_param($query_param);
4355
-                // if it's not a normal field, maybe it's a custom selection?
4356
-                if (! $field_obj) {
4357
-                    if ($this->_custom_selections instanceof CustomSelects) {
4358
-                        $field_obj = $this->_custom_selections->getDataTypeForAlias($query_param);
4359
-                    } else {
4360
-                        throw new EE_Error(
4361
-                            sprintf(
4362
-                                esc_html__(
4363
-                                    "%s is neither a valid model field name, nor a custom selection",
4364
-                                    "event_espresso"
4365
-                                ),
4366
-                                $query_param
4367
-                            )
4368
-                        );
4369
-                    }
4370
-                }
4371
-                $op_and_value_sql = $this->_construct_op_and_value($op_and_value_or_sub_condition, $field_obj);
4372
-                $where_clauses[]  = $this->_deduce_column_name_from_query_param($query_param) . SP . $op_and_value_sql;
4373
-            }
4374
-        }
4375
-        return $where_clauses ? implode($glue, $where_clauses) : '';
4376
-    }
4377
-
4378
-
4379
-    /**
4380
-     * Takes the input parameter and extract the table name (alias) and column name
4381
-     *
4382
-     * @param string $query_param like Registration.Transaction.TXN_ID, Event.Datetime.start_time, or REG_ID
4383
-     * @return string table alias and column name for SQL, eg "Transaction.TXN_ID"
4384
-     * @throws EE_Error
4385
-     */
4386
-    private function _deduce_column_name_from_query_param($query_param)
4387
-    {
4388
-        $field = $this->_deduce_field_from_query_param($query_param);
4389
-        if ($field) {
4390
-            $table_alias_prefix = EE_Model_Parser::extract_table_alias_model_relation_chain_from_query_param(
4391
-                $field->get_model_name(),
4392
-                $query_param
4393
-            );
4394
-            return $table_alias_prefix . $field->get_qualified_column();
4395
-        }
4396
-        if (
4397
-            $this->_custom_selections instanceof CustomSelects
4398
-            && in_array($query_param, $this->_custom_selections->columnAliases(), true)
4399
-        ) {
4400
-            // maybe it's custom selection item?
4401
-            // if so, just use it as the "column name"
4402
-            return $query_param;
4403
-        }
4404
-        $custom_select_aliases = $this->_custom_selections instanceof CustomSelects
4405
-            ? implode(',', $this->_custom_selections->columnAliases())
4406
-            : '';
4407
-        throw new EE_Error(
4408
-            sprintf(
4409
-                esc_html__(
4410
-                    "%s is not a valid field on this model, nor a custom selection (%s)",
4411
-                    "event_espresso"
4412
-                ),
4413
-                $query_param,
4414
-                $custom_select_aliases
4415
-            )
4416
-        );
4417
-    }
4418
-
4419
-
4420
-    /**
4421
-     * Removes the * and anything after it from the condition query param key. It is useful to add the * to condition
4422
-     * query param keys (eg, 'OR*', 'EVT_ID') in order for the array keys to still be unique, so that they don't get
4423
-     * overwritten Takes a string like 'Event.EVT_ID*', 'TXN_total**', 'OR*1st', and 'DTT_reg_start*foobar' to
4424
-     * 'Event.EVT_ID', 'TXN_total', 'OR', and 'DTT_reg_start', respectively.
4425
-     *
4426
-     * @param string $condition_query_param_key
4427
-     * @return string
4428
-     */
4429
-    private function _remove_stars_and_anything_after_from_condition_query_param_key($condition_query_param_key)
4430
-    {
4431
-        $pos_of_star = strpos($condition_query_param_key, '*');
4432
-        if ($pos_of_star === false) {
4433
-            return $condition_query_param_key;
4434
-        }
4435
-        $condition_query_param_sans_star = substr($condition_query_param_key, 0, $pos_of_star);
4436
-        return $condition_query_param_sans_star;
4437
-    }
4438
-
4439
-
4440
-    /**
4441
-     * creates the SQL for the operator and the value in a WHERE clause, eg "< 23" or "LIKE '%monkey%'"
4442
-     *
4443
-     * @param mixed      array | string    $op_and_value
4444
-     * @param EE_Model_Field_Base|string $field_obj . If string, should be one of EEM_Base::_valid_wpdb_data_types
4445
-     * @return string
4446
-     * @throws EE_Error
4447
-     */
4448
-    private function _construct_op_and_value($op_and_value, $field_obj)
4449
-    {
4450
-        if (is_array($op_and_value)) {
4451
-            $operator = isset($op_and_value[0]) ? $this->_prepare_operator_for_sql($op_and_value[0]) : null;
4452
-            if (! $operator) {
4453
-                $php_array_like_string = [];
4454
-                foreach ($op_and_value as $key => $value) {
4455
-                    $php_array_like_string[] = "$key=>$value";
4456
-                }
4457
-                throw new EE_Error(
4458
-                    sprintf(
4459
-                        esc_html__(
4460
-                            "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))",
4461
-                            "event_espresso"
4462
-                        ),
4463
-                        implode(",", $php_array_like_string)
4464
-                    )
4465
-                );
4466
-            }
4467
-            $value = isset($op_and_value[1]) ? $op_and_value[1] : null;
4468
-        } else {
4469
-            $operator = '=';
4470
-            $value    = $op_and_value;
4471
-        }
4472
-        // check to see if the value is actually another field
4473
-        if (is_array($op_and_value) && isset($op_and_value[2]) && $op_and_value[2] == true) {
4474
-            return $operator . SP . $this->_deduce_column_name_from_query_param($value);
4475
-        }
4476
-        if (in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4477
-            // in this case, the value should be an array, or at least a comma-separated list
4478
-            // it will need to handle a little differently
4479
-            $cleaned_value = $this->_construct_in_value($value, $field_obj);
4480
-            // note: $cleaned_value has already been run through $wpdb->prepare()
4481
-            return $operator . SP . $cleaned_value;
4482
-        }
4483
-        if (in_array($operator, $this->valid_between_style_operators()) && is_array($value)) {
4484
-            // the value should be an array with count of two.
4485
-            if (count($value) !== 2) {
4486
-                throw new EE_Error(
4487
-                    sprintf(
4488
-                        esc_html__(
4489
-                            "The '%s' operator must be used with an array of values and there must be exactly TWO values in that array.",
4490
-                            'event_espresso'
4491
-                        ),
4492
-                        "BETWEEN"
4493
-                    )
4494
-                );
4495
-            }
4496
-            $cleaned_value = $this->_construct_between_value($value, $field_obj);
4497
-            return $operator . SP . $cleaned_value;
4498
-        }
4499
-        if (in_array($operator, $this->valid_null_style_operators())) {
4500
-            if ($value !== null) {
4501
-                throw new EE_Error(
4502
-                    sprintf(
4503
-                        esc_html__(
4504
-                            "You attempted to give a value  (%s) while using a NULL-style operator (%s). That isn't valid",
4505
-                            "event_espresso"
4506
-                        ),
4507
-                        $value,
4508
-                        $operator
4509
-                    )
4510
-                );
4511
-            }
4512
-            return $operator;
4513
-        }
4514
-        if (in_array($operator, $this->valid_like_style_operators()) && ! is_array($value)) {
4515
-            // if the operator is 'LIKE', we want to allow percent signs (%) and not
4516
-            // remove other junk. So just treat it as a string.
4517
-            return $operator . SP . $this->_wpdb_prepare_using_field($value, '%s');
4518
-        }
4519
-        if (! in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4520
-            return $operator . SP . $this->_wpdb_prepare_using_field($value, $field_obj);
4521
-        }
4522
-        if (in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4523
-            throw new EE_Error(
4524
-                sprintf(
4525
-                    esc_html__(
4526
-                        "Operator '%s' must be used with an array of values, eg 'Registration.REG_ID' => array('%s',array(1,2,3))",
4527
-                        'event_espresso'
4528
-                    ),
4529
-                    $operator,
4530
-                    $operator
4531
-                )
4532
-            );
4533
-        }
4534
-        if (! in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4535
-            throw new EE_Error(
4536
-                sprintf(
4537
-                    esc_html__(
4538
-                        "Operator '%s' must be used with a single value, not an array. Eg 'Registration.REG_ID => array('%s',23))",
4539
-                        'event_espresso'
4540
-                    ),
4541
-                    $operator,
4542
-                    $operator
4543
-                )
4544
-            );
4545
-        }
4546
-        throw new EE_Error(
4547
-            sprintf(
4548
-                esc_html__(
4549
-                    "It appears you've provided some totally invalid query parameters. Operator and value were:'%s', which isn't right at all",
4550
-                    "event_espresso"
4551
-                ),
4552
-                http_build_query($op_and_value)
4553
-            )
4554
-        );
4555
-    }
4556
-
4557
-
4558
-    /**
4559
-     * Creates the operands to be used in a BETWEEN query, eg "'2014-12-31 20:23:33' AND '2015-01-23 12:32:54'"
4560
-     *
4561
-     * @param array                      $values
4562
-     * @param EE_Model_Field_Base|string $field_obj if string, it should be the datatype to be used when querying, eg
4563
-     *                                              '%s'
4564
-     * @return string
4565
-     * @throws EE_Error
4566
-     */
4567
-    public function _construct_between_value($values, $field_obj)
4568
-    {
4569
-        $cleaned_values = [];
4570
-        foreach ($values as $value) {
4571
-            $cleaned_values[] = $this->_wpdb_prepare_using_field($value, $field_obj);
4572
-        }
4573
-        return $cleaned_values[0] . " AND " . $cleaned_values[1];
4574
-    }
4575
-
4576
-
4577
-    /**
4578
-     * Takes an array or a comma-separated list of $values and cleans them
4579
-     * according to $data_type using $wpdb->prepare, and then makes the list a
4580
-     * string surrounded by ( and ). Eg, _construct_in_value(array(1,2,3),'%d') would
4581
-     * return '(1,2,3)'; _construct_in_value("1,2,hack",'%d') would return '(1,2,1)' (assuming
4582
-     * I'm right that a string, when interpreted as a digit, becomes a 1. It might become a 0)
4583
-     *
4584
-     * @param mixed                      $values    array or comma-separated string
4585
-     * @param EE_Model_Field_Base|string $field_obj if string, it should be a wpdb data type like '%s', or '%d'
4586
-     * @return string of SQL to follow an 'IN' or 'NOT IN' operator
4587
-     * @throws EE_Error
4588
-     */
4589
-    public function _construct_in_value($values, $field_obj)
4590
-    {
4591
-        $prepped = [];
4592
-        // check if the value is a CSV list
4593
-        if (is_string($values)) {
4594
-            // in which case, turn it into an array
4595
-            $values = explode(',', $values);
4596
-        }
4597
-        // make sure we only have one of each value in the list
4598
-        $values = array_unique($values);
4599
-        foreach ($values as $value) {
4600
-            $prepped[] = $this->_wpdb_prepare_using_field($value, $field_obj);
4601
-        }
4602
-        // we would just LOVE to leave $cleaned_values as an empty array, and return the value as "()",
4603
-        // but unfortunately that's invalid SQL. So instead we return a string which we KNOW will evaluate to be the empty set
4604
-        // which is effectively equivalent to returning "()". We don't return "(0)" because that only works for auto-incrementing columns
4605
-        if (empty($prepped)) {
4606
-            $all_fields  = $this->field_settings();
4607
-            $first_field = reset($all_fields);
4608
-            $main_table  = $this->_get_main_table();
4609
-            $prepped[]   = "SELECT {$first_field->get_table_column()} FROM {$main_table->get_table_name()} WHERE FALSE";
4610
-        }
4611
-        return '(' . implode(',', $prepped) . ')';
4612
-    }
4613
-
4614
-
4615
-    /**
4616
-     * @param mixed                      $value
4617
-     * @param EE_Model_Field_Base|string $field_obj if string it should be a wpdb data type like '%d'
4618
-     * @return false|null|string
4619
-     * @throws EE_Error
4620
-     */
4621
-    private function _wpdb_prepare_using_field($value, $field_obj)
4622
-    {
4623
-        /** @type WPDB $wpdb */
4624
-        global $wpdb;
4625
-        if ($field_obj instanceof EE_Model_Field_Base) {
4626
-            return $wpdb->prepare(
4627
-                $field_obj->get_wpdb_data_type(),
4628
-                $this->_prepare_value_for_use_in_db($value, $field_obj)
4629
-            );
4630
-        } //$field_obj should really just be a data type
4631
-        if (! in_array($field_obj, $this->_valid_wpdb_data_types)) {
4632
-            throw new EE_Error(
4633
-                sprintf(
4634
-                    esc_html__("%s is not a valid wpdb datatype. Valid ones are %s", "event_espresso"),
4635
-                    $field_obj,
4636
-                    implode(",", $this->_valid_wpdb_data_types)
4637
-                )
4638
-            );
4639
-        }
4640
-        return $wpdb->prepare($field_obj, $value);
4641
-    }
4642
-
4643
-
4644
-    /**
4645
-     * Takes the input parameter and finds the model field that it indicates.
4646
-     *
4647
-     * @param string $query_param_name like Registration.Transaction.TXN_ID, Event.Datetime.start_time, or REG_ID
4648
-     * @return EE_Model_Field_Base
4649
-     * @throws EE_Error
4650
-     */
4651
-    protected function _deduce_field_from_query_param($query_param_name)
4652
-    {
4653
-        // ok, now proceed with deducing which part is the model's name, and which is the field's name
4654
-        // which will help us find the database table and column
4655
-        $query_param_parts = explode(".", $query_param_name);
4656
-        if (empty($query_param_parts)) {
4657
-            throw new EE_Error(
4658
-                sprintf(
4659
-                    esc_html__(
4660
-                        "_extract_column_name is empty when trying to extract column and table name from %s",
4661
-                        'event_espresso'
4662
-                    ),
4663
-                    $query_param_name
4664
-                )
4665
-            );
4666
-        }
4667
-        $number_of_parts       = count($query_param_parts);
4668
-        $last_query_param_part = $query_param_parts[ count($query_param_parts) - 1 ];
4669
-        if ($number_of_parts === 1) {
4670
-            $field_name = $last_query_param_part;
4671
-            $model_obj  = $this;
4672
-        } else {// $number_of_parts >= 2
4673
-            // the last part is the column name, and there are only 2parts. therefore...
4674
-            $field_name = $last_query_param_part;
4675
-            $model_obj  = $this->get_related_model_obj($query_param_parts[ $number_of_parts - 2 ]);
4676
-        }
4677
-        try {
4678
-            return $model_obj->field_settings_for($field_name);
4679
-        } catch (EE_Error $e) {
4680
-            return null;
4681
-        }
4682
-    }
4683
-
4684
-
4685
-    /**
4686
-     * Given a field's name (ie, a key in $this->field_settings()), uses the EE_Model_Field object to get the table's
4687
-     * alias and column which corresponds to it
4688
-     *
4689
-     * @param string $field_name
4690
-     * @return string
4691
-     * @throws EE_Error
4692
-     */
4693
-    public function _get_qualified_column_for_field($field_name)
4694
-    {
4695
-        $all_fields = $this->field_settings();
4696
-        $field      = isset($all_fields[ $field_name ]) ? $all_fields[ $field_name ] : false;
4697
-        if ($field) {
4698
-            return $field->get_qualified_column();
4699
-        }
4700
-        throw new EE_Error(
4701
-            sprintf(
4702
-                esc_html__(
4703
-                    "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.",
4704
-                    'event_espresso'
4705
-                ),
4706
-                $field_name,
4707
-                get_class($this)
4708
-            )
4709
-        );
4710
-    }
4711
-
4712
-
4713
-    /**
4714
-     * similar to \EEM_Base::_get_qualified_column_for_field() but returns an array with data for ALL fields.
4715
-     * Example usage:
4716
-     * EEM_Ticket::instance()->get_all_wpdb_results(
4717
-     *      array(),
4718
-     *      ARRAY_A,
4719
-     *      EEM_Ticket::instance()->get_qualified_columns_for_all_fields()
4720
-     *  );
4721
-     * is equivalent to
4722
-     *  EEM_Ticket::instance()->get_all_wpdb_results( array(), ARRAY_A, '*' );
4723
-     * and
4724
-     *  EEM_Event::instance()->get_all_wpdb_results(
4725
-     *      array(
4726
-     *          array(
4727
-     *              'Datetime.Ticket.TKT_ID' => array( '<', 100 ),
4728
-     *          ),
4729
-     *          ARRAY_A,
4730
-     *          implode(
4731
-     *              ', ',
4732
-     *              array_merge(
4733
-     *                  EEM_Event::instance()->get_qualified_columns_for_all_fields( '', false ),
4734
-     *                  EEM_Ticket::instance()->get_qualified_columns_for_all_fields( 'Datetime', false )
4735
-     *              )
4736
-     *          )
4737
-     *      )
4738
-     *  );
4739
-     * selects rows from the database, selecting all the event and ticket columns, where the ticket ID is below 100
4740
-     *
4741
-     * @param string $model_relation_chain        the chain of models used to join between the model you want to query
4742
-     *                                            and the one whose fields you are selecting for example: when querying
4743
-     *                                            tickets model and selecting fields from the tickets model you would
4744
-     *                                            leave this parameter empty, because no models are needed to join
4745
-     *                                            between the queried model and the selected one. Likewise when
4746
-     *                                            querying the datetime model and selecting fields from the tickets
4747
-     *                                            model, it would also be left empty, because there is a direct
4748
-     *                                            relation from datetimes to tickets, so no model is needed to join
4749
-     *                                            them together. However, when querying from the event model and
4750
-     *                                            selecting fields from the ticket model, you should provide the string
4751
-     *                                            'Datetime', indicating that the event model must first join to the
4752
-     *                                            datetime model in order to find its relation to ticket model.
4753
-     *                                            Also, when querying from the venue model and selecting fields from
4754
-     *                                            the ticket model, you should provide the string 'Event.Datetime',
4755
-     *                                            indicating you need to join the venue model to the event model,
4756
-     *                                            to the datetime model, in order to find its relation to the ticket
4757
-     *                                            model. This string is used to deduce the prefix that gets added onto
4758
-     *                                            the models' tables qualified columns
4759
-     * @param bool   $return_string               if true, will return a string with qualified column names separated
4760
-     *                                            by ', ' if false, will simply return a numerically indexed array of
4761
-     *                                            qualified column names
4762
-     * @return array|string
4763
-     */
4764
-    public function get_qualified_columns_for_all_fields($model_relation_chain = '', $return_string = true)
4765
-    {
4766
-        $table_prefix      = str_replace('.', '__', $model_relation_chain) . (empty($model_relation_chain) ? '' : '__');
4767
-        $qualified_columns = [];
4768
-        foreach ($this->field_settings() as $field_name => $field) {
4769
-            $qualified_columns[] = $table_prefix . $field->get_qualified_column();
4770
-        }
4771
-        return $return_string ? implode(', ', $qualified_columns) : $qualified_columns;
4772
-    }
4773
-
4774
-
4775
-    /**
4776
-     * constructs the select use on special limit joins
4777
-     * NOTE: for now this has only been tested and will work when the  table alias is for the PRIMARY table. Although
4778
-     * its setup so the select query will be setup on and just doing the special select join off of the primary table
4779
-     * (as that is typically where the limits would be set).
4780
-     *
4781
-     * @param string       $table_alias The table the select is being built for
4782
-     * @param mixed|string $limit       The limit for this select
4783
-     * @return string                The final select join element for the query.
4784
-     * @throws EE_Error
4785
-     * @throws EE_Error
4786
-     */
4787
-    public function _construct_limit_join_select($table_alias, $limit)
4788
-    {
4789
-        $SQL = '';
4790
-        foreach ($this->_tables as $table_obj) {
4791
-            if ($table_obj instanceof EE_Primary_Table) {
4792
-                $SQL .= $table_alias === $table_obj->get_table_alias()
4793
-                    ? $table_obj->get_select_join_limit($limit)
4794
-                    : SP . $table_obj->get_table_name() . " AS " . $table_obj->get_table_alias() . SP;
4795
-            } elseif ($table_obj instanceof EE_Secondary_Table) {
4796
-                $SQL .= $table_alias === $table_obj->get_table_alias()
4797
-                    ? $table_obj->get_select_join_limit_join($limit)
4798
-                    : SP . $table_obj->get_join_sql($table_alias) . SP;
4799
-            }
4800
-        }
4801
-        return $SQL;
4802
-    }
4803
-
4804
-
4805
-    /**
4806
-     * Constructs the internal join if there are multiple tables, or simply the table's name and alias
4807
-     * Eg "wp_post AS Event" or "wp_post AS Event INNER JOIN wp_postmeta Event_Meta ON Event.ID = Event_Meta.post_id"
4808
-     *
4809
-     * @return string SQL
4810
-     * @throws EE_Error
4811
-     */
4812
-    public function _construct_internal_join()
4813
-    {
4814
-        $SQL = $this->_get_main_table()->get_table_sql();
4815
-        $SQL .= $this->_construct_internal_join_to_table_with_alias($this->_get_main_table()->get_table_alias());
4816
-        return $SQL;
4817
-    }
4818
-
4819
-
4820
-    /**
4821
-     * Constructs the SQL for joining all the tables on this model.
4822
-     * Normally $alias should be the primary table's alias, but in cases where
4823
-     * we have already joined to a secondary table (eg, the secondary table has a foreign key and is joined before the
4824
-     * primary table) then we should provide that secondary table's alias. Eg, with $alias being the primary table's
4825
-     * alias, this will construct SQL like:
4826
-     * " INNER JOIN wp_esp_secondary_table AS Secondary_Table ON Primary_Table.pk = Secondary_Table.fk".
4827
-     * With $alias being a secondary table's alias, this will construct SQL like:
4828
-     * " INNER JOIN wp_esp_primary_table AS Primary_Table ON Primary_Table.pk = Secondary_Table.fk".
4829
-     *
4830
-     * @param string $alias_prefixed table alias to join to (this table should already be in the FROM SQL clause)
4831
-     * @return string
4832
-     * @throws EE_Error
4833
-     * @throws EE_Error
4834
-     */
4835
-    public function _construct_internal_join_to_table_with_alias($alias_prefixed)
4836
-    {
4837
-        $SQL               = '';
4838
-        $alias_sans_prefix = EE_Model_Parser::remove_table_alias_model_relation_chain_prefix($alias_prefixed);
4839
-        foreach ($this->_tables as $table_obj) {
4840
-            if ($table_obj instanceof EE_Secondary_Table) {// table is secondary table
4841
-                if ($alias_sans_prefix === $table_obj->get_table_alias()) {
4842
-                    // so we're joining to this table, meaning the table is already in
4843
-                    // the FROM statement, BUT the primary table isn't. So we want
4844
-                    // to add the inverse join sql
4845
-                    $SQL .= $table_obj->get_inverse_join_sql($alias_prefixed);
4846
-                } else {
4847
-                    // just add a regular JOIN to this table from the primary table
4848
-                    $SQL .= $table_obj->get_join_sql($alias_prefixed);
4849
-                }
4850
-            }//if it's a primary table, dont add any SQL. it should already be in the FROM statement
4851
-        }
4852
-        return $SQL;
4853
-    }
4854
-
4855
-
4856
-    /**
4857
-     * Gets an array for storing all the data types on the next-to-be-executed-query.
4858
-     * This should be a growing array of keys being table-columns (eg 'EVT_ID' and 'Event.EVT_ID'), and values being
4859
-     * their data type (eg, '%s', '%d', etc)
4860
-     *
4861
-     * @return array
4862
-     */
4863
-    public function _get_data_types()
4864
-    {
4865
-        $data_types = [];
4866
-        foreach ($this->field_settings() as $field_obj) {
4867
-            // $data_types[$field_obj->get_table_column()] = $field_obj->get_wpdb_data_type();
4868
-            /** @var $field_obj EE_Model_Field_Base */
4869
-            $data_types[ $field_obj->get_qualified_column() ] = $field_obj->get_wpdb_data_type();
4870
-        }
4871
-        return $data_types;
4872
-    }
4873
-
4874
-
4875
-    /**
4876
-     * Gets the model object given the relation's name / model's name (eg, 'Event', 'Registration',etc. Always singular)
4877
-     *
4878
-     * @param string $model_name
4879
-     * @return EEM_Base
4880
-     * @throws EE_Error
4881
-     */
4882
-    public function get_related_model_obj($model_name)
4883
-    {
4884
-        $model_classname = "EEM_" . $model_name;
4885
-        if (! class_exists($model_classname)) {
4886
-            throw new EE_Error(
4887
-                sprintf(
4888
-                    esc_html__(
4889
-                        "You specified a related model named %s in your query. No such model exists, if it did, it would have the classname %s",
4890
-                        'event_espresso'
4891
-                    ),
4892
-                    $model_name,
4893
-                    $model_classname
4894
-                )
4895
-            );
4896
-        }
4897
-        return call_user_func($model_classname . "::instance");
4898
-    }
4899
-
4900
-
4901
-    /**
4902
-     * Returns the array of EE_ModelRelations for this model.
4903
-     *
4904
-     * @return EE_Model_Relation_Base[]
4905
-     */
4906
-    public function relation_settings()
4907
-    {
4908
-        return $this->_model_relations;
4909
-    }
4910
-
4911
-
4912
-    /**
4913
-     * Gets all related models that this model BELONGS TO. Handy to know sometimes
4914
-     * because without THOSE models, this model probably doesn't have much purpose.
4915
-     * (Eg, without an event, datetimes have little purpose.)
4916
-     *
4917
-     * @return EE_Belongs_To_Relation[]
4918
-     */
4919
-    public function belongs_to_relations()
4920
-    {
4921
-        $belongs_to_relations = [];
4922
-        foreach ($this->relation_settings() as $model_name => $relation_obj) {
4923
-            if ($relation_obj instanceof EE_Belongs_To_Relation) {
4924
-                $belongs_to_relations[ $model_name ] = $relation_obj;
4925
-            }
4926
-        }
4927
-        return $belongs_to_relations;
4928
-    }
4929
-
4930
-
4931
-    /**
4932
-     * Returns the specified EE_Model_Relation, or throws an exception
4933
-     *
4934
-     * @param string $relation_name name of relation, key in $this->_relatedModels
4935
-     * @return EE_Model_Relation_Base
4936
-     * @throws EE_Error
4937
-     */
4938
-    public function related_settings_for($relation_name)
4939
-    {
4940
-        $relatedModels = $this->relation_settings();
4941
-        if (! array_key_exists($relation_name, $relatedModels)) {
4942
-            throw new EE_Error(
4943
-                sprintf(
4944
-                    esc_html__(
4945
-                        'Cannot get %s related to %s. There is no model relation of that type. There is, however, %s...',
4946
-                        'event_espresso'
4947
-                    ),
4948
-                    $relation_name,
4949
-                    $this->_get_class_name(),
4950
-                    implode(', ', array_keys($relatedModels))
4951
-                )
4952
-            );
4953
-        }
4954
-        return $relatedModels[ $relation_name ];
4955
-    }
4956
-
4957
-
4958
-    /**
4959
-     * A convenience method for getting a specific field's settings, instead of getting all field settings for all
4960
-     * fields
4961
-     *
4962
-     * @param string  $fieldName
4963
-     * @param boolean $include_db_only_fields
4964
-     * @return EE_Model_Field_Base
4965
-     * @throws EE_Error
4966
-     */
4967
-    public function field_settings_for($fieldName, $include_db_only_fields = true)
4968
-    {
4969
-        $fieldSettings = $this->field_settings($include_db_only_fields);
4970
-        if (! array_key_exists($fieldName, $fieldSettings)) {
4971
-            throw new EE_Error(
4972
-                sprintf(
4973
-                    esc_html__("There is no field/column '%s' on '%s'", 'event_espresso'),
4974
-                    $fieldName,
4975
-                    get_class($this)
4976
-                )
4977
-            );
4978
-        }
4979
-        return $fieldSettings[ $fieldName ];
4980
-    }
4981
-
4982
-
4983
-    /**
4984
-     * Checks if this field exists on this model
4985
-     *
4986
-     * @param string $fieldName a key in the model's _field_settings array
4987
-     * @return boolean
4988
-     */
4989
-    public function has_field($fieldName)
4990
-    {
4991
-        $fieldSettings = $this->field_settings(true);
4992
-        if (isset($fieldSettings[ $fieldName ])) {
4993
-            return true;
4994
-        }
4995
-        return false;
4996
-    }
4997
-
4998
-
4999
-    /**
5000
-     * Returns whether or not this model has a relation to the specified model
5001
-     *
5002
-     * @param string $relation_name possibly one of the keys in the relation_settings array
5003
-     * @return boolean
5004
-     */
5005
-    public function has_relation($relation_name)
5006
-    {
5007
-        $relations = $this->relation_settings();
5008
-        if (isset($relations[ $relation_name ])) {
5009
-            return true;
5010
-        }
5011
-        return false;
5012
-    }
5013
-
5014
-
5015
-    /**
5016
-     * gets the field object of type 'primary_key' from the fieldsSettings attribute.
5017
-     * Eg, on EE_Answer that would be ANS_ID field object
5018
-     *
5019
-     * @param $field_obj
5020
-     * @return boolean
5021
-     */
5022
-    public function is_primary_key_field($field_obj)
5023
-    {
5024
-        return $field_obj instanceof EE_Primary_Key_Field_Base ? true : false;
5025
-    }
5026
-
5027
-
5028
-    /**
5029
-     * gets the field object of type 'primary_key' from the fieldsSettings attribute.
5030
-     * Eg, on EE_Answer that would be ANS_ID field object
5031
-     *
5032
-     * @return EE_Primary_Key_Field_Base
5033
-     * @throws EE_Error
5034
-     */
5035
-    public function get_primary_key_field()
5036
-    {
5037
-        if ($this->_primary_key_field === null) {
5038
-            foreach ($this->field_settings(true) as $field_obj) {
5039
-                if ($this->is_primary_key_field($field_obj)) {
5040
-                    $this->_primary_key_field = $field_obj;
5041
-                    break;
5042
-                }
5043
-            }
5044
-            if (! $this->_primary_key_field instanceof EE_Primary_Key_Field_Base) {
5045
-                throw new EE_Error(
5046
-                    sprintf(
5047
-                        esc_html__("There is no Primary Key defined on model %s", 'event_espresso'),
5048
-                        get_class($this)
5049
-                    )
5050
-                );
5051
-            }
5052
-        }
5053
-        return $this->_primary_key_field;
5054
-    }
5055
-
5056
-
5057
-    /**
5058
-     * Returns whether or not not there is a primary key on this model.
5059
-     * Internally does some caching.
5060
-     *
5061
-     * @return boolean
5062
-     */
5063
-    public function has_primary_key_field()
5064
-    {
5065
-        if ($this->_has_primary_key_field === null) {
5066
-            try {
5067
-                $this->get_primary_key_field();
5068
-                $this->_has_primary_key_field = true;
5069
-            } catch (EE_Error $e) {
5070
-                $this->_has_primary_key_field = false;
5071
-            }
5072
-        }
5073
-        return $this->_has_primary_key_field;
5074
-    }
5075
-
5076
-
5077
-    /**
5078
-     * Finds the first field of type $field_class_name.
5079
-     *
5080
-     * @param string $field_class_name class name of field that you want to find. Eg, EE_Datetime_Field,
5081
-     *                                 EE_Foreign_Key_Field, etc
5082
-     * @return EE_Model_Field_Base or null if none is found
5083
-     */
5084
-    public function get_a_field_of_type($field_class_name)
5085
-    {
5086
-        foreach ($this->field_settings() as $field) {
5087
-            if ($field instanceof $field_class_name) {
5088
-                return $field;
5089
-            }
5090
-        }
5091
-        return null;
5092
-    }
5093
-
5094
-
5095
-    /**
5096
-     * Gets a foreign key field pointing to model.
5097
-     *
5098
-     * @param string $model_name eg Event, Registration, not EEM_Event
5099
-     * @return EE_Foreign_Key_Field_Base
5100
-     * @throws EE_Error
5101
-     */
5102
-    public function get_foreign_key_to($model_name)
5103
-    {
5104
-        if (! isset($this->_cache_foreign_key_to_fields[ $model_name ])) {
5105
-            foreach ($this->field_settings() as $field) {
5106
-                if (
5107
-                    $field instanceof EE_Foreign_Key_Field_Base
5108
-                    && in_array($model_name, $field->get_model_names_pointed_to())
5109
-                ) {
5110
-                    $this->_cache_foreign_key_to_fields[ $model_name ] = $field;
5111
-                    break;
5112
-                }
5113
-            }
5114
-            if (! isset($this->_cache_foreign_key_to_fields[ $model_name ])) {
5115
-                throw new EE_Error(
5116
-                    sprintf(
5117
-                        esc_html__(
5118
-                            "There is no foreign key field pointing to model %s on model %s",
5119
-                            'event_espresso'
5120
-                        ),
5121
-                        $model_name,
5122
-                        get_class($this)
5123
-                    )
5124
-                );
5125
-            }
5126
-        }
5127
-        return $this->_cache_foreign_key_to_fields[ $model_name ];
5128
-    }
5129
-
5130
-
5131
-    /**
5132
-     * Gets the table name (including $wpdb->prefix) for the table alias
5133
-     *
5134
-     * @param string $table_alias eg Event, Event_Meta, Registration, Transaction, but maybe
5135
-     *                            a table alias with a model chain prefix, like 'Venue__Event_Venue___Event_Meta'.
5136
-     *                            Either one works
5137
-     * @return string
5138
-     */
5139
-    public function get_table_for_alias($table_alias)
5140
-    {
5141
-        $table_alias_sans_model_relation_chain_prefix =
5142
-            EE_Model_Parser::remove_table_alias_model_relation_chain_prefix($table_alias);
5143
-        return $this->_tables[ $table_alias_sans_model_relation_chain_prefix ]->get_table_name();
5144
-    }
5145
-
5146
-
5147
-    /**
5148
-     * Returns a flat array of all field son this model, instead of organizing them
5149
-     * by table_alias as they are in the constructor.
5150
-     *
5151
-     * @param bool $include_db_only_fields flag indicating whether or not to include the db-only fields
5152
-     * @return EE_Model_Field_Base[] where the keys are the field's name
5153
-     */
5154
-    public function field_settings($include_db_only_fields = false)
5155
-    {
5156
-        if ($include_db_only_fields) {
5157
-            if ($this->_cached_fields === null) {
5158
-                $this->_cached_fields = [];
5159
-                foreach ($this->_fields as $fields_corresponding_to_table) {
5160
-                    foreach ($fields_corresponding_to_table as $field_name => $field_obj) {
5161
-                        $this->_cached_fields[ $field_name ] = $field_obj;
5162
-                    }
5163
-                }
5164
-            }
5165
-            return $this->_cached_fields;
5166
-        }
5167
-        if ($this->_cached_fields_non_db_only === null) {
5168
-            $this->_cached_fields_non_db_only = [];
5169
-            foreach ($this->_fields as $fields_corresponding_to_table) {
5170
-                foreach ($fields_corresponding_to_table as $field_name => $field_obj) {
5171
-                    /** @var $field_obj EE_Model_Field_Base */
5172
-                    if (! $field_obj->is_db_only_field()) {
5173
-                        $this->_cached_fields_non_db_only[ $field_name ] = $field_obj;
5174
-                    }
5175
-                }
5176
-            }
5177
-        }
5178
-        return $this->_cached_fields_non_db_only;
5179
-    }
5180
-
5181
-
5182
-    /**
5183
-     *        cycle though array of attendees and create objects out of each item
5184
-     *
5185
-     * @access        private
5186
-     * @param array $rows        of results of $wpdb->get_results($query,ARRAY_A)
5187
-     * @return EE_Base_Class[] array keys are primary keys (if there is a primary key on the model. if not,
5188
-     *                           numerically indexed)
5189
-     * @throws EE_Error
5190
-     * @throws ReflectionException
5191
-     */
5192
-    protected function _create_objects($rows = [])
5193
-    {
5194
-        $array_of_objects = [];
5195
-        if (empty($rows)) {
5196
-            return [];
5197
-        }
5198
-        $count_if_model_has_no_primary_key = 0;
5199
-        $has_primary_key                   = $this->has_primary_key_field();
5200
-        $primary_key_field                 = $has_primary_key ? $this->get_primary_key_field() : null;
5201
-        foreach ((array) $rows as $row) {
5202
-            if (empty($row)) {
5203
-                // wp did its weird thing where it returns an array like array(0=>null), which is totally not helpful...
5204
-                return [];
5205
-            }
5206
-            // check if we've already set this object in the results array,
5207
-            // in which case there's no need to process it further (again)
5208
-            if ($has_primary_key) {
5209
-                $table_pk_value = $this->_get_column_value_with_table_alias_or_not(
5210
-                    $row,
5211
-                    $primary_key_field->get_qualified_column(),
5212
-                    $primary_key_field->get_table_column()
5213
-                );
5214
-                if ($table_pk_value && isset($array_of_objects[ $table_pk_value ])) {
5215
-                    continue;
5216
-                }
5217
-            }
5218
-            $classInstance = $this->instantiate_class_from_array_or_object($row);
5219
-            if (! $classInstance) {
5220
-                throw new EE_Error(
5221
-                    sprintf(
5222
-                        esc_html__('Could not create instance of class %s from row %s', 'event_espresso'),
5223
-                        $this->get_this_model_name(),
5224
-                        http_build_query($row)
5225
-                    )
5226
-                );
5227
-            }
5228
-            // set the timezone on the instantiated objects
5229
-            $classInstance->set_timezone($this->_timezone);
5230
-            // make sure if there is any timezone setting present that we set the timezone for the object
5231
-            $key                      = $has_primary_key ? $classInstance->ID() : $count_if_model_has_no_primary_key++;
5232
-            $array_of_objects[ $key ] = $classInstance;
5233
-            // also, for all the relations of type BelongsTo, see if we can cache
5234
-            // those related models
5235
-            // (we could do this for other relations too, but if there are conditions
5236
-            // that filtered out some fo the results, then we'd be caching an incomplete set
5237
-            // so it requires a little more thought than just caching them immediately...)
5238
-            foreach ($this->_model_relations as $modelName => $relation_obj) {
5239
-                if ($relation_obj instanceof EE_Belongs_To_Relation) {
5240
-                    // check if this model's INFO is present. If so, cache it on the model
5241
-                    $other_model           = $relation_obj->get_other_model();
5242
-                    $other_model_obj_maybe = $other_model->instantiate_class_from_array_or_object($row);
5243
-                    // if we managed to make a model object from the results, cache it on the main model object
5244
-                    if ($other_model_obj_maybe) {
5245
-                        // set timezone on these other model objects if they are present
5246
-                        $other_model_obj_maybe->set_timezone($this->_timezone);
5247
-                        $classInstance->cache($modelName, $other_model_obj_maybe);
5248
-                    }
5249
-                }
5250
-            }
5251
-            // also, if this was a custom select query, let's see if there are any results for the custom select fields
5252
-            // and add them to the object as well.  We'll convert according to the set data_type if there's any set for
5253
-            // the field in the CustomSelects object
5254
-            if ($this->_custom_selections instanceof CustomSelects) {
5255
-                $classInstance->setCustomSelectsValues(
5256
-                    $this->getValuesForCustomSelectAliasesFromResults($row)
5257
-                );
5258
-            }
5259
-        }
5260
-        return $array_of_objects;
5261
-    }
5262
-
5263
-
5264
-    /**
5265
-     * This will parse a given row of results from the db and see if any keys in the results match an alias within the
5266
-     * current CustomSelects object. This will be used to build an array of values indexed by those keys.
5267
-     *
5268
-     * @param array $db_results_row
5269
-     * @return array
5270
-     */
5271
-    protected function getValuesForCustomSelectAliasesFromResults(array $db_results_row)
5272
-    {
5273
-        $results = [];
5274
-        if ($this->_custom_selections instanceof CustomSelects) {
5275
-            foreach ($this->_custom_selections->columnAliases() as $alias) {
5276
-                if (isset($db_results_row[ $alias ])) {
5277
-                    $results[ $alias ] = $this->convertValueToDataType(
5278
-                        $db_results_row[ $alias ],
5279
-                        $this->_custom_selections->getDataTypeForAlias($alias)
5280
-                    );
5281
-                }
5282
-            }
5283
-        }
5284
-        return $results;
5285
-    }
5286
-
5287
-
5288
-    /**
5289
-     * This will set the value for the given alias
5290
-     *
5291
-     * @param string $value
5292
-     * @param string $datatype (one of %d, %s, %f)
5293
-     * @return int|string|float (int for %d, string for %s, float for %f)
5294
-     */
5295
-    protected function convertValueToDataType($value, $datatype)
5296
-    {
5297
-        switch ($datatype) {
5298
-            case '%f':
5299
-                return (float) $value;
5300
-            case '%d':
5301
-                return (int) $value;
5302
-            default:
5303
-                return (string) $value;
5304
-        }
5305
-    }
5306
-
5307
-
5308
-    /**
5309
-     * The purpose of this method is to allow us to create a model object that is not in the db that holds default
5310
-     * values. A typical example of where this is used is when creating a new item and the initial load of a form.  We
5311
-     * dont' necessarily want to test for if the object is present but just assume it is BUT load the defaults from the
5312
-     * object (as set in the model_field!).
5313
-     *
5314
-     * @return EE_Base_Class single EE_Base_Class object with default values for the properties.
5315
-     * @throws EE_Error
5316
-     * @throws ReflectionException
5317
-     */
5318
-    public function create_default_object()
5319
-    {
5320
-        $this_model_fields_and_values = [];
5321
-        // setup the row using default values;
5322
-        foreach ($this->field_settings() as $field_name => $field_obj) {
5323
-            $this_model_fields_and_values[ $field_name ] = $field_obj->get_default_value();
5324
-        }
5325
-        $className     = $this->_get_class_name();
5326
-        return EE_Registry::instance()->load_class($className, [$this_model_fields_and_values], false, false);
5327
-    }
5328
-
5329
-
5330
-    /**
5331
-     * @param mixed $cols_n_values either an array of where each key is the name of a field, and the value is its value
5332
-     *                             or an stdClass where each property is the name of a column,
5333
-     * @return EE_Base_Class
5334
-     * @throws EE_Error
5335
-     * @throws ReflectionException
5336
-     */
5337
-    public function instantiate_class_from_array_or_object($cols_n_values)
5338
-    {
5339
-        if (! is_array($cols_n_values) && is_object($cols_n_values)) {
5340
-            $cols_n_values = get_object_vars($cols_n_values);
5341
-        }
5342
-        $primary_key = null;
5343
-        // make sure the array only has keys that are fields/columns on this model
5344
-        $this_model_fields_n_values = $this->_deduce_fields_n_values_from_cols_n_values($cols_n_values);
5345
-        if ($this->has_primary_key_field() && isset($this_model_fields_n_values[ $this->primary_key_name() ])) {
5346
-            $primary_key = $this_model_fields_n_values[ $this->primary_key_name() ];
5347
-        }
5348
-        $className = $this->_get_class_name();
5349
-        // check we actually found results that we can use to build our model object
5350
-        // if not, return null
5351
-        if ($this->has_primary_key_field()) {
5352
-            if (empty($this_model_fields_n_values[ $this->primary_key_name() ])) {
5353
-                return null;
5354
-            }
5355
-        } elseif ($this->unique_indexes()) {
5356
-            $first_column = reset($this_model_fields_n_values);
5357
-            if (empty($first_column)) {
5358
-                return null;
5359
-            }
5360
-        }
5361
-        // if there is no primary key or the object doesn't already exist in the entity map, then create a new instance
5362
-        if ($primary_key) {
5363
-            $classInstance = $this->get_from_entity_map($primary_key);
5364
-            if (! $classInstance) {
5365
-                $classInstance = EE_Registry::instance()
5366
-                                            ->load_class(
5367
-                                                $className,
5368
-                                                [$this_model_fields_n_values, $this->_timezone],
5369
-                                                true,
5370
-                                                false
5371
-                                            );
5372
-                // add this new object to the entity map
5373
-                $classInstance = $this->add_to_entity_map($classInstance);
5374
-            }
5375
-        } else {
5376
-            $classInstance = EE_Registry::instance()
5377
-                                        ->load_class(
5378
-                                            $className,
5379
-                                            [$this_model_fields_n_values, $this->_timezone],
5380
-                                            true,
5381
-                                            false
5382
-                                        );
5383
-        }
5384
-        return $classInstance;
5385
-    }
5386
-
5387
-
5388
-    /**
5389
-     * Gets the model object from the  entity map if it exists
5390
-     *
5391
-     * @param int|string $id the ID of the model object
5392
-     * @return EE_Base_Class
5393
-     */
5394
-    public function get_from_entity_map($id)
5395
-    {
5396
-        return isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])
5397
-            ? $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ] : null;
5398
-    }
5399
-
5400
-
5401
-    /**
5402
-     * add_to_entity_map
5403
-     * Adds the object to the model's entity mappings
5404
-     *        Effectively tells the models "Hey, this model object is the most up-to-date representation of the data,
5405
-     *        and for the remainder of the request, it's even more up-to-date than what's in the database.
5406
-     *        So, if the database doesn't agree with what's in the entity mapper, ignore the database"
5407
-     *        If the database gets updated directly and you want the entity mapper to reflect that change,
5408
-     *        then this method should be called immediately after the update query
5409
-     * Note: The map is indexed by whatever the current blog id is set (via EEM_Base::$_model_query_blog_id).  This is
5410
-     * so on multisite, the entity map is specific to the query being done for a specific site.
5411
-     *
5412
-     * @param EE_Base_Class $object
5413
-     * @return EE_Base_Class
5414
-     * @throws EE_Error
5415
-     * @throws ReflectionException
5416
-     */
5417
-    public function add_to_entity_map(EE_Base_Class $object)
5418
-    {
5419
-        $className = $this->_get_class_name();
5420
-        if (! $object instanceof $className) {
5421
-            throw new EE_Error(
5422
-                sprintf(
5423
-                    esc_html__("You tried adding a %s to a mapping of %ss", "event_espresso"),
5424
-                    is_object($object) ? get_class($object) : $object,
5425
-                    $className
5426
-                )
5427
-            );
5428
-        }
5429
-        /** @var $object EE_Base_Class */
5430
-        if (! $object->ID()) {
5431
-            throw new EE_Error(
5432
-                sprintf(
5433
-                    esc_html__(
5434
-                        "You tried storing a model object with NO ID in the %s entity mapper.",
5435
-                        "event_espresso"
5436
-                    ),
5437
-                    get_class($this)
5438
-                )
5439
-            );
5440
-        }
5441
-        // double check it's not already there
5442
-        $classInstance = $this->get_from_entity_map($object->ID());
5443
-        if ($classInstance) {
5444
-            return $classInstance;
5445
-        }
5446
-        $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $object->ID() ] = $object;
5447
-        return $object;
5448
-    }
5449
-
5450
-
5451
-    /**
5452
-     * if a valid identifier is provided, then that entity is unset from the entity map,
5453
-     * if no identifier is provided, then the entire entity map is emptied
5454
-     *
5455
-     * @param int|string $id the ID of the model object
5456
-     * @return boolean
5457
-     */
5458
-    public function clear_entity_map($id = null)
5459
-    {
5460
-        if (empty($id)) {
5461
-            $this->_entity_map[ EEM_Base::$_model_query_blog_id ] = [];
5462
-            return true;
5463
-        }
5464
-        if (isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])) {
5465
-            unset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ]);
5466
-            return true;
5467
-        }
5468
-        return false;
5469
-    }
5470
-
5471
-
5472
-    /**
5473
-     * Public wrapper for _deduce_fields_n_values_from_cols_n_values.
5474
-     * Given an array where keys are column (or column alias) names and values,
5475
-     * returns an array of their corresponding field names and database values
5476
-     *
5477
-     * @param array $cols_n_values
5478
-     * @return array
5479
-     * @throws EE_Error
5480
-     * @throws ReflectionException
5481
-     */
5482
-    public function deduce_fields_n_values_from_cols_n_values(array $cols_n_values): array
5483
-    {
5484
-        return $this->_deduce_fields_n_values_from_cols_n_values($cols_n_values);
5485
-    }
5486
-
5487
-
5488
-    /**
5489
-     * _deduce_fields_n_values_from_cols_n_values
5490
-     * Given an array where keys are column (or column alias) names and values,
5491
-     * returns an array of their corresponding field names and database values
5492
-     *
5493
-     * @param array|stdClass $cols_n_values
5494
-     * @return array
5495
-     * @throws EE_Error
5496
-     * @throws ReflectionException
5497
-     */
5498
-    protected function _deduce_fields_n_values_from_cols_n_values($cols_n_values): array
5499
-    {
5500
-        if ($cols_n_values instanceof stdClass) {
5501
-            $cols_n_values = get_object_vars($cols_n_values);
5502
-        }
5503
-        $this_model_fields_n_values = [];
5504
-        foreach ($this->get_tables() as $table_alias => $table_obj) {
5505
-            $table_pk_value = $this->_get_column_value_with_table_alias_or_not(
5506
-                $cols_n_values,
5507
-                $table_obj->get_fully_qualified_pk_column(),
5508
-                $table_obj->get_pk_column()
5509
-            );
5510
-            // there is a primary key on this table and its not set. Use defaults for all its columns
5511
-            if ($table_pk_value === null && $table_obj->get_pk_column()) {
5512
-                foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5513
-                    if (! $field_obj->is_db_only_field()) {
5514
-                        // prepare field as if its coming from db
5515
-                        $prepared_value                            =
5516
-                            $field_obj->prepare_for_set($field_obj->get_default_value());
5517
-                        $this_model_fields_n_values[ $field_name ] = $field_obj->prepare_for_use_in_db($prepared_value);
5518
-                    }
5519
-                }
5520
-            } else {
5521
-                // the table's rows existed. Use their values
5522
-                foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5523
-                    if (! $field_obj->is_db_only_field()) {
5524
-                        $this_model_fields_n_values[ $field_name ] = $this->_get_column_value_with_table_alias_or_not(
5525
-                            $cols_n_values,
5526
-                            $field_obj->get_qualified_column(),
5527
-                            $field_obj->get_table_column()
5528
-                        );
5529
-                    }
5530
-                }
5531
-            }
5532
-        }
5533
-        return $this_model_fields_n_values;
5534
-    }
5535
-
5536
-
5537
-    /**
5538
-     * @param $cols_n_values
5539
-     * @param $qualified_column
5540
-     * @param $regular_column
5541
-     * @return null
5542
-     * @throws EE_Error
5543
-     * @throws ReflectionException
5544
-     */
5545
-    protected function _get_column_value_with_table_alias_or_not($cols_n_values, $qualified_column, $regular_column)
5546
-    {
5547
-        $value = null;
5548
-        // ask the field what it think it's table_name.column_name should be, and call it the "qualified column"
5549
-        // does the field on the model relate to this column retrieved from the db?
5550
-        // or is it a db-only field? (not relating to the model)
5551
-        if (isset($cols_n_values[ $qualified_column ])) {
5552
-            $value = $cols_n_values[ $qualified_column ];
5553
-        } elseif (isset($cols_n_values[ $regular_column ])) {
5554
-            $value = $cols_n_values[ $regular_column ];
5555
-        } elseif (! empty($this->foreign_key_aliases)) {
5556
-            // no PK?  ok check if there is a foreign key alias set for this table
5557
-            // then check if that alias exists in the incoming data
5558
-            // AND that the actual PK the $FK_alias represents matches the $qualified_column (full PK)
5559
-            foreach ($this->foreign_key_aliases as $FK_alias => $PK_column) {
5560
-                if ($PK_column === $qualified_column && isset($cols_n_values[ $FK_alias ])) {
5561
-                    $value = $cols_n_values[ $FK_alias ];
5562
-                    [$pk_class] = explode('.', $PK_column);
5563
-                    $pk_model_name = "EEM_{$pk_class}";
5564
-                    /** @var EEM_Base $pk_model */
5565
-                    $pk_model = EE_Registry::instance()->load_model($pk_model_name);
5566
-                    if ($pk_model instanceof EEM_Base) {
5567
-                        // make sure object is pulled from db and added to entity map
5568
-                        $pk_model->get_one_by_ID($value);
5569
-                    }
5570
-                    break;
5571
-                }
5572
-            }
5573
-        }
5574
-        return $value;
5575
-    }
5576
-
5577
-
5578
-    /**
5579
-     * refresh_entity_map_from_db
5580
-     * Makes sure the model object in the entity map at $id assumes the values
5581
-     * of the database (opposite of EE_base_Class::save())
5582
-     *
5583
-     * @param int|string $id
5584
-     * @return EE_Base_Class|EE_Soft_Delete_Base_Class|mixed|null
5585
-     * @throws EE_Error
5586
-     * @throws ReflectionException
5587
-     */
5588
-    public function refresh_entity_map_from_db($id)
5589
-    {
5590
-        $obj_in_map = $this->get_from_entity_map($id);
5591
-        if ($obj_in_map) {
5592
-            $wpdb_results = $this->_get_all_wpdb_results(
5593
-                [[$this->get_primary_key_field()->get_name() => $id], 'limit' => 1]
5594
-            );
5595
-            if ($wpdb_results && is_array($wpdb_results)) {
5596
-                $one_row = reset($wpdb_results);
5597
-                foreach ($this->_deduce_fields_n_values_from_cols_n_values($one_row) as $field_name => $db_value) {
5598
-                    $obj_in_map->set_from_db($field_name, $db_value);
5599
-                }
5600
-                // clear the cache of related model objects
5601
-                foreach ($this->relation_settings() as $relation_name => $relation_obj) {
5602
-                    $obj_in_map->clear_cache($relation_name, null, true);
5603
-                }
5604
-            }
5605
-            $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ] = $obj_in_map;
5606
-            return $obj_in_map;
5607
-        }
5608
-        return $this->get_one_by_ID($id);
5609
-    }
5610
-
5611
-
5612
-    /**
5613
-     * refresh_entity_map_with
5614
-     * Leaves the entry in the entity map alone, but updates it to match the provided
5615
-     * $replacing_model_obj (which we assume to be its equivalent but somehow NOT in the entity map).
5616
-     * This is useful if you have a model object you want to make authoritative over what's in the entity map currently.
5617
-     * Note: The old $replacing_model_obj should now be destroyed as it's now un-authoritative
5618
-     *
5619
-     * @param int|string    $id
5620
-     * @param EE_Base_Class $replacing_model_obj
5621
-     * @return EE_Base_Class
5622
-     * @throws EE_Error
5623
-     * @throws ReflectionException
5624
-     */
5625
-    public function refresh_entity_map_with($id, $replacing_model_obj)
5626
-    {
5627
-        $obj_in_map = $this->get_from_entity_map($id);
5628
-        if ($obj_in_map) {
5629
-            if ($replacing_model_obj instanceof EE_Base_Class) {
5630
-                foreach ($replacing_model_obj->model_field_array() as $field_name => $value) {
5631
-                    $obj_in_map->set($field_name, $value);
5632
-                }
5633
-                // make the model object in the entity map's cache match the $replacing_model_obj
5634
-                foreach ($this->relation_settings() as $relation_name => $relation_obj) {
5635
-                    $obj_in_map->clear_cache($relation_name, null, true);
5636
-                    foreach ($replacing_model_obj->get_all_from_cache($relation_name) as $cache_id => $cached_obj) {
5637
-                        $obj_in_map->cache($relation_name, $cached_obj, $cache_id);
5638
-                    }
5639
-                }
5640
-            }
5641
-            return $obj_in_map;
5642
-        }
5643
-        $this->add_to_entity_map($replacing_model_obj);
5644
-        return $replacing_model_obj;
5645
-    }
5646
-
5647
-
5648
-    /**
5649
-     * Gets the EE class that corresponds to this model. Eg, for EEM_Answer that
5650
-     * would be EE_Answer.To import that class, you'd just add ".class.php" to the name, like so
5651
-     * require_once($this->_getClassName().".class.php");
5652
-     *
5653
-     * @return string
5654
-     */
5655
-    private function _get_class_name()
5656
-    {
5657
-        return "EE_" . $this->get_this_model_name();
5658
-    }
5659
-
5660
-
5661
-    /**
5662
-     * Get the name of the items this model represents, for the quantity specified. Eg,
5663
-     * if $quantity==1, on EEM_Event, it would 'Event' (internationalized), otherwise
5664
-     * it would be 'Events'.
5665
-     *
5666
-     * @param int|float|null $quantity
5667
-     * @return string
5668
-     */
5669
-    public function item_name($quantity = 1): string
5670
-    {
5671
-        $quantity = floor($quantity);
5672
-        return apply_filters(
5673
-            'FHEE__EEM_Base__item_name__plural_or_singular',
5674
-            $quantity > 1 ? $this->plural_item : $this->singular_item,
5675
-            $quantity,
5676
-            $this->plural_item,
5677
-            $this->singular_item
5678
-        );
5679
-    }
5680
-
5681
-
5682
-    /**
5683
-     * Very handy general function to allow for plugins to extend any child of EE_TempBase.
5684
-     * If a method is called on a child of EE_TempBase that doesn't exist, this function is called
5685
-     * (http://www.garfieldtech.com/blog/php-magic-call) and passed the method's name and arguments. Instead of
5686
-     * requiring a plugin to extend the EE_TempBase (which works fine is there's only 1 plugin, but when will that
5687
-     * happen?) they can add a hook onto 'filters_hook_espresso__{className}__{methodName}' (eg,
5688
-     * filters_hook_espresso__EE_Answer__my_great_function) and accepts 2 arguments: the object on which the function
5689
-     * was called, and an array of the original arguments passed to the function. Whatever their callback function
5690
-     * returns will be returned by this function. Example: in functions.php (or in a plugin):
5691
-     * add_filter('FHEE__EE_Answer__my_callback','my_callback',10,3); function
5692
-     * my_callback($previousReturnValue,EE_TempBase $object,$argsArray){
5693
-     * $returnString= "you called my_callback! and passed args:".implode(",",$argsArray);
5694
-     *        return $previousReturnValue.$returnString;
5695
-     * }
5696
-     * require('EEM_Answer.model.php');
5697
-     * echo EEM_Answer::instance()->my_callback('monkeys',100);
5698
-     * // will output "you called my_callback! and passed args:monkeys,100"
5699
-     *
5700
-     * @param string $methodName name of method which was called on a child of EE_TempBase, but which
5701
-     * @param array  $args       array of original arguments passed to the function
5702
-     * @return mixed whatever the plugin which calls add_filter decides
5703
-     * @throws EE_Error
5704
-     */
5705
-    public function __call($methodName, $args)
5706
-    {
5707
-        $className = get_class($this);
5708
-        $tagName   = "FHEE__{$className}__{$methodName}";
5709
-        if (! has_filter($tagName)) {
5710
-            throw new EE_Error(
5711
-                sprintf(
5712
-                    esc_html__(
5713
-                        '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 );',
5714
-                        'event_espresso'
5715
-                    ),
5716
-                    $methodName,
5717
-                    $className,
5718
-                    $tagName,
5719
-                    '<br />'
5720
-                )
5721
-            );
5722
-        }
5723
-        return apply_filters($tagName, null, $this, $args);
5724
-    }
5725
-
5726
-
5727
-    /**
5728
-     * Ensures $base_class_obj_or_id is of the EE_Base_Class child that corresponds ot this model.
5729
-     * If not, assumes its an ID, and uses $this->get_one_by_ID() to get the EE_Base_Class.
5730
-     *
5731
-     * @param EE_Base_Class|string|int $base_class_obj_or_id either:
5732
-     *                                                       the EE_Base_Class object that corresponds to this Model,
5733
-     *                                                       the object's class name
5734
-     *                                                       or object's ID
5735
-     * @param boolean                  $ensure_is_in_db      if set, we will also verify this model object
5736
-     *                                                       exists in the database. If it does not, we add it
5737
-     * @return EE_Base_Class
5738
-     * @throws EE_Error
5739
-     * @throws ReflectionException
5740
-     */
5741
-    public function ensure_is_obj($base_class_obj_or_id, $ensure_is_in_db = false)
5742
-    {
5743
-        $className = $this->_get_class_name();
5744
-        if ($base_class_obj_or_id instanceof $className) {
5745
-            $model_object = $base_class_obj_or_id;
5746
-        } else {
5747
-            $primary_key_field = $this->get_primary_key_field();
5748
-            if (
5749
-                $primary_key_field instanceof EE_Primary_Key_Int_Field
5750
-                && (
5751
-                    is_int($base_class_obj_or_id)
5752
-                    || is_string($base_class_obj_or_id)
5753
-                )
5754
-            ) {
5755
-                // assume it's an ID.
5756
-                // either a proper integer or a string representing an integer (eg "101" instead of 101)
5757
-                $model_object = $this->get_one_by_ID($base_class_obj_or_id);
5758
-            } elseif (
5759
-                $primary_key_field instanceof EE_Primary_Key_String_Field
5760
-                && is_string($base_class_obj_or_id)
5761
-            ) {
5762
-                // assume its a string representation of the object
5763
-                $model_object = $this->get_one_by_ID($base_class_obj_or_id);
5764
-            } else {
5765
-                throw new EE_Error(
5766
-                    sprintf(
5767
-                        esc_html__(
5768
-                            "'%s' is neither an object of type %s, nor an ID! Its full value is '%s'",
5769
-                            'event_espresso'
5770
-                        ),
5771
-                        $base_class_obj_or_id,
5772
-                        $this->_get_class_name(),
5773
-                        print_r($base_class_obj_or_id, true)
5774
-                    )
5775
-                );
5776
-            }
5777
-        }
5778
-        if ($ensure_is_in_db && $model_object->ID() !== null) {
5779
-            $model_object->save();
5780
-        }
5781
-        return $model_object;
5782
-    }
5783
-
5784
-
5785
-    /**
5786
-     * Similar to ensure_is_obj(), this method makes sure $base_class_obj_or_id
5787
-     * is a value of the this model's primary key. If it's an EE_Base_Class child,
5788
-     * returns it ID.
5789
-     *
5790
-     * @param EE_Base_Class|int|string $base_class_obj_or_id
5791
-     * @return int|string depending on the type of this model object's ID
5792
-     * @throws EE_Error
5793
-     * @throws ReflectionException
5794
-     */
5795
-    public function ensure_is_ID($base_class_obj_or_id)
5796
-    {
5797
-        $className = $this->_get_class_name();
5798
-        if ($base_class_obj_or_id instanceof $className) {
5799
-            /** @var $base_class_obj_or_id EE_Base_Class */
5800
-            $id = $base_class_obj_or_id->ID();
5801
-        } elseif (is_int($base_class_obj_or_id)) {
5802
-            // assume it's an ID
5803
-            $id = $base_class_obj_or_id;
5804
-        } elseif (is_string($base_class_obj_or_id)) {
5805
-            // assume its a string representation of the object
5806
-            $id = $base_class_obj_or_id;
5807
-        } else {
5808
-            throw new EE_Error(
5809
-                sprintf(
5810
-                    esc_html__(
5811
-                        "'%s' is neither an object of type %s, nor an ID! Its full value is '%s'",
5812
-                        'event_espresso'
5813
-                    ),
5814
-                    $base_class_obj_or_id,
5815
-                    $this->_get_class_name(),
5816
-                    print_r($base_class_obj_or_id, true)
5817
-                )
5818
-            );
5819
-        }
5820
-        return $id;
5821
-    }
5822
-
5823
-
5824
-    /**
5825
-     * Sets whether the values passed to the model (eg, values in WHERE, values in INSERT, UPDATE, etc)
5826
-     * have already been ran through the appropriate model field's prepare_for_use_in_db method. IE, they have
5827
-     * been sanitized and converted into the appropriate domain.
5828
-     * Usually the only place you'll want to change the default (which is to assume values have NOT been sanitized by
5829
-     * the model object/model field) is when making a method call from WITHIN a model object, which has direct access
5830
-     * to its sanitized values. Note: after changing this setting, you should set it back to its previous value (using
5831
-     * get_assumption_concerning_values_already_prepared_by_model_object()) eg.
5832
-     * $EVT = EEM_Event::instance(); $old_setting =
5833
-     * $EVT->get_assumption_concerning_values_already_prepared_by_model_object();
5834
-     * $EVT->assume_values_already_prepared_by_model_object(true);
5835
-     * $EVT->update(array('foo'=>'bar'),array(array('foo'=>'monkey')));
5836
-     * $EVT->assume_values_already_prepared_by_model_object($old_setting);
5837
-     *
5838
-     * @param int $values_already_prepared like one of the constants on EEM_Base
5839
-     * @return void
5840
-     */
5841
-    public function assume_values_already_prepared_by_model_object(
5842
-        $values_already_prepared = self::not_prepared_by_model_object
5843
-    ) {
5844
-        $this->_values_already_prepared_by_model_object = $values_already_prepared;
5845
-    }
5846
-
5847
-
5848
-    /**
5849
-     * Read comments for assume_values_already_prepared_by_model_object()
5850
-     *
5851
-     * @return int
5852
-     */
5853
-    public function get_assumption_concerning_values_already_prepared_by_model_object()
5854
-    {
5855
-        return $this->_values_already_prepared_by_model_object;
5856
-    }
5857
-
5858
-
5859
-    /**
5860
-     * Gets all the indexes on this model
5861
-     *
5862
-     * @return EE_Index[]
5863
-     */
5864
-    public function indexes()
5865
-    {
5866
-        return $this->_indexes;
5867
-    }
5868
-
5869
-
5870
-    /**
5871
-     * Gets all the Unique Indexes on this model
5872
-     *
5873
-     * @return EE_Unique_Index[]
5874
-     */
5875
-    public function unique_indexes()
5876
-    {
5877
-        $unique_indexes = [];
5878
-        foreach ($this->_indexes as $name => $index) {
5879
-            if ($index instanceof EE_Unique_Index) {
5880
-                $unique_indexes [ $name ] = $index;
5881
-            }
5882
-        }
5883
-        return $unique_indexes;
5884
-    }
5885
-
5886
-
5887
-    /**
5888
-     * Gets all the fields which, when combined, make the primary key.
5889
-     * This is usually just an array with 1 element (the primary key), but in cases
5890
-     * where there is no primary key, it's a combination of fields as defined
5891
-     * on a primary index
5892
-     *
5893
-     * @return EE_Model_Field_Base[] indexed by the field's name
5894
-     * @throws EE_Error
5895
-     */
5896
-    public function get_combined_primary_key_fields()
5897
-    {
5898
-        foreach ($this->indexes() as $index) {
5899
-            if ($index instanceof EE_Primary_Key_Index) {
5900
-                return $index->fields();
5901
-            }
5902
-        }
5903
-        return [$this->primary_key_name() => $this->get_primary_key_field()];
5904
-    }
5905
-
5906
-
5907
-    /**
5908
-     * Used to build a primary key string (when the model has no primary key),
5909
-     * which can be used a unique string to identify this model object.
5910
-     *
5911
-     * @param array $fields_n_values keys are field names, values are their values.
5912
-     *                               Note: if you have results from `EEM_Base::get_all_wpdb_results()`, you need to
5913
-     *                               run it through `EEM_Base::deduce_fields_n_values_from_cols_n_values()`
5914
-     *                               before passing it to this function (that will convert it from columns-n-values
5915
-     *                               to field-names-n-values).
5916
-     * @return string
5917
-     * @throws EE_Error
5918
-     */
5919
-    public function get_index_primary_key_string($fields_n_values)
5920
-    {
5921
-        $cols_n_values_for_primary_key_index = array_intersect_key(
5922
-            $fields_n_values,
5923
-            $this->get_combined_primary_key_fields()
5924
-        );
5925
-        return http_build_query($cols_n_values_for_primary_key_index);
5926
-    }
5927
-
5928
-
5929
-    /**
5930
-     * Gets the field values from the primary key string
5931
-     *
5932
-     * @param string $index_primary_key_string
5933
-     * @return null|array
5934
-     * @throws EE_Error
5935
-     * @see EEM_Base::get_combined_primary_key_fields() and EEM_Base::get_index_primary_key_string()
5936
-     */
5937
-    public function parse_index_primary_key_string($index_primary_key_string)
5938
-    {
5939
-        $key_fields = $this->get_combined_primary_key_fields();
5940
-        // check all of them are in the $id
5941
-        $key_vals_in_combined_pk = [];
5942
-        parse_str($index_primary_key_string, $key_vals_in_combined_pk);
5943
-        foreach ($key_fields as $key_field_name => $field_obj) {
5944
-            if (! isset($key_vals_in_combined_pk[ $key_field_name ])) {
5945
-                return null;
5946
-            }
5947
-        }
5948
-        return $key_vals_in_combined_pk;
5949
-    }
5950
-
5951
-
5952
-    /**
5953
-     * verifies that an array of key-value pairs for model fields has a key
5954
-     * for each field comprising the primary key index
5955
-     *
5956
-     * @param array $key_vals
5957
-     * @return boolean
5958
-     * @throws EE_Error
5959
-     */
5960
-    public function has_all_combined_primary_key_fields($key_vals)
5961
-    {
5962
-        $keys_it_should_have = array_keys($this->get_combined_primary_key_fields());
5963
-        foreach ($keys_it_should_have as $key) {
5964
-            if (! isset($key_vals[ $key ])) {
5965
-                return false;
5966
-            }
5967
-        }
5968
-        return true;
5969
-    }
5970
-
5971
-
5972
-    /**
5973
-     * Finds all model objects in the DB that appear to be a copy of $model_object_or_attributes_array.
5974
-     * We consider something to be a copy if all the attributes match (except the ID, of course).
5975
-     *
5976
-     * @param array|EE_Base_Class $model_object_or_attributes_array If its an array, it's field-value pairs
5977
-     * @param array               $query_params                     @see
5978
-     *                                                              https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
5979
-     * @throws EE_Error
5980
-     * @throws ReflectionException
5981
-     * @return EE_Base_Class[] Array keys are object IDs (if there is a primary key on the model. if not, numerically
5982
-     *                                                              indexed)
5983
-     */
5984
-    public function get_all_copies($model_object_or_attributes_array, $query_params = [])
5985
-    {
5986
-        if ($model_object_or_attributes_array instanceof EE_Base_Class) {
5987
-            $attributes_array = $model_object_or_attributes_array->model_field_array();
5988
-        } elseif (is_array($model_object_or_attributes_array)) {
5989
-            $attributes_array = $model_object_or_attributes_array;
5990
-        } else {
5991
-            throw new EE_Error(
5992
-                sprintf(
5993
-                    esc_html__(
5994
-                        "get_all_copies should be provided with either a model object or an array of field-value-pairs, but was given %s",
5995
-                        "event_espresso"
5996
-                    ),
5997
-                    $model_object_or_attributes_array
5998
-                )
5999
-            );
6000
-        }
6001
-        // even copies obviously won't have the same ID, so remove the primary key
6002
-        // from the WHERE conditions for finding copies (if there is a primary key, of course)
6003
-        if ($this->has_primary_key_field() && isset($attributes_array[ $this->primary_key_name() ])) {
6004
-            unset($attributes_array[ $this->primary_key_name() ]);
6005
-        }
6006
-        if (isset($query_params[0])) {
6007
-            $query_params[0] = array_merge($attributes_array, $query_params);
6008
-        } else {
6009
-            $query_params[0] = $attributes_array;
6010
-        }
6011
-        return $this->get_all($query_params);
6012
-    }
6013
-
6014
-
6015
-    /**
6016
-     * Gets the first copy we find. See get_all_copies for more details
6017
-     *
6018
-     * @param mixed EE_Base_Class | array        $model_object_or_attributes_array
6019
-     * @param array $query_params
6020
-     * @return EE_Base_Class
6021
-     * @throws EE_Error
6022
-     * @throws ReflectionException
6023
-     */
6024
-    public function get_one_copy($model_object_or_attributes_array, $query_params = [])
6025
-    {
6026
-        if (! is_array($query_params)) {
6027
-            EE_Error::doing_it_wrong(
6028
-                'EEM_Base::get_one_copy',
6029
-                sprintf(
6030
-                    esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
6031
-                    gettype($query_params)
6032
-                ),
6033
-                '4.6.0'
6034
-            );
6035
-            $query_params = [];
6036
-        }
6037
-        $query_params['limit'] = 1;
6038
-        $copies                = $this->get_all_copies($model_object_or_attributes_array, $query_params);
6039
-        if (is_array($copies)) {
6040
-            return array_shift($copies);
6041
-        }
6042
-        return null;
6043
-    }
6044
-
6045
-
6046
-    /**
6047
-     * Updates the item with the specified id. Ignores default query parameters because
6048
-     * we have specified the ID, and its assumed we KNOW what we're doing
6049
-     *
6050
-     * @param array      $fields_n_values keys are field names, values are their new values
6051
-     * @param int|string $id              the value of the primary key to update
6052
-     * @return int number of rows updated
6053
-     * @throws EE_Error
6054
-     * @throws ReflectionException
6055
-     */
6056
-    public function update_by_ID($fields_n_values, $id)
6057
-    {
6058
-        $query_params = [
6059
-            0                          => [$this->get_primary_key_field()->get_name() => $id],
6060
-            'default_where_conditions' => EEM_Base::default_where_conditions_others_only,
6061
-        ];
6062
-        return $this->update($fields_n_values, $query_params);
6063
-    }
6064
-
6065
-
6066
-    /**
6067
-     * Changes an operator which was supplied to the models into one usable in SQL
6068
-     *
6069
-     * @param string $operator_supplied
6070
-     * @return string an operator which can be used in SQL
6071
-     * @throws EE_Error
6072
-     */
6073
-    private function _prepare_operator_for_sql($operator_supplied)
6074
-    {
6075
-        $sql_operator = $this->_valid_operators[ $operator_supplied ] ?? null;
6076
-        if ($sql_operator) {
6077
-            return $sql_operator;
6078
-        }
6079
-        throw new EE_Error(
6080
-            sprintf(
6081
-                esc_html__(
6082
-                    "The operator '%s' is not in the list of valid operators: %s",
6083
-                    "event_espresso"
6084
-                ),
6085
-                $operator_supplied,
6086
-                implode(",", array_keys($this->_valid_operators))
6087
-            )
6088
-        );
6089
-    }
6090
-
6091
-
6092
-    /**
6093
-     * Gets the valid operators
6094
-     *
6095
-     * @return array keys are accepted strings, values are the SQL they are converted to
6096
-     */
6097
-    public function valid_operators()
6098
-    {
6099
-        return $this->_valid_operators;
6100
-    }
6101
-
6102
-
6103
-    /**
6104
-     * Gets the between-style operators (take 2 arguments).
6105
-     *
6106
-     * @return array keys are accepted strings, values are the SQL they are converted to
6107
-     */
6108
-    public function valid_between_style_operators()
6109
-    {
6110
-        return array_intersect(
6111
-            $this->valid_operators(),
6112
-            $this->_between_style_operators
6113
-        );
6114
-    }
6115
-
6116
-
6117
-    /**
6118
-     * Gets the "like"-style operators (take a single argument, but it may contain wildcards)
6119
-     *
6120
-     * @return array keys are accepted strings, values are the SQL they are converted to
6121
-     */
6122
-    public function valid_like_style_operators()
6123
-    {
6124
-        return array_intersect(
6125
-            $this->valid_operators(),
6126
-            $this->_like_style_operators
6127
-        );
6128
-    }
6129
-
6130
-
6131
-    /**
6132
-     * Gets the "in"-style operators
6133
-     *
6134
-     * @return array keys are accepted strings, values are the SQL they are converted to
6135
-     */
6136
-    public function valid_in_style_operators()
6137
-    {
6138
-        return array_intersect(
6139
-            $this->valid_operators(),
6140
-            $this->_in_style_operators
6141
-        );
6142
-    }
6143
-
6144
-
6145
-    /**
6146
-     * Gets the "null"-style operators (accept no arguments)
6147
-     *
6148
-     * @return array keys are accepted strings, values are the SQL they are converted to
6149
-     */
6150
-    public function valid_null_style_operators()
6151
-    {
6152
-        return array_intersect(
6153
-            $this->valid_operators(),
6154
-            $this->_null_style_operators
6155
-        );
6156
-    }
6157
-
6158
-
6159
-    /**
6160
-     * Gets an array where keys are the primary keys and values are their 'names'
6161
-     * (as determined by the model object's name() function, which is often overridden)
6162
-     *
6163
-     * @param array $query_params like get_all's
6164
-     * @return string[]
6165
-     * @throws EE_Error
6166
-     * @throws ReflectionException
6167
-     */
6168
-    public function get_all_names($query_params = [])
6169
-    {
6170
-        $objs  = $this->get_all($query_params);
6171
-        $names = [];
6172
-        foreach ($objs as $obj) {
6173
-            $names[ $obj->ID() ] = $obj->name();
6174
-        }
6175
-        return $names;
6176
-    }
6177
-
6178
-
6179
-    /**
6180
-     * Gets an array of primary keys from the model objects. If you acquired the model objects
6181
-     * using EEM_Base::get_all() you don't need to call this (and probably shouldn't because
6182
-     * this is duplicated effort and reduces efficiency) you would be better to use
6183
-     * array_keys() on $model_objects.
6184
-     *
6185
-     * @param \EE_Base_Class[] $model_objects
6186
-     * @param boolean          $filter_out_empty_ids if a model object has an ID of '' or 0, don't bother including it
6187
-     *                                               in the returned array
6188
-     * @return array
6189
-     * @throws EE_Error
6190
-     * @throws ReflectionException
6191
-     */
6192
-    public function get_IDs($model_objects, $filter_out_empty_ids = false)
6193
-    {
6194
-        if (! $this->has_primary_key_field()) {
6195
-            if (WP_DEBUG) {
6196
-                EE_Error::add_error(
6197
-                    esc_html__('Trying to get IDs from a model than has no primary key', 'event_espresso'),
6198
-                    __FILE__,
6199
-                    __FUNCTION__,
6200
-                    __LINE__
6201
-                );
6202
-            }
6203
-        }
6204
-        $IDs = [];
6205
-        foreach ($model_objects as $model_object) {
6206
-            $id = $model_object->ID();
6207
-            if (! $id) {
6208
-                if ($filter_out_empty_ids) {
6209
-                    continue;
6210
-                }
6211
-                if (WP_DEBUG) {
6212
-                    EE_Error::add_error(
6213
-                        esc_html__(
6214
-                            'Called %1$s on a model object that has no ID and so probably hasn\'t been saved to the database',
6215
-                            'event_espresso'
6216
-                        ),
6217
-                        __FILE__,
6218
-                        __FUNCTION__,
6219
-                        __LINE__
6220
-                    );
6221
-                }
6222
-            }
6223
-            $IDs[] = $id;
6224
-        }
6225
-        return $IDs;
6226
-    }
6227
-
6228
-
6229
-    /**
6230
-     * Returns the string used in capabilities relating to this model. If there
6231
-     * are no capabilities that relate to this model returns false
6232
-     *
6233
-     * @return string|false
6234
-     */
6235
-    public function cap_slug()
6236
-    {
6237
-        return apply_filters('FHEE__EEM_Base__cap_slug', $this->_caps_slug, $this);
6238
-    }
6239
-
6240
-
6241
-    /**
6242
-     * Returns the capability-restrictions array (@param string $context
6243
-     *
6244
-     * @return EE_Default_Where_Conditions[] indexed by associated capability
6245
-     * @throws EE_Error
6246
-     * @see EEM_Base::_cap_restrictions).
6247
-     *      If $context is provided (which should be set to one of EEM_Base::valid_cap_contexts())
6248
-     *      only returns the cap restrictions array in that context (ie, the array
6249
-     *      at that key)
6250
-     *
6251
-     */
6252
-    public function cap_restrictions($context = EEM_Base::caps_read)
6253
-    {
6254
-        EEM_Base::verify_is_valid_cap_context($context);
6255
-        // check if we ought to run the restriction generator first
6256
-        if (
6257
-            isset($this->_cap_restriction_generators[ $context ])
6258
-            && $this->_cap_restriction_generators[ $context ] instanceof EE_Restriction_Generator_Base
6259
-            && ! $this->_cap_restriction_generators[ $context ]->has_generated_cap_restrictions()
6260
-        ) {
6261
-            $this->_cap_restrictions[ $context ] = array_merge(
6262
-                $this->_cap_restrictions[ $context ],
6263
-                $this->_cap_restriction_generators[ $context ]->generate_restrictions()
6264
-            );
6265
-        }
6266
-        // and make sure we've finalized the construction of each restriction
6267
-        foreach ($this->_cap_restrictions[ $context ] as $where_conditions_obj) {
6268
-            if ($where_conditions_obj instanceof EE_Default_Where_Conditions) {
6269
-                $where_conditions_obj->_finalize_construct($this);
6270
-            }
6271
-        }
6272
-        return $this->_cap_restrictions[ $context ];
6273
-    }
6274
-
6275
-
6276
-    /**
6277
-     * Indicating whether or not this model thinks its a wp core model
6278
-     *
6279
-     * @return boolean
6280
-     */
6281
-    public function is_wp_core_model()
6282
-    {
6283
-        return $this->_wp_core_model;
6284
-    }
6285
-
6286
-
6287
-    /**
6288
-     * Gets all the caps that are missing which impose a restriction on
6289
-     * queries made in this context
6290
-     *
6291
-     * @param string $context one of EEM_Base::caps_ constants
6292
-     * @return EE_Default_Where_Conditions[] indexed by capability name
6293
-     * @throws EE_Error
6294
-     */
6295
-    public function caps_missing($context = EEM_Base::caps_read)
6296
-    {
6297
-        $missing_caps     = [];
6298
-        $cap_restrictions = $this->cap_restrictions($context);
6299
-        foreach ($cap_restrictions as $cap => $restriction_if_no_cap) {
6300
-            if (
6301
-                ! EE_Capabilities::instance()
6302
-                                 ->current_user_can($cap, $this->get_this_model_name() . '_model_applying_caps')
6303
-            ) {
6304
-                $missing_caps[ $cap ] = $restriction_if_no_cap;
6305
-            }
6306
-        }
6307
-        return $missing_caps;
6308
-    }
6309
-
6310
-
6311
-    /**
6312
-     * Gets the mapping from capability contexts to action strings used in capability names
6313
-     *
6314
-     * @return array keys are one of EEM_Base::valid_cap_contexts(), and values are usually
6315
-     * one of 'read', 'edit', or 'delete'
6316
-     */
6317
-    public function cap_contexts_to_cap_action_map()
6318
-    {
6319
-        return apply_filters(
6320
-            'FHEE__EEM_Base__cap_contexts_to_cap_action_map',
6321
-            $this->_cap_contexts_to_cap_action_map,
6322
-            $this
6323
-        );
6324
-    }
6325
-
6326
-
6327
-    /**
6328
-     * Gets the action string for the specified capability context
6329
-     *
6330
-     * @param string $context
6331
-     * @return string one of EEM_Base::cap_contexts_to_cap_action_map() values
6332
-     * @throws EE_Error
6333
-     */
6334
-    public function cap_action_for_context($context)
6335
-    {
6336
-        $mapping = $this->cap_contexts_to_cap_action_map();
6337
-        if (isset($mapping[ $context ])) {
6338
-            return $mapping[ $context ];
6339
-        }
6340
-        if ($action = apply_filters('FHEE__EEM_Base__cap_action_for_context', null, $this, $mapping, $context)) {
6341
-            return $action;
6342
-        }
6343
-        throw new EE_Error(
6344
-            sprintf(
6345
-                esc_html__(
6346
-                    'Cannot find capability restrictions for context "%1$s", allowed values are:%2$s',
6347
-                    'event_espresso'
6348
-                ),
6349
-                $context,
6350
-                implode(',', array_keys($this->cap_contexts_to_cap_action_map()))
6351
-            )
6352
-        );
6353
-    }
6354
-
6355
-
6356
-    /**
6357
-     * Returns all the capability contexts which are valid when querying models
6358
-     *
6359
-     * @return array
6360
-     */
6361
-    public static function valid_cap_contexts()
6362
-    {
6363
-        return apply_filters('FHEE__EEM_Base__valid_cap_contexts', [
6364
-            self::caps_read,
6365
-            self::caps_read_admin,
6366
-            self::caps_edit,
6367
-            self::caps_delete,
6368
-        ]);
6369
-    }
6370
-
6371
-
6372
-    /**
6373
-     * Returns all valid options for 'default_where_conditions'
6374
-     *
6375
-     * @return array
6376
-     */
6377
-    public static function valid_default_where_conditions()
6378
-    {
6379
-        return [
6380
-            EEM_Base::default_where_conditions_all,
6381
-            EEM_Base::default_where_conditions_this_only,
6382
-            EEM_Base::default_where_conditions_others_only,
6383
-            EEM_Base::default_where_conditions_minimum_all,
6384
-            EEM_Base::default_where_conditions_minimum_others,
6385
-            EEM_Base::default_where_conditions_none,
6386
-        ];
6387
-    }
6388
-
6389
-    // public static function default_where_conditions_full
6390
-
6391
-
6392
-    /**
6393
-     * Verifies $context is one of EEM_Base::valid_cap_contexts(), if not it throws an exception
6394
-     *
6395
-     * @param string $context
6396
-     * @return bool
6397
-     * @throws EE_Error
6398
-     */
6399
-    public static function verify_is_valid_cap_context($context)
6400
-    {
6401
-        $valid_cap_contexts = EEM_Base::valid_cap_contexts();
6402
-        if (in_array($context, $valid_cap_contexts)) {
6403
-            return true;
6404
-        }
6405
-        throw new EE_Error(
6406
-            sprintf(
6407
-                esc_html__(
6408
-                    'Context "%1$s" passed into model "%2$s" is not a valid context. They are: %3$s',
6409
-                    'event_espresso'
6410
-                ),
6411
-                $context,
6412
-                'EEM_Base',
6413
-                implode(',', $valid_cap_contexts)
6414
-            )
6415
-        );
6416
-    }
6417
-
6418
-
6419
-    /**
6420
-     * Clears all the models field caches. This is only useful when a sub-class
6421
-     * might have added a field or something and these caches might be invalidated
6422
-     */
6423
-    protected function _invalidate_field_caches()
6424
-    {
6425
-        $this->_cache_foreign_key_to_fields = [];
6426
-        $this->_cached_fields               = null;
6427
-        $this->_cached_fields_non_db_only   = null;
6428
-    }
6429
-
6430
-
6431
-    /**
6432
-     * Gets the list of all the where query param keys that relate to logic instead of field names
6433
-     * (eg "and", "or", "not").
6434
-     *
6435
-     * @return array
6436
-     */
6437
-    public function logic_query_param_keys()
6438
-    {
6439
-        return $this->_logic_query_param_keys;
6440
-    }
6441
-
6442
-
6443
-    /**
6444
-     * Determines whether or not the where query param array key is for a logic query param.
6445
-     * Eg 'OR', 'not*', and 'and*because-i-say-so' should all return true, whereas
6446
-     * 'ATT_fname', 'EVT_name*not-you-or-me', and 'ORG_name' should return false
6447
-     *
6448
-     * @param $query_param_key
6449
-     * @return bool
6450
-     */
6451
-    public function is_logic_query_param_key($query_param_key)
6452
-    {
6453
-        foreach ($this->logic_query_param_keys() as $logic_query_param_key) {
6454
-            if (
6455
-                $query_param_key === $logic_query_param_key
6456
-                || strpos($query_param_key, $logic_query_param_key . '*') === 0
6457
-            ) {
6458
-                return true;
6459
-            }
6460
-        }
6461
-        return false;
6462
-    }
6463
-
6464
-
6465
-    /**
6466
-     * Returns true if this model has a password field on it (regardless of whether that password field has any content)
6467
-     *
6468
-     * @return boolean
6469
-     * @since 4.9.74.p
6470
-     */
6471
-    public function hasPassword()
6472
-    {
6473
-        // if we don't yet know if there's a password field, find out and remember it for next time.
6474
-        if ($this->has_password_field === null) {
6475
-            $password_field           = $this->getPasswordField();
6476
-            $this->has_password_field = $password_field instanceof EE_Password_Field ? true : false;
6477
-        }
6478
-        return $this->has_password_field;
6479
-    }
6480
-
6481
-
6482
-    /**
6483
-     * Returns the password field on this model, if there is one
6484
-     *
6485
-     * @return EE_Password_Field|null
6486
-     * @since 4.9.74.p
6487
-     */
6488
-    public function getPasswordField()
6489
-    {
6490
-        // if we definetely already know there is a password field or not (because has_password_field is true or false)
6491
-        // there's no need to search for it. If we don't know yet, then find out
6492
-        if ($this->has_password_field === null && $this->password_field === null) {
6493
-            $this->password_field = $this->get_a_field_of_type('EE_Password_Field');
6494
-        }
6495
-        // don't bother setting has_password_field because that's hasPassword()'s job.
6496
-        return $this->password_field;
6497
-    }
6498
-
6499
-
6500
-    /**
6501
-     * Returns the list of field (as EE_Model_Field_Bases) that are protected by the password
6502
-     *
6503
-     * @return EE_Model_Field_Base[]
6504
-     * @throws EE_Error
6505
-     * @since 4.9.74.p
6506
-     */
6507
-    public function getPasswordProtectedFields()
6508
-    {
6509
-        $password_field = $this->getPasswordField();
6510
-        $fields         = [];
6511
-        if ($password_field instanceof EE_Password_Field) {
6512
-            $field_names = $password_field->protectedFields();
6513
-            foreach ($field_names as $field_name) {
6514
-                $fields[ $field_name ] = $this->field_settings_for($field_name);
6515
-            }
6516
-        }
6517
-        return $fields;
6518
-    }
6519
-
6520
-
6521
-    /**
6522
-     * Checks if the current user can perform the requested action on this model
6523
-     *
6524
-     * @param string              $cap_to_check one of the array keys from _cap_contexts_to_cap_action_map
6525
-     * @param EE_Base_Class|array $model_obj_or_fields_n_values
6526
-     * @return bool
6527
-     * @throws EE_Error
6528
-     * @throws InvalidArgumentException
6529
-     * @throws InvalidDataTypeException
6530
-     * @throws InvalidInterfaceException
6531
-     * @throws ReflectionException
6532
-     * @throws UnexpectedEntityException
6533
-     * @since 4.9.74.p
6534
-     */
6535
-    public function currentUserCan($cap_to_check, $model_obj_or_fields_n_values)
6536
-    {
6537
-        if ($model_obj_or_fields_n_values instanceof EE_Base_Class) {
6538
-            $model_obj_or_fields_n_values = $model_obj_or_fields_n_values->model_field_array();
6539
-        }
6540
-        if (! is_array($model_obj_or_fields_n_values)) {
6541
-            throw new UnexpectedEntityException(
6542
-                $model_obj_or_fields_n_values,
6543
-                'EE_Base_Class',
6544
-                sprintf(
6545
-                    esc_html__(
6546
-                        '%1$s must be passed an `EE_Base_Class or an array of fields names with their values. You passed in something different.',
6547
-                        'event_espresso'
6548
-                    ),
6549
-                    __FUNCTION__
6550
-                )
6551
-            );
6552
-        }
6553
-        return $this->exists(
6554
-            $this->alter_query_params_to_restrict_by_ID(
6555
-                $this->get_index_primary_key_string($model_obj_or_fields_n_values),
6556
-                [
6557
-                    'default_where_conditions' => 'none',
6558
-                    'caps'                     => $cap_to_check,
6559
-                ]
6560
-            )
6561
-        );
6562
-    }
6563
-
6564
-
6565
-    /**
6566
-     * Returns the query param where conditions key to the password affecting this model.
6567
-     * Eg on EEM_Event this would just be "password", on EEM_Datetime this would be "Event.password", etc.
6568
-     *
6569
-     * @return null|string
6570
-     * @throws EE_Error
6571
-     * @throws InvalidArgumentException
6572
-     * @throws InvalidDataTypeException
6573
-     * @throws InvalidInterfaceException
6574
-     * @throws ModelConfigurationException
6575
-     * @throws ReflectionException
6576
-     * @since 4.9.74.p
6577
-     */
6578
-    public function modelChainAndPassword()
6579
-    {
6580
-        if ($this->model_chain_to_password === null) {
6581
-            throw new ModelConfigurationException(
6582
-                $this,
6583
-                esc_html_x(
6584
-                // @codingStandardsIgnoreStart
6585
-                    'Cannot exclude protected data because the model has not specified which model has the password.',
6586
-                    // @codingStandardsIgnoreEnd
6587
-                    '1: model name',
6588
-                    'event_espresso'
6589
-                )
6590
-            );
6591
-        }
6592
-        if ($this->model_chain_to_password === '') {
6593
-            $model_with_password = $this;
6594
-        } else {
6595
-            if ($pos_of_period = strrpos($this->model_chain_to_password, '.')) {
6596
-                $last_model_in_chain = substr($this->model_chain_to_password, $pos_of_period + 1);
6597
-            } else {
6598
-                $last_model_in_chain = $this->model_chain_to_password;
6599
-            }
6600
-            $model_with_password = EE_Registry::instance()->load_model($last_model_in_chain);
6601
-        }
6602
-
6603
-        $password_field = $model_with_password->getPasswordField();
6604
-        if ($password_field instanceof EE_Password_Field) {
6605
-            $password_field_name = $password_field->get_name();
6606
-        } else {
6607
-            throw new ModelConfigurationException(
6608
-                $this,
6609
-                sprintf(
6610
-                    esc_html_x(
6611
-                        '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"',
6612
-                        '1: model name, 2: special string',
6613
-                        'event_espresso'
6614
-                    ),
6615
-                    $model_with_password->get_this_model_name(),
6616
-                    $this->model_chain_to_password
6617
-                )
6618
-            );
6619
-        }
6620
-        return ($this->model_chain_to_password ? $this->model_chain_to_password . '.' : '') . $password_field_name;
6621
-    }
6622
-
6623
-
6624
-    /**
6625
-     * Returns true if there is a password on a related model which restricts access to some of this model's rows,
6626
-     * or if this model itself has a password affecting access to some of its other fields.
6627
-     *
6628
-     * @return boolean
6629
-     * @since 4.9.74.p
6630
-     */
6631
-    public function restrictedByRelatedModelPassword()
6632
-    {
6633
-        return $this->model_chain_to_password !== null;
6634
-    }
3891
+		}
3892
+		return $null_friendly_where_conditions;
3893
+	}
3894
+
3895
+
3896
+	/**
3897
+	 * Uses the _default_where_conditions_strategy set during __construct() to get
3898
+	 * default where conditions on all get_all, update, and delete queries done by this model.
3899
+	 * Use the same syntax as client code. Eg on the Event model, use array('Event.EVT_post_type'=>'esp_event'),
3900
+	 * NOT array('Event_CPT.post_type'=>'esp_event').
3901
+	 *
3902
+	 * @param string $model_relation_path eg, path from Event to Payment is "Registration.Transaction.Payment."
3903
+	 * @return array @see
3904
+	 *                                    https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3905
+	 * @throws EE_Error
3906
+	 * @throws EE_Error
3907
+	 */
3908
+	private function _get_default_where_conditions($model_relation_path = '')
3909
+	{
3910
+		if ($this->_ignore_where_strategy) {
3911
+			return [];
3912
+		}
3913
+		return $this->_default_where_conditions_strategy->get_default_where_conditions($model_relation_path);
3914
+	}
3915
+
3916
+
3917
+	/**
3918
+	 * Uses the _minimum_where_conditions_strategy set during __construct() to get
3919
+	 * minimum where conditions on all get_all, update, and delete queries done by this model.
3920
+	 * Use the same syntax as client code. Eg on the Event model, use array('Event.EVT_post_type'=>'esp_event'),
3921
+	 * NOT array('Event_CPT.post_type'=>'esp_event').
3922
+	 * Similar to _get_default_where_conditions
3923
+	 *
3924
+	 * @param string $model_relation_path eg, path from Event to Payment is "Registration.Transaction.Payment."
3925
+	 * @return array @see
3926
+	 *                                    https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
3927
+	 * @throws EE_Error
3928
+	 * @throws EE_Error
3929
+	 */
3930
+	protected function _get_minimum_where_conditions($model_relation_path = '')
3931
+	{
3932
+		if ($this->_ignore_where_strategy) {
3933
+			return [];
3934
+		}
3935
+		return $this->_minimum_where_conditions_strategy->get_default_where_conditions($model_relation_path);
3936
+	}
3937
+
3938
+
3939
+	/**
3940
+	 * Creates the string of SQL for the select part of a select query, everything behind SELECT and before FROM.
3941
+	 * Eg, "Event.post_id, Event.post_name,Event_Detail.EVT_ID..."
3942
+	 *
3943
+	 * @param EE_Model_Query_Info_Carrier $model_query_info
3944
+	 * @return string
3945
+	 * @throws EE_Error
3946
+	 */
3947
+	private function _construct_default_select_sql(EE_Model_Query_Info_Carrier $model_query_info)
3948
+	{
3949
+		$selects = $this->_get_columns_to_select_for_this_model();
3950
+		foreach (
3951
+			$model_query_info->get_model_names_included() as $model_relation_chain => $name_of_other_model_included
3952
+		) {
3953
+			$other_model_included = $this->get_related_model_obj($name_of_other_model_included);
3954
+			$other_model_selects  = $other_model_included->_get_columns_to_select_for_this_model($model_relation_chain);
3955
+			foreach ($other_model_selects as $key => $value) {
3956
+				$selects[] = $value;
3957
+			}
3958
+		}
3959
+		return implode(", ", $selects);
3960
+	}
3961
+
3962
+
3963
+	/**
3964
+	 * Gets an array of columns to select for this model, which are necessary for it to create its objects.
3965
+	 * So that's going to be the columns for all the fields on the model
3966
+	 *
3967
+	 * @param string $model_relation_chain like 'Question.Question_Group.Event'
3968
+	 * @return array numerically indexed, values are columns to select and rename, eg "Event.ID AS 'Event.ID'"
3969
+	 */
3970
+	public function _get_columns_to_select_for_this_model($model_relation_chain = '')
3971
+	{
3972
+		$fields                                       = $this->field_settings();
3973
+		$selects                                      = [];
3974
+		$table_alias_with_model_relation_chain_prefix =
3975
+			EE_Model_Parser::extract_table_alias_model_relation_chain_prefix(
3976
+				$model_relation_chain,
3977
+				$this->get_this_model_name()
3978
+			);
3979
+		foreach ($fields as $field_obj) {
3980
+			$selects[] = $table_alias_with_model_relation_chain_prefix
3981
+						 . $field_obj->get_table_alias()
3982
+						 . "."
3983
+						 . $field_obj->get_table_column()
3984
+						 . " AS '"
3985
+						 . $table_alias_with_model_relation_chain_prefix
3986
+						 . $field_obj->get_table_alias()
3987
+						 . "."
3988
+						 . $field_obj->get_table_column()
3989
+						 . "'";
3990
+		}
3991
+		// make sure we are also getting the PKs of each table
3992
+		$tables = $this->get_tables();
3993
+		if (count($tables) > 1) {
3994
+			foreach ($tables as $table_obj) {
3995
+				$qualified_pk_column = $table_alias_with_model_relation_chain_prefix
3996
+									   . $table_obj->get_fully_qualified_pk_column();
3997
+				if (! in_array($qualified_pk_column, $selects)) {
3998
+					$selects[] = "$qualified_pk_column AS '$qualified_pk_column'";
3999
+				}
4000
+			}
4001
+		}
4002
+		return $selects;
4003
+	}
4004
+
4005
+
4006
+	/**
4007
+	 * Given a $query_param like 'Registration.Transaction.TXN_ID', pops off 'Registration.',
4008
+	 * gets the join statement for it; gets the data types for it; and passes the remaining 'Transaction.TXN_ID'
4009
+	 * onto its related Transaction object to do the same. Returns an EE_Join_And_Data_Types object which contains the
4010
+	 * SQL for joining, and the data types
4011
+	 *
4012
+	 * @param null|string                 $original_query_param
4013
+	 * @param string                      $query_param          like Registration.Transaction.TXN_ID
4014
+	 * @param EE_Model_Query_Info_Carrier $passed_in_query_info
4015
+	 * @param string                      $query_param_type     like Registration.Transaction.TXN_ID
4016
+	 *                                                          or 'PAY_ID'. Otherwise, we don't expect there to be a
4017
+	 *                                                          column name. We only want model names, eg 'Event.Venue'
4018
+	 *                                                          or 'Registration's
4019
+	 * @param string                      $original_query_param what it originally was (eg
4020
+	 *                                                          Registration.Transaction.TXN_ID). If null, we assume it
4021
+	 *                                                          matches $query_param
4022
+	 * @return void only modifies the EEM_Related_Model_Info_Carrier passed into it
4023
+	 * @throws EE_Error
4024
+	 */
4025
+	private function _extract_related_model_info_from_query_param(
4026
+		$query_param,
4027
+		EE_Model_Query_Info_Carrier $passed_in_query_info,
4028
+		$query_param_type,
4029
+		$original_query_param = null
4030
+	) {
4031
+		if ($original_query_param === null) {
4032
+			$original_query_param = $query_param;
4033
+		}
4034
+		$query_param = $this->_remove_stars_and_anything_after_from_condition_query_param_key($query_param);
4035
+		// check to see if we have a field on this model
4036
+		$this_model_fields = $this->field_settings(true);
4037
+		if (array_key_exists($query_param, $this_model_fields)) {
4038
+			$field_is_allowed = in_array(
4039
+				$query_param_type,
4040
+				[0, 'where', 'having', 'order_by', 'group_by', 'order', 'custom_selects'],
4041
+				true
4042
+			);
4043
+			if ($field_is_allowed) {
4044
+				return;
4045
+			}
4046
+			throw new EE_Error(
4047
+				sprintf(
4048
+					esc_html__(
4049
+						"Using a field name (%s) on model %s is not allowed on this query param type '%s'. Original query param was %s",
4050
+						"event_espresso"
4051
+					),
4052
+					$query_param,
4053
+					get_class($this),
4054
+					$query_param_type,
4055
+					$original_query_param
4056
+				)
4057
+			);
4058
+		}
4059
+		// check if this is a special logic query param
4060
+		if (in_array($query_param, $this->_logic_query_param_keys, true)) {
4061
+			$operator_is_allowed = in_array($query_param_type, ['where', 'having', 0, 'custom_selects'], true);
4062
+			if ($operator_is_allowed) {
4063
+				return;
4064
+			}
4065
+			throw new EE_Error(
4066
+				sprintf(
4067
+					esc_html__(
4068
+						'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',
4069
+						'event_espresso'
4070
+					),
4071
+					implode('", "', $this->_logic_query_param_keys),
4072
+					$query_param,
4073
+					get_class($this),
4074
+					'<br />',
4075
+					"\t"
4076
+					. ' $passed_in_query_info = <pre>'
4077
+					. print_r($passed_in_query_info, true)
4078
+					. '</pre>'
4079
+					. "\n\t"
4080
+					. ' $query_param_type = '
4081
+					. $query_param_type
4082
+					. "\n\t"
4083
+					. ' $original_query_param = '
4084
+					. $original_query_param
4085
+				)
4086
+			);
4087
+		}
4088
+		// check if it's a custom selection
4089
+		if (
4090
+			$this->_custom_selections instanceof CustomSelects
4091
+			&& in_array($query_param, $this->_custom_selections->columnAliases(), true)
4092
+		) {
4093
+			return;
4094
+		}
4095
+		// check if has a model name at the beginning
4096
+		// and
4097
+		// check if it's a field on a related model
4098
+		if (
4099
+			$this->extractJoinModelFromQueryParams(
4100
+				$passed_in_query_info,
4101
+				$query_param,
4102
+				$original_query_param,
4103
+				$query_param_type
4104
+			)
4105
+		) {
4106
+			return;
4107
+		}
4108
+
4109
+		// ok so $query_param didn't start with a model name
4110
+		// and we previously confirmed it wasn't a logic query param or field on the current model
4111
+		// it's wack, that's what it is
4112
+		throw new EE_Error(
4113
+			sprintf(
4114
+				esc_html__(
4115
+					"There is no model named '%s' related to %s. Query param type is %s and original query param is %s",
4116
+					"event_espresso"
4117
+				),
4118
+				$query_param,
4119
+				get_class($this),
4120
+				$query_param_type,
4121
+				$original_query_param
4122
+			)
4123
+		);
4124
+	}
4125
+
4126
+
4127
+	/**
4128
+	 * Extracts any possible join model information from the provided possible_join_string.
4129
+	 * This method will read the provided $possible_join_string value and determine if there are any possible model
4130
+	 * join
4131
+	 * parts that should be added to the query.
4132
+	 *
4133
+	 * @param EE_Model_Query_Info_Carrier $query_info_carrier
4134
+	 * @param string                      $possible_join_string  Such as Registration.REG_ID, or Registration
4135
+	 * @param null|string                 $original_query_param
4136
+	 * @param string                      $query_parameter_type  The type for the source of the $possible_join_string
4137
+	 *                                                           ('where', 'order_by', 'group_by', 'custom_selects'
4138
+	 *                                                           etc.)
4139
+	 * @return bool  returns true if a join was added and false if not.
4140
+	 * @throws EE_Error
4141
+	 */
4142
+	private function extractJoinModelFromQueryParams(
4143
+		EE_Model_Query_Info_Carrier $query_info_carrier,
4144
+		$possible_join_string,
4145
+		$original_query_param,
4146
+		$query_parameter_type
4147
+	) {
4148
+		foreach ($this->_model_relations as $valid_related_model_name => $relation_obj) {
4149
+			if (strpos($possible_join_string, $valid_related_model_name . ".") === 0) {
4150
+				$this->_add_join_to_model($valid_related_model_name, $query_info_carrier, $original_query_param);
4151
+				$possible_join_string = substr($possible_join_string, strlen($valid_related_model_name . "."));
4152
+				if ($possible_join_string === '') {
4153
+					// nothing left to $query_param
4154
+					// we should actually end in a field name, not a model like this!
4155
+					throw new EE_Error(
4156
+						sprintf(
4157
+							esc_html__(
4158
+								"Query param '%s' (of type %s on model %s) shouldn't end on a period (.) ",
4159
+								"event_espresso"
4160
+							),
4161
+							$possible_join_string,
4162
+							$query_parameter_type,
4163
+							get_class($this),
4164
+							$valid_related_model_name
4165
+						)
4166
+					);
4167
+				}
4168
+				$related_model_obj = $this->get_related_model_obj($valid_related_model_name);
4169
+				$related_model_obj->_extract_related_model_info_from_query_param(
4170
+					$possible_join_string,
4171
+					$query_info_carrier,
4172
+					$query_parameter_type,
4173
+					$original_query_param
4174
+				);
4175
+				return true;
4176
+			}
4177
+			if ($possible_join_string === $valid_related_model_name) {
4178
+				$this->_add_join_to_model(
4179
+					$valid_related_model_name,
4180
+					$query_info_carrier,
4181
+					$original_query_param
4182
+				);
4183
+				return true;
4184
+			}
4185
+		}
4186
+		return false;
4187
+	}
4188
+
4189
+
4190
+	/**
4191
+	 * Extracts related models from Custom Selects and sets up any joins for those related models.
4192
+	 *
4193
+	 * @param EE_Model_Query_Info_Carrier $query_info_carrier
4194
+	 * @throws EE_Error
4195
+	 */
4196
+	private function extractRelatedModelsFromCustomSelects(EE_Model_Query_Info_Carrier $query_info_carrier)
4197
+	{
4198
+		if (
4199
+			$this->_custom_selections instanceof CustomSelects
4200
+			&& (
4201
+				$this->_custom_selections->type() === CustomSelects::TYPE_STRUCTURED
4202
+				|| $this->_custom_selections->type() == CustomSelects::TYPE_COMPLEX
4203
+			)
4204
+		) {
4205
+			$original_selects = $this->_custom_selections->originalSelects();
4206
+			foreach ($original_selects as $alias => $select_configuration) {
4207
+				$this->extractJoinModelFromQueryParams(
4208
+					$query_info_carrier,
4209
+					$select_configuration[0],
4210
+					$select_configuration[0],
4211
+					'custom_selects'
4212
+				);
4213
+			}
4214
+		}
4215
+	}
4216
+
4217
+
4218
+	/**
4219
+	 * Privately used by _extract_related_model_info_from_query_param to add a join to $model_name
4220
+	 * and store it on $passed_in_query_info
4221
+	 *
4222
+	 * @param string                      $model_name
4223
+	 * @param EE_Model_Query_Info_Carrier $passed_in_query_info
4224
+	 * @param string                      $original_query_param used to extract the relation chain between the queried
4225
+	 *                                                          model and $model_name. Eg, if we are querying Event,
4226
+	 *                                                          and are adding a join to 'Payment' with the original
4227
+	 *                                                          query param key
4228
+	 *                                                          'Registration.Transaction.Payment.PAY_amount', we want
4229
+	 *                                                          to extract 'Registration.Transaction.Payment', in case
4230
+	 *                                                          Payment wants to add default query params so that it
4231
+	 *                                                          will know what models to prepend onto its default query
4232
+	 *                                                          params or in case it wants to rename tables (in case
4233
+	 *                                                          there are multiple joins to the same table)
4234
+	 * @return void
4235
+	 * @throws EE_Error
4236
+	 */
4237
+	private function _add_join_to_model(
4238
+		$model_name,
4239
+		EE_Model_Query_Info_Carrier $passed_in_query_info,
4240
+		$original_query_param
4241
+	) {
4242
+		$relation_obj         = $this->related_settings_for($model_name);
4243
+		$model_relation_chain = EE_Model_Parser::extract_model_relation_chain($model_name, $original_query_param);
4244
+		// check if the relation is HABTM, because then we're essentially doing two joins
4245
+		// If so, join first to the JOIN table, and add its data types, and then continue as normal
4246
+		if ($relation_obj instanceof EE_HABTM_Relation) {
4247
+			$join_model_obj = $relation_obj->get_join_model();
4248
+			// replace the model specified with the join model for this relation chain, whi
4249
+			$relation_chain_to_join_model =
4250
+				EE_Model_Parser::replace_model_name_with_join_model_name_in_model_relation_chain(
4251
+					$model_name,
4252
+					$join_model_obj->get_this_model_name(),
4253
+					$model_relation_chain
4254
+				);
4255
+			$passed_in_query_info->merge(
4256
+				new EE_Model_Query_Info_Carrier(
4257
+					[$relation_chain_to_join_model => $join_model_obj->get_this_model_name()],
4258
+					$relation_obj->get_join_to_intermediate_model_statement($relation_chain_to_join_model)
4259
+				)
4260
+			);
4261
+		}
4262
+		// now just join to the other table pointed to by the relation object, and add its data types
4263
+		$passed_in_query_info->merge(
4264
+			new EE_Model_Query_Info_Carrier(
4265
+				[$model_relation_chain => $model_name],
4266
+				$relation_obj->get_join_statement($model_relation_chain)
4267
+			)
4268
+		);
4269
+	}
4270
+
4271
+
4272
+	/**
4273
+	 * Constructs SQL for where clause, like "WHERE Event.ID = 23 AND Transaction.amount > 100" etc.
4274
+	 *
4275
+	 * @param array $where_params @see
4276
+	 *                            https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
4277
+	 * @return string of SQL
4278
+	 * @throws EE_Error
4279
+	 */
4280
+	private function _construct_where_clause($where_params)
4281
+	{
4282
+		$SQL = $this->_construct_condition_clause_recursive($where_params, ' AND ');
4283
+		if ($SQL) {
4284
+			return " WHERE " . $SQL;
4285
+		}
4286
+		return '';
4287
+	}
4288
+
4289
+
4290
+	/**
4291
+	 * Just like the _construct_where_clause, except prepends 'HAVING' instead of 'WHERE',
4292
+	 * and should be passed HAVING parameters, not WHERE parameters
4293
+	 *
4294
+	 * @param array $having_params
4295
+	 * @return string
4296
+	 * @throws EE_Error
4297
+	 */
4298
+	private function _construct_having_clause($having_params)
4299
+	{
4300
+		$SQL = $this->_construct_condition_clause_recursive($having_params, ' AND ');
4301
+		if ($SQL) {
4302
+			return " HAVING " . $SQL;
4303
+		}
4304
+		return '';
4305
+	}
4306
+
4307
+
4308
+	/**
4309
+	 * Used for creating nested WHERE conditions. Eg "WHERE ! (Event.ID = 3 OR ( Event_Meta.meta_key = 'bob' AND
4310
+	 * Event_Meta.meta_value = 'foo'))"
4311
+	 *
4312
+	 * @param array  $where_params @see
4313
+	 *                             https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
4314
+	 * @param string $glue         joins each subclause together. Should really only be " AND " or " OR "...
4315
+	 * @return string of SQL
4316
+	 * @throws EE_Error
4317
+	 */
4318
+	private function _construct_condition_clause_recursive($where_params, $glue = ' AND')
4319
+	{
4320
+		$where_clauses = [];
4321
+		foreach ($where_params as $query_param => $op_and_value_or_sub_condition) {
4322
+			$query_param = $this->_remove_stars_and_anything_after_from_condition_query_param_key($query_param);
4323
+			if (in_array($query_param, $this->_logic_query_param_keys, true)) {
4324
+				switch ($query_param) {
4325
+					case 'not':
4326
+					case 'NOT':
4327
+						$where_clauses[] = "! ("
4328
+										   . $this->_construct_condition_clause_recursive(
4329
+											   $op_and_value_or_sub_condition,
4330
+											   $glue
4331
+										   )
4332
+										   . ")";
4333
+						break;
4334
+					case 'and':
4335
+					case 'AND':
4336
+						$where_clauses[] = " ("
4337
+										   . $this->_construct_condition_clause_recursive(
4338
+											   $op_and_value_or_sub_condition,
4339
+											   ' AND '
4340
+										   )
4341
+										   . ")";
4342
+						break;
4343
+					case 'or':
4344
+					case 'OR':
4345
+						$where_clauses[] = " ("
4346
+										   . $this->_construct_condition_clause_recursive(
4347
+											   $op_and_value_or_sub_condition,
4348
+											   ' OR '
4349
+										   )
4350
+										   . ")";
4351
+						break;
4352
+				}
4353
+			} else {
4354
+				$field_obj = $this->_deduce_field_from_query_param($query_param);
4355
+				// if it's not a normal field, maybe it's a custom selection?
4356
+				if (! $field_obj) {
4357
+					if ($this->_custom_selections instanceof CustomSelects) {
4358
+						$field_obj = $this->_custom_selections->getDataTypeForAlias($query_param);
4359
+					} else {
4360
+						throw new EE_Error(
4361
+							sprintf(
4362
+								esc_html__(
4363
+									"%s is neither a valid model field name, nor a custom selection",
4364
+									"event_espresso"
4365
+								),
4366
+								$query_param
4367
+							)
4368
+						);
4369
+					}
4370
+				}
4371
+				$op_and_value_sql = $this->_construct_op_and_value($op_and_value_or_sub_condition, $field_obj);
4372
+				$where_clauses[]  = $this->_deduce_column_name_from_query_param($query_param) . SP . $op_and_value_sql;
4373
+			}
4374
+		}
4375
+		return $where_clauses ? implode($glue, $where_clauses) : '';
4376
+	}
4377
+
4378
+
4379
+	/**
4380
+	 * Takes the input parameter and extract the table name (alias) and column name
4381
+	 *
4382
+	 * @param string $query_param like Registration.Transaction.TXN_ID, Event.Datetime.start_time, or REG_ID
4383
+	 * @return string table alias and column name for SQL, eg "Transaction.TXN_ID"
4384
+	 * @throws EE_Error
4385
+	 */
4386
+	private function _deduce_column_name_from_query_param($query_param)
4387
+	{
4388
+		$field = $this->_deduce_field_from_query_param($query_param);
4389
+		if ($field) {
4390
+			$table_alias_prefix = EE_Model_Parser::extract_table_alias_model_relation_chain_from_query_param(
4391
+				$field->get_model_name(),
4392
+				$query_param
4393
+			);
4394
+			return $table_alias_prefix . $field->get_qualified_column();
4395
+		}
4396
+		if (
4397
+			$this->_custom_selections instanceof CustomSelects
4398
+			&& in_array($query_param, $this->_custom_selections->columnAliases(), true)
4399
+		) {
4400
+			// maybe it's custom selection item?
4401
+			// if so, just use it as the "column name"
4402
+			return $query_param;
4403
+		}
4404
+		$custom_select_aliases = $this->_custom_selections instanceof CustomSelects
4405
+			? implode(',', $this->_custom_selections->columnAliases())
4406
+			: '';
4407
+		throw new EE_Error(
4408
+			sprintf(
4409
+				esc_html__(
4410
+					"%s is not a valid field on this model, nor a custom selection (%s)",
4411
+					"event_espresso"
4412
+				),
4413
+				$query_param,
4414
+				$custom_select_aliases
4415
+			)
4416
+		);
4417
+	}
4418
+
4419
+
4420
+	/**
4421
+	 * Removes the * and anything after it from the condition query param key. It is useful to add the * to condition
4422
+	 * query param keys (eg, 'OR*', 'EVT_ID') in order for the array keys to still be unique, so that they don't get
4423
+	 * overwritten Takes a string like 'Event.EVT_ID*', 'TXN_total**', 'OR*1st', and 'DTT_reg_start*foobar' to
4424
+	 * 'Event.EVT_ID', 'TXN_total', 'OR', and 'DTT_reg_start', respectively.
4425
+	 *
4426
+	 * @param string $condition_query_param_key
4427
+	 * @return string
4428
+	 */
4429
+	private function _remove_stars_and_anything_after_from_condition_query_param_key($condition_query_param_key)
4430
+	{
4431
+		$pos_of_star = strpos($condition_query_param_key, '*');
4432
+		if ($pos_of_star === false) {
4433
+			return $condition_query_param_key;
4434
+		}
4435
+		$condition_query_param_sans_star = substr($condition_query_param_key, 0, $pos_of_star);
4436
+		return $condition_query_param_sans_star;
4437
+	}
4438
+
4439
+
4440
+	/**
4441
+	 * creates the SQL for the operator and the value in a WHERE clause, eg "< 23" or "LIKE '%monkey%'"
4442
+	 *
4443
+	 * @param mixed      array | string    $op_and_value
4444
+	 * @param EE_Model_Field_Base|string $field_obj . If string, should be one of EEM_Base::_valid_wpdb_data_types
4445
+	 * @return string
4446
+	 * @throws EE_Error
4447
+	 */
4448
+	private function _construct_op_and_value($op_and_value, $field_obj)
4449
+	{
4450
+		if (is_array($op_and_value)) {
4451
+			$operator = isset($op_and_value[0]) ? $this->_prepare_operator_for_sql($op_and_value[0]) : null;
4452
+			if (! $operator) {
4453
+				$php_array_like_string = [];
4454
+				foreach ($op_and_value as $key => $value) {
4455
+					$php_array_like_string[] = "$key=>$value";
4456
+				}
4457
+				throw new EE_Error(
4458
+					sprintf(
4459
+						esc_html__(
4460
+							"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))",
4461
+							"event_espresso"
4462
+						),
4463
+						implode(",", $php_array_like_string)
4464
+					)
4465
+				);
4466
+			}
4467
+			$value = isset($op_and_value[1]) ? $op_and_value[1] : null;
4468
+		} else {
4469
+			$operator = '=';
4470
+			$value    = $op_and_value;
4471
+		}
4472
+		// check to see if the value is actually another field
4473
+		if (is_array($op_and_value) && isset($op_and_value[2]) && $op_and_value[2] == true) {
4474
+			return $operator . SP . $this->_deduce_column_name_from_query_param($value);
4475
+		}
4476
+		if (in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4477
+			// in this case, the value should be an array, or at least a comma-separated list
4478
+			// it will need to handle a little differently
4479
+			$cleaned_value = $this->_construct_in_value($value, $field_obj);
4480
+			// note: $cleaned_value has already been run through $wpdb->prepare()
4481
+			return $operator . SP . $cleaned_value;
4482
+		}
4483
+		if (in_array($operator, $this->valid_between_style_operators()) && is_array($value)) {
4484
+			// the value should be an array with count of two.
4485
+			if (count($value) !== 2) {
4486
+				throw new EE_Error(
4487
+					sprintf(
4488
+						esc_html__(
4489
+							"The '%s' operator must be used with an array of values and there must be exactly TWO values in that array.",
4490
+							'event_espresso'
4491
+						),
4492
+						"BETWEEN"
4493
+					)
4494
+				);
4495
+			}
4496
+			$cleaned_value = $this->_construct_between_value($value, $field_obj);
4497
+			return $operator . SP . $cleaned_value;
4498
+		}
4499
+		if (in_array($operator, $this->valid_null_style_operators())) {
4500
+			if ($value !== null) {
4501
+				throw new EE_Error(
4502
+					sprintf(
4503
+						esc_html__(
4504
+							"You attempted to give a value  (%s) while using a NULL-style operator (%s). That isn't valid",
4505
+							"event_espresso"
4506
+						),
4507
+						$value,
4508
+						$operator
4509
+					)
4510
+				);
4511
+			}
4512
+			return $operator;
4513
+		}
4514
+		if (in_array($operator, $this->valid_like_style_operators()) && ! is_array($value)) {
4515
+			// if the operator is 'LIKE', we want to allow percent signs (%) and not
4516
+			// remove other junk. So just treat it as a string.
4517
+			return $operator . SP . $this->_wpdb_prepare_using_field($value, '%s');
4518
+		}
4519
+		if (! in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4520
+			return $operator . SP . $this->_wpdb_prepare_using_field($value, $field_obj);
4521
+		}
4522
+		if (in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4523
+			throw new EE_Error(
4524
+				sprintf(
4525
+					esc_html__(
4526
+						"Operator '%s' must be used with an array of values, eg 'Registration.REG_ID' => array('%s',array(1,2,3))",
4527
+						'event_espresso'
4528
+					),
4529
+					$operator,
4530
+					$operator
4531
+				)
4532
+			);
4533
+		}
4534
+		if (! in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4535
+			throw new EE_Error(
4536
+				sprintf(
4537
+					esc_html__(
4538
+						"Operator '%s' must be used with a single value, not an array. Eg 'Registration.REG_ID => array('%s',23))",
4539
+						'event_espresso'
4540
+					),
4541
+					$operator,
4542
+					$operator
4543
+				)
4544
+			);
4545
+		}
4546
+		throw new EE_Error(
4547
+			sprintf(
4548
+				esc_html__(
4549
+					"It appears you've provided some totally invalid query parameters. Operator and value were:'%s', which isn't right at all",
4550
+					"event_espresso"
4551
+				),
4552
+				http_build_query($op_and_value)
4553
+			)
4554
+		);
4555
+	}
4556
+
4557
+
4558
+	/**
4559
+	 * Creates the operands to be used in a BETWEEN query, eg "'2014-12-31 20:23:33' AND '2015-01-23 12:32:54'"
4560
+	 *
4561
+	 * @param array                      $values
4562
+	 * @param EE_Model_Field_Base|string $field_obj if string, it should be the datatype to be used when querying, eg
4563
+	 *                                              '%s'
4564
+	 * @return string
4565
+	 * @throws EE_Error
4566
+	 */
4567
+	public function _construct_between_value($values, $field_obj)
4568
+	{
4569
+		$cleaned_values = [];
4570
+		foreach ($values as $value) {
4571
+			$cleaned_values[] = $this->_wpdb_prepare_using_field($value, $field_obj);
4572
+		}
4573
+		return $cleaned_values[0] . " AND " . $cleaned_values[1];
4574
+	}
4575
+
4576
+
4577
+	/**
4578
+	 * Takes an array or a comma-separated list of $values and cleans them
4579
+	 * according to $data_type using $wpdb->prepare, and then makes the list a
4580
+	 * string surrounded by ( and ). Eg, _construct_in_value(array(1,2,3),'%d') would
4581
+	 * return '(1,2,3)'; _construct_in_value("1,2,hack",'%d') would return '(1,2,1)' (assuming
4582
+	 * I'm right that a string, when interpreted as a digit, becomes a 1. It might become a 0)
4583
+	 *
4584
+	 * @param mixed                      $values    array or comma-separated string
4585
+	 * @param EE_Model_Field_Base|string $field_obj if string, it should be a wpdb data type like '%s', or '%d'
4586
+	 * @return string of SQL to follow an 'IN' or 'NOT IN' operator
4587
+	 * @throws EE_Error
4588
+	 */
4589
+	public function _construct_in_value($values, $field_obj)
4590
+	{
4591
+		$prepped = [];
4592
+		// check if the value is a CSV list
4593
+		if (is_string($values)) {
4594
+			// in which case, turn it into an array
4595
+			$values = explode(',', $values);
4596
+		}
4597
+		// make sure we only have one of each value in the list
4598
+		$values = array_unique($values);
4599
+		foreach ($values as $value) {
4600
+			$prepped[] = $this->_wpdb_prepare_using_field($value, $field_obj);
4601
+		}
4602
+		// we would just LOVE to leave $cleaned_values as an empty array, and return the value as "()",
4603
+		// but unfortunately that's invalid SQL. So instead we return a string which we KNOW will evaluate to be the empty set
4604
+		// which is effectively equivalent to returning "()". We don't return "(0)" because that only works for auto-incrementing columns
4605
+		if (empty($prepped)) {
4606
+			$all_fields  = $this->field_settings();
4607
+			$first_field = reset($all_fields);
4608
+			$main_table  = $this->_get_main_table();
4609
+			$prepped[]   = "SELECT {$first_field->get_table_column()} FROM {$main_table->get_table_name()} WHERE FALSE";
4610
+		}
4611
+		return '(' . implode(',', $prepped) . ')';
4612
+	}
4613
+
4614
+
4615
+	/**
4616
+	 * @param mixed                      $value
4617
+	 * @param EE_Model_Field_Base|string $field_obj if string it should be a wpdb data type like '%d'
4618
+	 * @return false|null|string
4619
+	 * @throws EE_Error
4620
+	 */
4621
+	private function _wpdb_prepare_using_field($value, $field_obj)
4622
+	{
4623
+		/** @type WPDB $wpdb */
4624
+		global $wpdb;
4625
+		if ($field_obj instanceof EE_Model_Field_Base) {
4626
+			return $wpdb->prepare(
4627
+				$field_obj->get_wpdb_data_type(),
4628
+				$this->_prepare_value_for_use_in_db($value, $field_obj)
4629
+			);
4630
+		} //$field_obj should really just be a data type
4631
+		if (! in_array($field_obj, $this->_valid_wpdb_data_types)) {
4632
+			throw new EE_Error(
4633
+				sprintf(
4634
+					esc_html__("%s is not a valid wpdb datatype. Valid ones are %s", "event_espresso"),
4635
+					$field_obj,
4636
+					implode(",", $this->_valid_wpdb_data_types)
4637
+				)
4638
+			);
4639
+		}
4640
+		return $wpdb->prepare($field_obj, $value);
4641
+	}
4642
+
4643
+
4644
+	/**
4645
+	 * Takes the input parameter and finds the model field that it indicates.
4646
+	 *
4647
+	 * @param string $query_param_name like Registration.Transaction.TXN_ID, Event.Datetime.start_time, or REG_ID
4648
+	 * @return EE_Model_Field_Base
4649
+	 * @throws EE_Error
4650
+	 */
4651
+	protected function _deduce_field_from_query_param($query_param_name)
4652
+	{
4653
+		// ok, now proceed with deducing which part is the model's name, and which is the field's name
4654
+		// which will help us find the database table and column
4655
+		$query_param_parts = explode(".", $query_param_name);
4656
+		if (empty($query_param_parts)) {
4657
+			throw new EE_Error(
4658
+				sprintf(
4659
+					esc_html__(
4660
+						"_extract_column_name is empty when trying to extract column and table name from %s",
4661
+						'event_espresso'
4662
+					),
4663
+					$query_param_name
4664
+				)
4665
+			);
4666
+		}
4667
+		$number_of_parts       = count($query_param_parts);
4668
+		$last_query_param_part = $query_param_parts[ count($query_param_parts) - 1 ];
4669
+		if ($number_of_parts === 1) {
4670
+			$field_name = $last_query_param_part;
4671
+			$model_obj  = $this;
4672
+		} else {// $number_of_parts >= 2
4673
+			// the last part is the column name, and there are only 2parts. therefore...
4674
+			$field_name = $last_query_param_part;
4675
+			$model_obj  = $this->get_related_model_obj($query_param_parts[ $number_of_parts - 2 ]);
4676
+		}
4677
+		try {
4678
+			return $model_obj->field_settings_for($field_name);
4679
+		} catch (EE_Error $e) {
4680
+			return null;
4681
+		}
4682
+	}
4683
+
4684
+
4685
+	/**
4686
+	 * Given a field's name (ie, a key in $this->field_settings()), uses the EE_Model_Field object to get the table's
4687
+	 * alias and column which corresponds to it
4688
+	 *
4689
+	 * @param string $field_name
4690
+	 * @return string
4691
+	 * @throws EE_Error
4692
+	 */
4693
+	public function _get_qualified_column_for_field($field_name)
4694
+	{
4695
+		$all_fields = $this->field_settings();
4696
+		$field      = isset($all_fields[ $field_name ]) ? $all_fields[ $field_name ] : false;
4697
+		if ($field) {
4698
+			return $field->get_qualified_column();
4699
+		}
4700
+		throw new EE_Error(
4701
+			sprintf(
4702
+				esc_html__(
4703
+					"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.",
4704
+					'event_espresso'
4705
+				),
4706
+				$field_name,
4707
+				get_class($this)
4708
+			)
4709
+		);
4710
+	}
4711
+
4712
+
4713
+	/**
4714
+	 * similar to \EEM_Base::_get_qualified_column_for_field() but returns an array with data for ALL fields.
4715
+	 * Example usage:
4716
+	 * EEM_Ticket::instance()->get_all_wpdb_results(
4717
+	 *      array(),
4718
+	 *      ARRAY_A,
4719
+	 *      EEM_Ticket::instance()->get_qualified_columns_for_all_fields()
4720
+	 *  );
4721
+	 * is equivalent to
4722
+	 *  EEM_Ticket::instance()->get_all_wpdb_results( array(), ARRAY_A, '*' );
4723
+	 * and
4724
+	 *  EEM_Event::instance()->get_all_wpdb_results(
4725
+	 *      array(
4726
+	 *          array(
4727
+	 *              'Datetime.Ticket.TKT_ID' => array( '<', 100 ),
4728
+	 *          ),
4729
+	 *          ARRAY_A,
4730
+	 *          implode(
4731
+	 *              ', ',
4732
+	 *              array_merge(
4733
+	 *                  EEM_Event::instance()->get_qualified_columns_for_all_fields( '', false ),
4734
+	 *                  EEM_Ticket::instance()->get_qualified_columns_for_all_fields( 'Datetime', false )
4735
+	 *              )
4736
+	 *          )
4737
+	 *      )
4738
+	 *  );
4739
+	 * selects rows from the database, selecting all the event and ticket columns, where the ticket ID is below 100
4740
+	 *
4741
+	 * @param string $model_relation_chain        the chain of models used to join between the model you want to query
4742
+	 *                                            and the one whose fields you are selecting for example: when querying
4743
+	 *                                            tickets model and selecting fields from the tickets model you would
4744
+	 *                                            leave this parameter empty, because no models are needed to join
4745
+	 *                                            between the queried model and the selected one. Likewise when
4746
+	 *                                            querying the datetime model and selecting fields from the tickets
4747
+	 *                                            model, it would also be left empty, because there is a direct
4748
+	 *                                            relation from datetimes to tickets, so no model is needed to join
4749
+	 *                                            them together. However, when querying from the event model and
4750
+	 *                                            selecting fields from the ticket model, you should provide the string
4751
+	 *                                            'Datetime', indicating that the event model must first join to the
4752
+	 *                                            datetime model in order to find its relation to ticket model.
4753
+	 *                                            Also, when querying from the venue model and selecting fields from
4754
+	 *                                            the ticket model, you should provide the string 'Event.Datetime',
4755
+	 *                                            indicating you need to join the venue model to the event model,
4756
+	 *                                            to the datetime model, in order to find its relation to the ticket
4757
+	 *                                            model. This string is used to deduce the prefix that gets added onto
4758
+	 *                                            the models' tables qualified columns
4759
+	 * @param bool   $return_string               if true, will return a string with qualified column names separated
4760
+	 *                                            by ', ' if false, will simply return a numerically indexed array of
4761
+	 *                                            qualified column names
4762
+	 * @return array|string
4763
+	 */
4764
+	public function get_qualified_columns_for_all_fields($model_relation_chain = '', $return_string = true)
4765
+	{
4766
+		$table_prefix      = str_replace('.', '__', $model_relation_chain) . (empty($model_relation_chain) ? '' : '__');
4767
+		$qualified_columns = [];
4768
+		foreach ($this->field_settings() as $field_name => $field) {
4769
+			$qualified_columns[] = $table_prefix . $field->get_qualified_column();
4770
+		}
4771
+		return $return_string ? implode(', ', $qualified_columns) : $qualified_columns;
4772
+	}
4773
+
4774
+
4775
+	/**
4776
+	 * constructs the select use on special limit joins
4777
+	 * NOTE: for now this has only been tested and will work when the  table alias is for the PRIMARY table. Although
4778
+	 * its setup so the select query will be setup on and just doing the special select join off of the primary table
4779
+	 * (as that is typically where the limits would be set).
4780
+	 *
4781
+	 * @param string       $table_alias The table the select is being built for
4782
+	 * @param mixed|string $limit       The limit for this select
4783
+	 * @return string                The final select join element for the query.
4784
+	 * @throws EE_Error
4785
+	 * @throws EE_Error
4786
+	 */
4787
+	public function _construct_limit_join_select($table_alias, $limit)
4788
+	{
4789
+		$SQL = '';
4790
+		foreach ($this->_tables as $table_obj) {
4791
+			if ($table_obj instanceof EE_Primary_Table) {
4792
+				$SQL .= $table_alias === $table_obj->get_table_alias()
4793
+					? $table_obj->get_select_join_limit($limit)
4794
+					: SP . $table_obj->get_table_name() . " AS " . $table_obj->get_table_alias() . SP;
4795
+			} elseif ($table_obj instanceof EE_Secondary_Table) {
4796
+				$SQL .= $table_alias === $table_obj->get_table_alias()
4797
+					? $table_obj->get_select_join_limit_join($limit)
4798
+					: SP . $table_obj->get_join_sql($table_alias) . SP;
4799
+			}
4800
+		}
4801
+		return $SQL;
4802
+	}
4803
+
4804
+
4805
+	/**
4806
+	 * Constructs the internal join if there are multiple tables, or simply the table's name and alias
4807
+	 * Eg "wp_post AS Event" or "wp_post AS Event INNER JOIN wp_postmeta Event_Meta ON Event.ID = Event_Meta.post_id"
4808
+	 *
4809
+	 * @return string SQL
4810
+	 * @throws EE_Error
4811
+	 */
4812
+	public function _construct_internal_join()
4813
+	{
4814
+		$SQL = $this->_get_main_table()->get_table_sql();
4815
+		$SQL .= $this->_construct_internal_join_to_table_with_alias($this->_get_main_table()->get_table_alias());
4816
+		return $SQL;
4817
+	}
4818
+
4819
+
4820
+	/**
4821
+	 * Constructs the SQL for joining all the tables on this model.
4822
+	 * Normally $alias should be the primary table's alias, but in cases where
4823
+	 * we have already joined to a secondary table (eg, the secondary table has a foreign key and is joined before the
4824
+	 * primary table) then we should provide that secondary table's alias. Eg, with $alias being the primary table's
4825
+	 * alias, this will construct SQL like:
4826
+	 * " INNER JOIN wp_esp_secondary_table AS Secondary_Table ON Primary_Table.pk = Secondary_Table.fk".
4827
+	 * With $alias being a secondary table's alias, this will construct SQL like:
4828
+	 * " INNER JOIN wp_esp_primary_table AS Primary_Table ON Primary_Table.pk = Secondary_Table.fk".
4829
+	 *
4830
+	 * @param string $alias_prefixed table alias to join to (this table should already be in the FROM SQL clause)
4831
+	 * @return string
4832
+	 * @throws EE_Error
4833
+	 * @throws EE_Error
4834
+	 */
4835
+	public function _construct_internal_join_to_table_with_alias($alias_prefixed)
4836
+	{
4837
+		$SQL               = '';
4838
+		$alias_sans_prefix = EE_Model_Parser::remove_table_alias_model_relation_chain_prefix($alias_prefixed);
4839
+		foreach ($this->_tables as $table_obj) {
4840
+			if ($table_obj instanceof EE_Secondary_Table) {// table is secondary table
4841
+				if ($alias_sans_prefix === $table_obj->get_table_alias()) {
4842
+					// so we're joining to this table, meaning the table is already in
4843
+					// the FROM statement, BUT the primary table isn't. So we want
4844
+					// to add the inverse join sql
4845
+					$SQL .= $table_obj->get_inverse_join_sql($alias_prefixed);
4846
+				} else {
4847
+					// just add a regular JOIN to this table from the primary table
4848
+					$SQL .= $table_obj->get_join_sql($alias_prefixed);
4849
+				}
4850
+			}//if it's a primary table, dont add any SQL. it should already be in the FROM statement
4851
+		}
4852
+		return $SQL;
4853
+	}
4854
+
4855
+
4856
+	/**
4857
+	 * Gets an array for storing all the data types on the next-to-be-executed-query.
4858
+	 * This should be a growing array of keys being table-columns (eg 'EVT_ID' and 'Event.EVT_ID'), and values being
4859
+	 * their data type (eg, '%s', '%d', etc)
4860
+	 *
4861
+	 * @return array
4862
+	 */
4863
+	public function _get_data_types()
4864
+	{
4865
+		$data_types = [];
4866
+		foreach ($this->field_settings() as $field_obj) {
4867
+			// $data_types[$field_obj->get_table_column()] = $field_obj->get_wpdb_data_type();
4868
+			/** @var $field_obj EE_Model_Field_Base */
4869
+			$data_types[ $field_obj->get_qualified_column() ] = $field_obj->get_wpdb_data_type();
4870
+		}
4871
+		return $data_types;
4872
+	}
4873
+
4874
+
4875
+	/**
4876
+	 * Gets the model object given the relation's name / model's name (eg, 'Event', 'Registration',etc. Always singular)
4877
+	 *
4878
+	 * @param string $model_name
4879
+	 * @return EEM_Base
4880
+	 * @throws EE_Error
4881
+	 */
4882
+	public function get_related_model_obj($model_name)
4883
+	{
4884
+		$model_classname = "EEM_" . $model_name;
4885
+		if (! class_exists($model_classname)) {
4886
+			throw new EE_Error(
4887
+				sprintf(
4888
+					esc_html__(
4889
+						"You specified a related model named %s in your query. No such model exists, if it did, it would have the classname %s",
4890
+						'event_espresso'
4891
+					),
4892
+					$model_name,
4893
+					$model_classname
4894
+				)
4895
+			);
4896
+		}
4897
+		return call_user_func($model_classname . "::instance");
4898
+	}
4899
+
4900
+
4901
+	/**
4902
+	 * Returns the array of EE_ModelRelations for this model.
4903
+	 *
4904
+	 * @return EE_Model_Relation_Base[]
4905
+	 */
4906
+	public function relation_settings()
4907
+	{
4908
+		return $this->_model_relations;
4909
+	}
4910
+
4911
+
4912
+	/**
4913
+	 * Gets all related models that this model BELONGS TO. Handy to know sometimes
4914
+	 * because without THOSE models, this model probably doesn't have much purpose.
4915
+	 * (Eg, without an event, datetimes have little purpose.)
4916
+	 *
4917
+	 * @return EE_Belongs_To_Relation[]
4918
+	 */
4919
+	public function belongs_to_relations()
4920
+	{
4921
+		$belongs_to_relations = [];
4922
+		foreach ($this->relation_settings() as $model_name => $relation_obj) {
4923
+			if ($relation_obj instanceof EE_Belongs_To_Relation) {
4924
+				$belongs_to_relations[ $model_name ] = $relation_obj;
4925
+			}
4926
+		}
4927
+		return $belongs_to_relations;
4928
+	}
4929
+
4930
+
4931
+	/**
4932
+	 * Returns the specified EE_Model_Relation, or throws an exception
4933
+	 *
4934
+	 * @param string $relation_name name of relation, key in $this->_relatedModels
4935
+	 * @return EE_Model_Relation_Base
4936
+	 * @throws EE_Error
4937
+	 */
4938
+	public function related_settings_for($relation_name)
4939
+	{
4940
+		$relatedModels = $this->relation_settings();
4941
+		if (! array_key_exists($relation_name, $relatedModels)) {
4942
+			throw new EE_Error(
4943
+				sprintf(
4944
+					esc_html__(
4945
+						'Cannot get %s related to %s. There is no model relation of that type. There is, however, %s...',
4946
+						'event_espresso'
4947
+					),
4948
+					$relation_name,
4949
+					$this->_get_class_name(),
4950
+					implode(', ', array_keys($relatedModels))
4951
+				)
4952
+			);
4953
+		}
4954
+		return $relatedModels[ $relation_name ];
4955
+	}
4956
+
4957
+
4958
+	/**
4959
+	 * A convenience method for getting a specific field's settings, instead of getting all field settings for all
4960
+	 * fields
4961
+	 *
4962
+	 * @param string  $fieldName
4963
+	 * @param boolean $include_db_only_fields
4964
+	 * @return EE_Model_Field_Base
4965
+	 * @throws EE_Error
4966
+	 */
4967
+	public function field_settings_for($fieldName, $include_db_only_fields = true)
4968
+	{
4969
+		$fieldSettings = $this->field_settings($include_db_only_fields);
4970
+		if (! array_key_exists($fieldName, $fieldSettings)) {
4971
+			throw new EE_Error(
4972
+				sprintf(
4973
+					esc_html__("There is no field/column '%s' on '%s'", 'event_espresso'),
4974
+					$fieldName,
4975
+					get_class($this)
4976
+				)
4977
+			);
4978
+		}
4979
+		return $fieldSettings[ $fieldName ];
4980
+	}
4981
+
4982
+
4983
+	/**
4984
+	 * Checks if this field exists on this model
4985
+	 *
4986
+	 * @param string $fieldName a key in the model's _field_settings array
4987
+	 * @return boolean
4988
+	 */
4989
+	public function has_field($fieldName)
4990
+	{
4991
+		$fieldSettings = $this->field_settings(true);
4992
+		if (isset($fieldSettings[ $fieldName ])) {
4993
+			return true;
4994
+		}
4995
+		return false;
4996
+	}
4997
+
4998
+
4999
+	/**
5000
+	 * Returns whether or not this model has a relation to the specified model
5001
+	 *
5002
+	 * @param string $relation_name possibly one of the keys in the relation_settings array
5003
+	 * @return boolean
5004
+	 */
5005
+	public function has_relation($relation_name)
5006
+	{
5007
+		$relations = $this->relation_settings();
5008
+		if (isset($relations[ $relation_name ])) {
5009
+			return true;
5010
+		}
5011
+		return false;
5012
+	}
5013
+
5014
+
5015
+	/**
5016
+	 * gets the field object of type 'primary_key' from the fieldsSettings attribute.
5017
+	 * Eg, on EE_Answer that would be ANS_ID field object
5018
+	 *
5019
+	 * @param $field_obj
5020
+	 * @return boolean
5021
+	 */
5022
+	public function is_primary_key_field($field_obj)
5023
+	{
5024
+		return $field_obj instanceof EE_Primary_Key_Field_Base ? true : false;
5025
+	}
5026
+
5027
+
5028
+	/**
5029
+	 * gets the field object of type 'primary_key' from the fieldsSettings attribute.
5030
+	 * Eg, on EE_Answer that would be ANS_ID field object
5031
+	 *
5032
+	 * @return EE_Primary_Key_Field_Base
5033
+	 * @throws EE_Error
5034
+	 */
5035
+	public function get_primary_key_field()
5036
+	{
5037
+		if ($this->_primary_key_field === null) {
5038
+			foreach ($this->field_settings(true) as $field_obj) {
5039
+				if ($this->is_primary_key_field($field_obj)) {
5040
+					$this->_primary_key_field = $field_obj;
5041
+					break;
5042
+				}
5043
+			}
5044
+			if (! $this->_primary_key_field instanceof EE_Primary_Key_Field_Base) {
5045
+				throw new EE_Error(
5046
+					sprintf(
5047
+						esc_html__("There is no Primary Key defined on model %s", 'event_espresso'),
5048
+						get_class($this)
5049
+					)
5050
+				);
5051
+			}
5052
+		}
5053
+		return $this->_primary_key_field;
5054
+	}
5055
+
5056
+
5057
+	/**
5058
+	 * Returns whether or not not there is a primary key on this model.
5059
+	 * Internally does some caching.
5060
+	 *
5061
+	 * @return boolean
5062
+	 */
5063
+	public function has_primary_key_field()
5064
+	{
5065
+		if ($this->_has_primary_key_field === null) {
5066
+			try {
5067
+				$this->get_primary_key_field();
5068
+				$this->_has_primary_key_field = true;
5069
+			} catch (EE_Error $e) {
5070
+				$this->_has_primary_key_field = false;
5071
+			}
5072
+		}
5073
+		return $this->_has_primary_key_field;
5074
+	}
5075
+
5076
+
5077
+	/**
5078
+	 * Finds the first field of type $field_class_name.
5079
+	 *
5080
+	 * @param string $field_class_name class name of field that you want to find. Eg, EE_Datetime_Field,
5081
+	 *                                 EE_Foreign_Key_Field, etc
5082
+	 * @return EE_Model_Field_Base or null if none is found
5083
+	 */
5084
+	public function get_a_field_of_type($field_class_name)
5085
+	{
5086
+		foreach ($this->field_settings() as $field) {
5087
+			if ($field instanceof $field_class_name) {
5088
+				return $field;
5089
+			}
5090
+		}
5091
+		return null;
5092
+	}
5093
+
5094
+
5095
+	/**
5096
+	 * Gets a foreign key field pointing to model.
5097
+	 *
5098
+	 * @param string $model_name eg Event, Registration, not EEM_Event
5099
+	 * @return EE_Foreign_Key_Field_Base
5100
+	 * @throws EE_Error
5101
+	 */
5102
+	public function get_foreign_key_to($model_name)
5103
+	{
5104
+		if (! isset($this->_cache_foreign_key_to_fields[ $model_name ])) {
5105
+			foreach ($this->field_settings() as $field) {
5106
+				if (
5107
+					$field instanceof EE_Foreign_Key_Field_Base
5108
+					&& in_array($model_name, $field->get_model_names_pointed_to())
5109
+				) {
5110
+					$this->_cache_foreign_key_to_fields[ $model_name ] = $field;
5111
+					break;
5112
+				}
5113
+			}
5114
+			if (! isset($this->_cache_foreign_key_to_fields[ $model_name ])) {
5115
+				throw new EE_Error(
5116
+					sprintf(
5117
+						esc_html__(
5118
+							"There is no foreign key field pointing to model %s on model %s",
5119
+							'event_espresso'
5120
+						),
5121
+						$model_name,
5122
+						get_class($this)
5123
+					)
5124
+				);
5125
+			}
5126
+		}
5127
+		return $this->_cache_foreign_key_to_fields[ $model_name ];
5128
+	}
5129
+
5130
+
5131
+	/**
5132
+	 * Gets the table name (including $wpdb->prefix) for the table alias
5133
+	 *
5134
+	 * @param string $table_alias eg Event, Event_Meta, Registration, Transaction, but maybe
5135
+	 *                            a table alias with a model chain prefix, like 'Venue__Event_Venue___Event_Meta'.
5136
+	 *                            Either one works
5137
+	 * @return string
5138
+	 */
5139
+	public function get_table_for_alias($table_alias)
5140
+	{
5141
+		$table_alias_sans_model_relation_chain_prefix =
5142
+			EE_Model_Parser::remove_table_alias_model_relation_chain_prefix($table_alias);
5143
+		return $this->_tables[ $table_alias_sans_model_relation_chain_prefix ]->get_table_name();
5144
+	}
5145
+
5146
+
5147
+	/**
5148
+	 * Returns a flat array of all field son this model, instead of organizing them
5149
+	 * by table_alias as they are in the constructor.
5150
+	 *
5151
+	 * @param bool $include_db_only_fields flag indicating whether or not to include the db-only fields
5152
+	 * @return EE_Model_Field_Base[] where the keys are the field's name
5153
+	 */
5154
+	public function field_settings($include_db_only_fields = false)
5155
+	{
5156
+		if ($include_db_only_fields) {
5157
+			if ($this->_cached_fields === null) {
5158
+				$this->_cached_fields = [];
5159
+				foreach ($this->_fields as $fields_corresponding_to_table) {
5160
+					foreach ($fields_corresponding_to_table as $field_name => $field_obj) {
5161
+						$this->_cached_fields[ $field_name ] = $field_obj;
5162
+					}
5163
+				}
5164
+			}
5165
+			return $this->_cached_fields;
5166
+		}
5167
+		if ($this->_cached_fields_non_db_only === null) {
5168
+			$this->_cached_fields_non_db_only = [];
5169
+			foreach ($this->_fields as $fields_corresponding_to_table) {
5170
+				foreach ($fields_corresponding_to_table as $field_name => $field_obj) {
5171
+					/** @var $field_obj EE_Model_Field_Base */
5172
+					if (! $field_obj->is_db_only_field()) {
5173
+						$this->_cached_fields_non_db_only[ $field_name ] = $field_obj;
5174
+					}
5175
+				}
5176
+			}
5177
+		}
5178
+		return $this->_cached_fields_non_db_only;
5179
+	}
5180
+
5181
+
5182
+	/**
5183
+	 *        cycle though array of attendees and create objects out of each item
5184
+	 *
5185
+	 * @access        private
5186
+	 * @param array $rows        of results of $wpdb->get_results($query,ARRAY_A)
5187
+	 * @return EE_Base_Class[] array keys are primary keys (if there is a primary key on the model. if not,
5188
+	 *                           numerically indexed)
5189
+	 * @throws EE_Error
5190
+	 * @throws ReflectionException
5191
+	 */
5192
+	protected function _create_objects($rows = [])
5193
+	{
5194
+		$array_of_objects = [];
5195
+		if (empty($rows)) {
5196
+			return [];
5197
+		}
5198
+		$count_if_model_has_no_primary_key = 0;
5199
+		$has_primary_key                   = $this->has_primary_key_field();
5200
+		$primary_key_field                 = $has_primary_key ? $this->get_primary_key_field() : null;
5201
+		foreach ((array) $rows as $row) {
5202
+			if (empty($row)) {
5203
+				// wp did its weird thing where it returns an array like array(0=>null), which is totally not helpful...
5204
+				return [];
5205
+			}
5206
+			// check if we've already set this object in the results array,
5207
+			// in which case there's no need to process it further (again)
5208
+			if ($has_primary_key) {
5209
+				$table_pk_value = $this->_get_column_value_with_table_alias_or_not(
5210
+					$row,
5211
+					$primary_key_field->get_qualified_column(),
5212
+					$primary_key_field->get_table_column()
5213
+				);
5214
+				if ($table_pk_value && isset($array_of_objects[ $table_pk_value ])) {
5215
+					continue;
5216
+				}
5217
+			}
5218
+			$classInstance = $this->instantiate_class_from_array_or_object($row);
5219
+			if (! $classInstance) {
5220
+				throw new EE_Error(
5221
+					sprintf(
5222
+						esc_html__('Could not create instance of class %s from row %s', 'event_espresso'),
5223
+						$this->get_this_model_name(),
5224
+						http_build_query($row)
5225
+					)
5226
+				);
5227
+			}
5228
+			// set the timezone on the instantiated objects
5229
+			$classInstance->set_timezone($this->_timezone);
5230
+			// make sure if there is any timezone setting present that we set the timezone for the object
5231
+			$key                      = $has_primary_key ? $classInstance->ID() : $count_if_model_has_no_primary_key++;
5232
+			$array_of_objects[ $key ] = $classInstance;
5233
+			// also, for all the relations of type BelongsTo, see if we can cache
5234
+			// those related models
5235
+			// (we could do this for other relations too, but if there are conditions
5236
+			// that filtered out some fo the results, then we'd be caching an incomplete set
5237
+			// so it requires a little more thought than just caching them immediately...)
5238
+			foreach ($this->_model_relations as $modelName => $relation_obj) {
5239
+				if ($relation_obj instanceof EE_Belongs_To_Relation) {
5240
+					// check if this model's INFO is present. If so, cache it on the model
5241
+					$other_model           = $relation_obj->get_other_model();
5242
+					$other_model_obj_maybe = $other_model->instantiate_class_from_array_or_object($row);
5243
+					// if we managed to make a model object from the results, cache it on the main model object
5244
+					if ($other_model_obj_maybe) {
5245
+						// set timezone on these other model objects if they are present
5246
+						$other_model_obj_maybe->set_timezone($this->_timezone);
5247
+						$classInstance->cache($modelName, $other_model_obj_maybe);
5248
+					}
5249
+				}
5250
+			}
5251
+			// also, if this was a custom select query, let's see if there are any results for the custom select fields
5252
+			// and add them to the object as well.  We'll convert according to the set data_type if there's any set for
5253
+			// the field in the CustomSelects object
5254
+			if ($this->_custom_selections instanceof CustomSelects) {
5255
+				$classInstance->setCustomSelectsValues(
5256
+					$this->getValuesForCustomSelectAliasesFromResults($row)
5257
+				);
5258
+			}
5259
+		}
5260
+		return $array_of_objects;
5261
+	}
5262
+
5263
+
5264
+	/**
5265
+	 * This will parse a given row of results from the db and see if any keys in the results match an alias within the
5266
+	 * current CustomSelects object. This will be used to build an array of values indexed by those keys.
5267
+	 *
5268
+	 * @param array $db_results_row
5269
+	 * @return array
5270
+	 */
5271
+	protected function getValuesForCustomSelectAliasesFromResults(array $db_results_row)
5272
+	{
5273
+		$results = [];
5274
+		if ($this->_custom_selections instanceof CustomSelects) {
5275
+			foreach ($this->_custom_selections->columnAliases() as $alias) {
5276
+				if (isset($db_results_row[ $alias ])) {
5277
+					$results[ $alias ] = $this->convertValueToDataType(
5278
+						$db_results_row[ $alias ],
5279
+						$this->_custom_selections->getDataTypeForAlias($alias)
5280
+					);
5281
+				}
5282
+			}
5283
+		}
5284
+		return $results;
5285
+	}
5286
+
5287
+
5288
+	/**
5289
+	 * This will set the value for the given alias
5290
+	 *
5291
+	 * @param string $value
5292
+	 * @param string $datatype (one of %d, %s, %f)
5293
+	 * @return int|string|float (int for %d, string for %s, float for %f)
5294
+	 */
5295
+	protected function convertValueToDataType($value, $datatype)
5296
+	{
5297
+		switch ($datatype) {
5298
+			case '%f':
5299
+				return (float) $value;
5300
+			case '%d':
5301
+				return (int) $value;
5302
+			default:
5303
+				return (string) $value;
5304
+		}
5305
+	}
5306
+
5307
+
5308
+	/**
5309
+	 * The purpose of this method is to allow us to create a model object that is not in the db that holds default
5310
+	 * values. A typical example of where this is used is when creating a new item and the initial load of a form.  We
5311
+	 * dont' necessarily want to test for if the object is present but just assume it is BUT load the defaults from the
5312
+	 * object (as set in the model_field!).
5313
+	 *
5314
+	 * @return EE_Base_Class single EE_Base_Class object with default values for the properties.
5315
+	 * @throws EE_Error
5316
+	 * @throws ReflectionException
5317
+	 */
5318
+	public function create_default_object()
5319
+	{
5320
+		$this_model_fields_and_values = [];
5321
+		// setup the row using default values;
5322
+		foreach ($this->field_settings() as $field_name => $field_obj) {
5323
+			$this_model_fields_and_values[ $field_name ] = $field_obj->get_default_value();
5324
+		}
5325
+		$className     = $this->_get_class_name();
5326
+		return EE_Registry::instance()->load_class($className, [$this_model_fields_and_values], false, false);
5327
+	}
5328
+
5329
+
5330
+	/**
5331
+	 * @param mixed $cols_n_values either an array of where each key is the name of a field, and the value is its value
5332
+	 *                             or an stdClass where each property is the name of a column,
5333
+	 * @return EE_Base_Class
5334
+	 * @throws EE_Error
5335
+	 * @throws ReflectionException
5336
+	 */
5337
+	public function instantiate_class_from_array_or_object($cols_n_values)
5338
+	{
5339
+		if (! is_array($cols_n_values) && is_object($cols_n_values)) {
5340
+			$cols_n_values = get_object_vars($cols_n_values);
5341
+		}
5342
+		$primary_key = null;
5343
+		// make sure the array only has keys that are fields/columns on this model
5344
+		$this_model_fields_n_values = $this->_deduce_fields_n_values_from_cols_n_values($cols_n_values);
5345
+		if ($this->has_primary_key_field() && isset($this_model_fields_n_values[ $this->primary_key_name() ])) {
5346
+			$primary_key = $this_model_fields_n_values[ $this->primary_key_name() ];
5347
+		}
5348
+		$className = $this->_get_class_name();
5349
+		// check we actually found results that we can use to build our model object
5350
+		// if not, return null
5351
+		if ($this->has_primary_key_field()) {
5352
+			if (empty($this_model_fields_n_values[ $this->primary_key_name() ])) {
5353
+				return null;
5354
+			}
5355
+		} elseif ($this->unique_indexes()) {
5356
+			$first_column = reset($this_model_fields_n_values);
5357
+			if (empty($first_column)) {
5358
+				return null;
5359
+			}
5360
+		}
5361
+		// if there is no primary key or the object doesn't already exist in the entity map, then create a new instance
5362
+		if ($primary_key) {
5363
+			$classInstance = $this->get_from_entity_map($primary_key);
5364
+			if (! $classInstance) {
5365
+				$classInstance = EE_Registry::instance()
5366
+											->load_class(
5367
+												$className,
5368
+												[$this_model_fields_n_values, $this->_timezone],
5369
+												true,
5370
+												false
5371
+											);
5372
+				// add this new object to the entity map
5373
+				$classInstance = $this->add_to_entity_map($classInstance);
5374
+			}
5375
+		} else {
5376
+			$classInstance = EE_Registry::instance()
5377
+										->load_class(
5378
+											$className,
5379
+											[$this_model_fields_n_values, $this->_timezone],
5380
+											true,
5381
+											false
5382
+										);
5383
+		}
5384
+		return $classInstance;
5385
+	}
5386
+
5387
+
5388
+	/**
5389
+	 * Gets the model object from the  entity map if it exists
5390
+	 *
5391
+	 * @param int|string $id the ID of the model object
5392
+	 * @return EE_Base_Class
5393
+	 */
5394
+	public function get_from_entity_map($id)
5395
+	{
5396
+		return isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])
5397
+			? $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ] : null;
5398
+	}
5399
+
5400
+
5401
+	/**
5402
+	 * add_to_entity_map
5403
+	 * Adds the object to the model's entity mappings
5404
+	 *        Effectively tells the models "Hey, this model object is the most up-to-date representation of the data,
5405
+	 *        and for the remainder of the request, it's even more up-to-date than what's in the database.
5406
+	 *        So, if the database doesn't agree with what's in the entity mapper, ignore the database"
5407
+	 *        If the database gets updated directly and you want the entity mapper to reflect that change,
5408
+	 *        then this method should be called immediately after the update query
5409
+	 * Note: The map is indexed by whatever the current blog id is set (via EEM_Base::$_model_query_blog_id).  This is
5410
+	 * so on multisite, the entity map is specific to the query being done for a specific site.
5411
+	 *
5412
+	 * @param EE_Base_Class $object
5413
+	 * @return EE_Base_Class
5414
+	 * @throws EE_Error
5415
+	 * @throws ReflectionException
5416
+	 */
5417
+	public function add_to_entity_map(EE_Base_Class $object)
5418
+	{
5419
+		$className = $this->_get_class_name();
5420
+		if (! $object instanceof $className) {
5421
+			throw new EE_Error(
5422
+				sprintf(
5423
+					esc_html__("You tried adding a %s to a mapping of %ss", "event_espresso"),
5424
+					is_object($object) ? get_class($object) : $object,
5425
+					$className
5426
+				)
5427
+			);
5428
+		}
5429
+		/** @var $object EE_Base_Class */
5430
+		if (! $object->ID()) {
5431
+			throw new EE_Error(
5432
+				sprintf(
5433
+					esc_html__(
5434
+						"You tried storing a model object with NO ID in the %s entity mapper.",
5435
+						"event_espresso"
5436
+					),
5437
+					get_class($this)
5438
+				)
5439
+			);
5440
+		}
5441
+		// double check it's not already there
5442
+		$classInstance = $this->get_from_entity_map($object->ID());
5443
+		if ($classInstance) {
5444
+			return $classInstance;
5445
+		}
5446
+		$this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $object->ID() ] = $object;
5447
+		return $object;
5448
+	}
5449
+
5450
+
5451
+	/**
5452
+	 * if a valid identifier is provided, then that entity is unset from the entity map,
5453
+	 * if no identifier is provided, then the entire entity map is emptied
5454
+	 *
5455
+	 * @param int|string $id the ID of the model object
5456
+	 * @return boolean
5457
+	 */
5458
+	public function clear_entity_map($id = null)
5459
+	{
5460
+		if (empty($id)) {
5461
+			$this->_entity_map[ EEM_Base::$_model_query_blog_id ] = [];
5462
+			return true;
5463
+		}
5464
+		if (isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])) {
5465
+			unset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ]);
5466
+			return true;
5467
+		}
5468
+		return false;
5469
+	}
5470
+
5471
+
5472
+	/**
5473
+	 * Public wrapper for _deduce_fields_n_values_from_cols_n_values.
5474
+	 * Given an array where keys are column (or column alias) names and values,
5475
+	 * returns an array of their corresponding field names and database values
5476
+	 *
5477
+	 * @param array $cols_n_values
5478
+	 * @return array
5479
+	 * @throws EE_Error
5480
+	 * @throws ReflectionException
5481
+	 */
5482
+	public function deduce_fields_n_values_from_cols_n_values(array $cols_n_values): array
5483
+	{
5484
+		return $this->_deduce_fields_n_values_from_cols_n_values($cols_n_values);
5485
+	}
5486
+
5487
+
5488
+	/**
5489
+	 * _deduce_fields_n_values_from_cols_n_values
5490
+	 * Given an array where keys are column (or column alias) names and values,
5491
+	 * returns an array of their corresponding field names and database values
5492
+	 *
5493
+	 * @param array|stdClass $cols_n_values
5494
+	 * @return array
5495
+	 * @throws EE_Error
5496
+	 * @throws ReflectionException
5497
+	 */
5498
+	protected function _deduce_fields_n_values_from_cols_n_values($cols_n_values): array
5499
+	{
5500
+		if ($cols_n_values instanceof stdClass) {
5501
+			$cols_n_values = get_object_vars($cols_n_values);
5502
+		}
5503
+		$this_model_fields_n_values = [];
5504
+		foreach ($this->get_tables() as $table_alias => $table_obj) {
5505
+			$table_pk_value = $this->_get_column_value_with_table_alias_or_not(
5506
+				$cols_n_values,
5507
+				$table_obj->get_fully_qualified_pk_column(),
5508
+				$table_obj->get_pk_column()
5509
+			);
5510
+			// there is a primary key on this table and its not set. Use defaults for all its columns
5511
+			if ($table_pk_value === null && $table_obj->get_pk_column()) {
5512
+				foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5513
+					if (! $field_obj->is_db_only_field()) {
5514
+						// prepare field as if its coming from db
5515
+						$prepared_value                            =
5516
+							$field_obj->prepare_for_set($field_obj->get_default_value());
5517
+						$this_model_fields_n_values[ $field_name ] = $field_obj->prepare_for_use_in_db($prepared_value);
5518
+					}
5519
+				}
5520
+			} else {
5521
+				// the table's rows existed. Use their values
5522
+				foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5523
+					if (! $field_obj->is_db_only_field()) {
5524
+						$this_model_fields_n_values[ $field_name ] = $this->_get_column_value_with_table_alias_or_not(
5525
+							$cols_n_values,
5526
+							$field_obj->get_qualified_column(),
5527
+							$field_obj->get_table_column()
5528
+						);
5529
+					}
5530
+				}
5531
+			}
5532
+		}
5533
+		return $this_model_fields_n_values;
5534
+	}
5535
+
5536
+
5537
+	/**
5538
+	 * @param $cols_n_values
5539
+	 * @param $qualified_column
5540
+	 * @param $regular_column
5541
+	 * @return null
5542
+	 * @throws EE_Error
5543
+	 * @throws ReflectionException
5544
+	 */
5545
+	protected function _get_column_value_with_table_alias_or_not($cols_n_values, $qualified_column, $regular_column)
5546
+	{
5547
+		$value = null;
5548
+		// ask the field what it think it's table_name.column_name should be, and call it the "qualified column"
5549
+		// does the field on the model relate to this column retrieved from the db?
5550
+		// or is it a db-only field? (not relating to the model)
5551
+		if (isset($cols_n_values[ $qualified_column ])) {
5552
+			$value = $cols_n_values[ $qualified_column ];
5553
+		} elseif (isset($cols_n_values[ $regular_column ])) {
5554
+			$value = $cols_n_values[ $regular_column ];
5555
+		} elseif (! empty($this->foreign_key_aliases)) {
5556
+			// no PK?  ok check if there is a foreign key alias set for this table
5557
+			// then check if that alias exists in the incoming data
5558
+			// AND that the actual PK the $FK_alias represents matches the $qualified_column (full PK)
5559
+			foreach ($this->foreign_key_aliases as $FK_alias => $PK_column) {
5560
+				if ($PK_column === $qualified_column && isset($cols_n_values[ $FK_alias ])) {
5561
+					$value = $cols_n_values[ $FK_alias ];
5562
+					[$pk_class] = explode('.', $PK_column);
5563
+					$pk_model_name = "EEM_{$pk_class}";
5564
+					/** @var EEM_Base $pk_model */
5565
+					$pk_model = EE_Registry::instance()->load_model($pk_model_name);
5566
+					if ($pk_model instanceof EEM_Base) {
5567
+						// make sure object is pulled from db and added to entity map
5568
+						$pk_model->get_one_by_ID($value);
5569
+					}
5570
+					break;
5571
+				}
5572
+			}
5573
+		}
5574
+		return $value;
5575
+	}
5576
+
5577
+
5578
+	/**
5579
+	 * refresh_entity_map_from_db
5580
+	 * Makes sure the model object in the entity map at $id assumes the values
5581
+	 * of the database (opposite of EE_base_Class::save())
5582
+	 *
5583
+	 * @param int|string $id
5584
+	 * @return EE_Base_Class|EE_Soft_Delete_Base_Class|mixed|null
5585
+	 * @throws EE_Error
5586
+	 * @throws ReflectionException
5587
+	 */
5588
+	public function refresh_entity_map_from_db($id)
5589
+	{
5590
+		$obj_in_map = $this->get_from_entity_map($id);
5591
+		if ($obj_in_map) {
5592
+			$wpdb_results = $this->_get_all_wpdb_results(
5593
+				[[$this->get_primary_key_field()->get_name() => $id], 'limit' => 1]
5594
+			);
5595
+			if ($wpdb_results && is_array($wpdb_results)) {
5596
+				$one_row = reset($wpdb_results);
5597
+				foreach ($this->_deduce_fields_n_values_from_cols_n_values($one_row) as $field_name => $db_value) {
5598
+					$obj_in_map->set_from_db($field_name, $db_value);
5599
+				}
5600
+				// clear the cache of related model objects
5601
+				foreach ($this->relation_settings() as $relation_name => $relation_obj) {
5602
+					$obj_in_map->clear_cache($relation_name, null, true);
5603
+				}
5604
+			}
5605
+			$this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ] = $obj_in_map;
5606
+			return $obj_in_map;
5607
+		}
5608
+		return $this->get_one_by_ID($id);
5609
+	}
5610
+
5611
+
5612
+	/**
5613
+	 * refresh_entity_map_with
5614
+	 * Leaves the entry in the entity map alone, but updates it to match the provided
5615
+	 * $replacing_model_obj (which we assume to be its equivalent but somehow NOT in the entity map).
5616
+	 * This is useful if you have a model object you want to make authoritative over what's in the entity map currently.
5617
+	 * Note: The old $replacing_model_obj should now be destroyed as it's now un-authoritative
5618
+	 *
5619
+	 * @param int|string    $id
5620
+	 * @param EE_Base_Class $replacing_model_obj
5621
+	 * @return EE_Base_Class
5622
+	 * @throws EE_Error
5623
+	 * @throws ReflectionException
5624
+	 */
5625
+	public function refresh_entity_map_with($id, $replacing_model_obj)
5626
+	{
5627
+		$obj_in_map = $this->get_from_entity_map($id);
5628
+		if ($obj_in_map) {
5629
+			if ($replacing_model_obj instanceof EE_Base_Class) {
5630
+				foreach ($replacing_model_obj->model_field_array() as $field_name => $value) {
5631
+					$obj_in_map->set($field_name, $value);
5632
+				}
5633
+				// make the model object in the entity map's cache match the $replacing_model_obj
5634
+				foreach ($this->relation_settings() as $relation_name => $relation_obj) {
5635
+					$obj_in_map->clear_cache($relation_name, null, true);
5636
+					foreach ($replacing_model_obj->get_all_from_cache($relation_name) as $cache_id => $cached_obj) {
5637
+						$obj_in_map->cache($relation_name, $cached_obj, $cache_id);
5638
+					}
5639
+				}
5640
+			}
5641
+			return $obj_in_map;
5642
+		}
5643
+		$this->add_to_entity_map($replacing_model_obj);
5644
+		return $replacing_model_obj;
5645
+	}
5646
+
5647
+
5648
+	/**
5649
+	 * Gets the EE class that corresponds to this model. Eg, for EEM_Answer that
5650
+	 * would be EE_Answer.To import that class, you'd just add ".class.php" to the name, like so
5651
+	 * require_once($this->_getClassName().".class.php");
5652
+	 *
5653
+	 * @return string
5654
+	 */
5655
+	private function _get_class_name()
5656
+	{
5657
+		return "EE_" . $this->get_this_model_name();
5658
+	}
5659
+
5660
+
5661
+	/**
5662
+	 * Get the name of the items this model represents, for the quantity specified. Eg,
5663
+	 * if $quantity==1, on EEM_Event, it would 'Event' (internationalized), otherwise
5664
+	 * it would be 'Events'.
5665
+	 *
5666
+	 * @param int|float|null $quantity
5667
+	 * @return string
5668
+	 */
5669
+	public function item_name($quantity = 1): string
5670
+	{
5671
+		$quantity = floor($quantity);
5672
+		return apply_filters(
5673
+			'FHEE__EEM_Base__item_name__plural_or_singular',
5674
+			$quantity > 1 ? $this->plural_item : $this->singular_item,
5675
+			$quantity,
5676
+			$this->plural_item,
5677
+			$this->singular_item
5678
+		);
5679
+	}
5680
+
5681
+
5682
+	/**
5683
+	 * Very handy general function to allow for plugins to extend any child of EE_TempBase.
5684
+	 * If a method is called on a child of EE_TempBase that doesn't exist, this function is called
5685
+	 * (http://www.garfieldtech.com/blog/php-magic-call) and passed the method's name and arguments. Instead of
5686
+	 * requiring a plugin to extend the EE_TempBase (which works fine is there's only 1 plugin, but when will that
5687
+	 * happen?) they can add a hook onto 'filters_hook_espresso__{className}__{methodName}' (eg,
5688
+	 * filters_hook_espresso__EE_Answer__my_great_function) and accepts 2 arguments: the object on which the function
5689
+	 * was called, and an array of the original arguments passed to the function. Whatever their callback function
5690
+	 * returns will be returned by this function. Example: in functions.php (or in a plugin):
5691
+	 * add_filter('FHEE__EE_Answer__my_callback','my_callback',10,3); function
5692
+	 * my_callback($previousReturnValue,EE_TempBase $object,$argsArray){
5693
+	 * $returnString= "you called my_callback! and passed args:".implode(",",$argsArray);
5694
+	 *        return $previousReturnValue.$returnString;
5695
+	 * }
5696
+	 * require('EEM_Answer.model.php');
5697
+	 * echo EEM_Answer::instance()->my_callback('monkeys',100);
5698
+	 * // will output "you called my_callback! and passed args:monkeys,100"
5699
+	 *
5700
+	 * @param string $methodName name of method which was called on a child of EE_TempBase, but which
5701
+	 * @param array  $args       array of original arguments passed to the function
5702
+	 * @return mixed whatever the plugin which calls add_filter decides
5703
+	 * @throws EE_Error
5704
+	 */
5705
+	public function __call($methodName, $args)
5706
+	{
5707
+		$className = get_class($this);
5708
+		$tagName   = "FHEE__{$className}__{$methodName}";
5709
+		if (! has_filter($tagName)) {
5710
+			throw new EE_Error(
5711
+				sprintf(
5712
+					esc_html__(
5713
+						'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 );',
5714
+						'event_espresso'
5715
+					),
5716
+					$methodName,
5717
+					$className,
5718
+					$tagName,
5719
+					'<br />'
5720
+				)
5721
+			);
5722
+		}
5723
+		return apply_filters($tagName, null, $this, $args);
5724
+	}
5725
+
5726
+
5727
+	/**
5728
+	 * Ensures $base_class_obj_or_id is of the EE_Base_Class child that corresponds ot this model.
5729
+	 * If not, assumes its an ID, and uses $this->get_one_by_ID() to get the EE_Base_Class.
5730
+	 *
5731
+	 * @param EE_Base_Class|string|int $base_class_obj_or_id either:
5732
+	 *                                                       the EE_Base_Class object that corresponds to this Model,
5733
+	 *                                                       the object's class name
5734
+	 *                                                       or object's ID
5735
+	 * @param boolean                  $ensure_is_in_db      if set, we will also verify this model object
5736
+	 *                                                       exists in the database. If it does not, we add it
5737
+	 * @return EE_Base_Class
5738
+	 * @throws EE_Error
5739
+	 * @throws ReflectionException
5740
+	 */
5741
+	public function ensure_is_obj($base_class_obj_or_id, $ensure_is_in_db = false)
5742
+	{
5743
+		$className = $this->_get_class_name();
5744
+		if ($base_class_obj_or_id instanceof $className) {
5745
+			$model_object = $base_class_obj_or_id;
5746
+		} else {
5747
+			$primary_key_field = $this->get_primary_key_field();
5748
+			if (
5749
+				$primary_key_field instanceof EE_Primary_Key_Int_Field
5750
+				&& (
5751
+					is_int($base_class_obj_or_id)
5752
+					|| is_string($base_class_obj_or_id)
5753
+				)
5754
+			) {
5755
+				// assume it's an ID.
5756
+				// either a proper integer or a string representing an integer (eg "101" instead of 101)
5757
+				$model_object = $this->get_one_by_ID($base_class_obj_or_id);
5758
+			} elseif (
5759
+				$primary_key_field instanceof EE_Primary_Key_String_Field
5760
+				&& is_string($base_class_obj_or_id)
5761
+			) {
5762
+				// assume its a string representation of the object
5763
+				$model_object = $this->get_one_by_ID($base_class_obj_or_id);
5764
+			} else {
5765
+				throw new EE_Error(
5766
+					sprintf(
5767
+						esc_html__(
5768
+							"'%s' is neither an object of type %s, nor an ID! Its full value is '%s'",
5769
+							'event_espresso'
5770
+						),
5771
+						$base_class_obj_or_id,
5772
+						$this->_get_class_name(),
5773
+						print_r($base_class_obj_or_id, true)
5774
+					)
5775
+				);
5776
+			}
5777
+		}
5778
+		if ($ensure_is_in_db && $model_object->ID() !== null) {
5779
+			$model_object->save();
5780
+		}
5781
+		return $model_object;
5782
+	}
5783
+
5784
+
5785
+	/**
5786
+	 * Similar to ensure_is_obj(), this method makes sure $base_class_obj_or_id
5787
+	 * is a value of the this model's primary key. If it's an EE_Base_Class child,
5788
+	 * returns it ID.
5789
+	 *
5790
+	 * @param EE_Base_Class|int|string $base_class_obj_or_id
5791
+	 * @return int|string depending on the type of this model object's ID
5792
+	 * @throws EE_Error
5793
+	 * @throws ReflectionException
5794
+	 */
5795
+	public function ensure_is_ID($base_class_obj_or_id)
5796
+	{
5797
+		$className = $this->_get_class_name();
5798
+		if ($base_class_obj_or_id instanceof $className) {
5799
+			/** @var $base_class_obj_or_id EE_Base_Class */
5800
+			$id = $base_class_obj_or_id->ID();
5801
+		} elseif (is_int($base_class_obj_or_id)) {
5802
+			// assume it's an ID
5803
+			$id = $base_class_obj_or_id;
5804
+		} elseif (is_string($base_class_obj_or_id)) {
5805
+			// assume its a string representation of the object
5806
+			$id = $base_class_obj_or_id;
5807
+		} else {
5808
+			throw new EE_Error(
5809
+				sprintf(
5810
+					esc_html__(
5811
+						"'%s' is neither an object of type %s, nor an ID! Its full value is '%s'",
5812
+						'event_espresso'
5813
+					),
5814
+					$base_class_obj_or_id,
5815
+					$this->_get_class_name(),
5816
+					print_r($base_class_obj_or_id, true)
5817
+				)
5818
+			);
5819
+		}
5820
+		return $id;
5821
+	}
5822
+
5823
+
5824
+	/**
5825
+	 * Sets whether the values passed to the model (eg, values in WHERE, values in INSERT, UPDATE, etc)
5826
+	 * have already been ran through the appropriate model field's prepare_for_use_in_db method. IE, they have
5827
+	 * been sanitized and converted into the appropriate domain.
5828
+	 * Usually the only place you'll want to change the default (which is to assume values have NOT been sanitized by
5829
+	 * the model object/model field) is when making a method call from WITHIN a model object, which has direct access
5830
+	 * to its sanitized values. Note: after changing this setting, you should set it back to its previous value (using
5831
+	 * get_assumption_concerning_values_already_prepared_by_model_object()) eg.
5832
+	 * $EVT = EEM_Event::instance(); $old_setting =
5833
+	 * $EVT->get_assumption_concerning_values_already_prepared_by_model_object();
5834
+	 * $EVT->assume_values_already_prepared_by_model_object(true);
5835
+	 * $EVT->update(array('foo'=>'bar'),array(array('foo'=>'monkey')));
5836
+	 * $EVT->assume_values_already_prepared_by_model_object($old_setting);
5837
+	 *
5838
+	 * @param int $values_already_prepared like one of the constants on EEM_Base
5839
+	 * @return void
5840
+	 */
5841
+	public function assume_values_already_prepared_by_model_object(
5842
+		$values_already_prepared = self::not_prepared_by_model_object
5843
+	) {
5844
+		$this->_values_already_prepared_by_model_object = $values_already_prepared;
5845
+	}
5846
+
5847
+
5848
+	/**
5849
+	 * Read comments for assume_values_already_prepared_by_model_object()
5850
+	 *
5851
+	 * @return int
5852
+	 */
5853
+	public function get_assumption_concerning_values_already_prepared_by_model_object()
5854
+	{
5855
+		return $this->_values_already_prepared_by_model_object;
5856
+	}
5857
+
5858
+
5859
+	/**
5860
+	 * Gets all the indexes on this model
5861
+	 *
5862
+	 * @return EE_Index[]
5863
+	 */
5864
+	public function indexes()
5865
+	{
5866
+		return $this->_indexes;
5867
+	}
5868
+
5869
+
5870
+	/**
5871
+	 * Gets all the Unique Indexes on this model
5872
+	 *
5873
+	 * @return EE_Unique_Index[]
5874
+	 */
5875
+	public function unique_indexes()
5876
+	{
5877
+		$unique_indexes = [];
5878
+		foreach ($this->_indexes as $name => $index) {
5879
+			if ($index instanceof EE_Unique_Index) {
5880
+				$unique_indexes [ $name ] = $index;
5881
+			}
5882
+		}
5883
+		return $unique_indexes;
5884
+	}
5885
+
5886
+
5887
+	/**
5888
+	 * Gets all the fields which, when combined, make the primary key.
5889
+	 * This is usually just an array with 1 element (the primary key), but in cases
5890
+	 * where there is no primary key, it's a combination of fields as defined
5891
+	 * on a primary index
5892
+	 *
5893
+	 * @return EE_Model_Field_Base[] indexed by the field's name
5894
+	 * @throws EE_Error
5895
+	 */
5896
+	public function get_combined_primary_key_fields()
5897
+	{
5898
+		foreach ($this->indexes() as $index) {
5899
+			if ($index instanceof EE_Primary_Key_Index) {
5900
+				return $index->fields();
5901
+			}
5902
+		}
5903
+		return [$this->primary_key_name() => $this->get_primary_key_field()];
5904
+	}
5905
+
5906
+
5907
+	/**
5908
+	 * Used to build a primary key string (when the model has no primary key),
5909
+	 * which can be used a unique string to identify this model object.
5910
+	 *
5911
+	 * @param array $fields_n_values keys are field names, values are their values.
5912
+	 *                               Note: if you have results from `EEM_Base::get_all_wpdb_results()`, you need to
5913
+	 *                               run it through `EEM_Base::deduce_fields_n_values_from_cols_n_values()`
5914
+	 *                               before passing it to this function (that will convert it from columns-n-values
5915
+	 *                               to field-names-n-values).
5916
+	 * @return string
5917
+	 * @throws EE_Error
5918
+	 */
5919
+	public function get_index_primary_key_string($fields_n_values)
5920
+	{
5921
+		$cols_n_values_for_primary_key_index = array_intersect_key(
5922
+			$fields_n_values,
5923
+			$this->get_combined_primary_key_fields()
5924
+		);
5925
+		return http_build_query($cols_n_values_for_primary_key_index);
5926
+	}
5927
+
5928
+
5929
+	/**
5930
+	 * Gets the field values from the primary key string
5931
+	 *
5932
+	 * @param string $index_primary_key_string
5933
+	 * @return null|array
5934
+	 * @throws EE_Error
5935
+	 * @see EEM_Base::get_combined_primary_key_fields() and EEM_Base::get_index_primary_key_string()
5936
+	 */
5937
+	public function parse_index_primary_key_string($index_primary_key_string)
5938
+	{
5939
+		$key_fields = $this->get_combined_primary_key_fields();
5940
+		// check all of them are in the $id
5941
+		$key_vals_in_combined_pk = [];
5942
+		parse_str($index_primary_key_string, $key_vals_in_combined_pk);
5943
+		foreach ($key_fields as $key_field_name => $field_obj) {
5944
+			if (! isset($key_vals_in_combined_pk[ $key_field_name ])) {
5945
+				return null;
5946
+			}
5947
+		}
5948
+		return $key_vals_in_combined_pk;
5949
+	}
5950
+
5951
+
5952
+	/**
5953
+	 * verifies that an array of key-value pairs for model fields has a key
5954
+	 * for each field comprising the primary key index
5955
+	 *
5956
+	 * @param array $key_vals
5957
+	 * @return boolean
5958
+	 * @throws EE_Error
5959
+	 */
5960
+	public function has_all_combined_primary_key_fields($key_vals)
5961
+	{
5962
+		$keys_it_should_have = array_keys($this->get_combined_primary_key_fields());
5963
+		foreach ($keys_it_should_have as $key) {
5964
+			if (! isset($key_vals[ $key ])) {
5965
+				return false;
5966
+			}
5967
+		}
5968
+		return true;
5969
+	}
5970
+
5971
+
5972
+	/**
5973
+	 * Finds all model objects in the DB that appear to be a copy of $model_object_or_attributes_array.
5974
+	 * We consider something to be a copy if all the attributes match (except the ID, of course).
5975
+	 *
5976
+	 * @param array|EE_Base_Class $model_object_or_attributes_array If its an array, it's field-value pairs
5977
+	 * @param array               $query_params                     @see
5978
+	 *                                                              https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md
5979
+	 * @throws EE_Error
5980
+	 * @throws ReflectionException
5981
+	 * @return EE_Base_Class[] Array keys are object IDs (if there is a primary key on the model. if not, numerically
5982
+	 *                                                              indexed)
5983
+	 */
5984
+	public function get_all_copies($model_object_or_attributes_array, $query_params = [])
5985
+	{
5986
+		if ($model_object_or_attributes_array instanceof EE_Base_Class) {
5987
+			$attributes_array = $model_object_or_attributes_array->model_field_array();
5988
+		} elseif (is_array($model_object_or_attributes_array)) {
5989
+			$attributes_array = $model_object_or_attributes_array;
5990
+		} else {
5991
+			throw new EE_Error(
5992
+				sprintf(
5993
+					esc_html__(
5994
+						"get_all_copies should be provided with either a model object or an array of field-value-pairs, but was given %s",
5995
+						"event_espresso"
5996
+					),
5997
+					$model_object_or_attributes_array
5998
+				)
5999
+			);
6000
+		}
6001
+		// even copies obviously won't have the same ID, so remove the primary key
6002
+		// from the WHERE conditions for finding copies (if there is a primary key, of course)
6003
+		if ($this->has_primary_key_field() && isset($attributes_array[ $this->primary_key_name() ])) {
6004
+			unset($attributes_array[ $this->primary_key_name() ]);
6005
+		}
6006
+		if (isset($query_params[0])) {
6007
+			$query_params[0] = array_merge($attributes_array, $query_params);
6008
+		} else {
6009
+			$query_params[0] = $attributes_array;
6010
+		}
6011
+		return $this->get_all($query_params);
6012
+	}
6013
+
6014
+
6015
+	/**
6016
+	 * Gets the first copy we find. See get_all_copies for more details
6017
+	 *
6018
+	 * @param mixed EE_Base_Class | array        $model_object_or_attributes_array
6019
+	 * @param array $query_params
6020
+	 * @return EE_Base_Class
6021
+	 * @throws EE_Error
6022
+	 * @throws ReflectionException
6023
+	 */
6024
+	public function get_one_copy($model_object_or_attributes_array, $query_params = [])
6025
+	{
6026
+		if (! is_array($query_params)) {
6027
+			EE_Error::doing_it_wrong(
6028
+				'EEM_Base::get_one_copy',
6029
+				sprintf(
6030
+					esc_html__('$query_params should be an array, you passed a variable of type %s', 'event_espresso'),
6031
+					gettype($query_params)
6032
+				),
6033
+				'4.6.0'
6034
+			);
6035
+			$query_params = [];
6036
+		}
6037
+		$query_params['limit'] = 1;
6038
+		$copies                = $this->get_all_copies($model_object_or_attributes_array, $query_params);
6039
+		if (is_array($copies)) {
6040
+			return array_shift($copies);
6041
+		}
6042
+		return null;
6043
+	}
6044
+
6045
+
6046
+	/**
6047
+	 * Updates the item with the specified id. Ignores default query parameters because
6048
+	 * we have specified the ID, and its assumed we KNOW what we're doing
6049
+	 *
6050
+	 * @param array      $fields_n_values keys are field names, values are their new values
6051
+	 * @param int|string $id              the value of the primary key to update
6052
+	 * @return int number of rows updated
6053
+	 * @throws EE_Error
6054
+	 * @throws ReflectionException
6055
+	 */
6056
+	public function update_by_ID($fields_n_values, $id)
6057
+	{
6058
+		$query_params = [
6059
+			0                          => [$this->get_primary_key_field()->get_name() => $id],
6060
+			'default_where_conditions' => EEM_Base::default_where_conditions_others_only,
6061
+		];
6062
+		return $this->update($fields_n_values, $query_params);
6063
+	}
6064
+
6065
+
6066
+	/**
6067
+	 * Changes an operator which was supplied to the models into one usable in SQL
6068
+	 *
6069
+	 * @param string $operator_supplied
6070
+	 * @return string an operator which can be used in SQL
6071
+	 * @throws EE_Error
6072
+	 */
6073
+	private function _prepare_operator_for_sql($operator_supplied)
6074
+	{
6075
+		$sql_operator = $this->_valid_operators[ $operator_supplied ] ?? null;
6076
+		if ($sql_operator) {
6077
+			return $sql_operator;
6078
+		}
6079
+		throw new EE_Error(
6080
+			sprintf(
6081
+				esc_html__(
6082
+					"The operator '%s' is not in the list of valid operators: %s",
6083
+					"event_espresso"
6084
+				),
6085
+				$operator_supplied,
6086
+				implode(",", array_keys($this->_valid_operators))
6087
+			)
6088
+		);
6089
+	}
6090
+
6091
+
6092
+	/**
6093
+	 * Gets the valid operators
6094
+	 *
6095
+	 * @return array keys are accepted strings, values are the SQL they are converted to
6096
+	 */
6097
+	public function valid_operators()
6098
+	{
6099
+		return $this->_valid_operators;
6100
+	}
6101
+
6102
+
6103
+	/**
6104
+	 * Gets the between-style operators (take 2 arguments).
6105
+	 *
6106
+	 * @return array keys are accepted strings, values are the SQL they are converted to
6107
+	 */
6108
+	public function valid_between_style_operators()
6109
+	{
6110
+		return array_intersect(
6111
+			$this->valid_operators(),
6112
+			$this->_between_style_operators
6113
+		);
6114
+	}
6115
+
6116
+
6117
+	/**
6118
+	 * Gets the "like"-style operators (take a single argument, but it may contain wildcards)
6119
+	 *
6120
+	 * @return array keys are accepted strings, values are the SQL they are converted to
6121
+	 */
6122
+	public function valid_like_style_operators()
6123
+	{
6124
+		return array_intersect(
6125
+			$this->valid_operators(),
6126
+			$this->_like_style_operators
6127
+		);
6128
+	}
6129
+
6130
+
6131
+	/**
6132
+	 * Gets the "in"-style operators
6133
+	 *
6134
+	 * @return array keys are accepted strings, values are the SQL they are converted to
6135
+	 */
6136
+	public function valid_in_style_operators()
6137
+	{
6138
+		return array_intersect(
6139
+			$this->valid_operators(),
6140
+			$this->_in_style_operators
6141
+		);
6142
+	}
6143
+
6144
+
6145
+	/**
6146
+	 * Gets the "null"-style operators (accept no arguments)
6147
+	 *
6148
+	 * @return array keys are accepted strings, values are the SQL they are converted to
6149
+	 */
6150
+	public function valid_null_style_operators()
6151
+	{
6152
+		return array_intersect(
6153
+			$this->valid_operators(),
6154
+			$this->_null_style_operators
6155
+		);
6156
+	}
6157
+
6158
+
6159
+	/**
6160
+	 * Gets an array where keys are the primary keys and values are their 'names'
6161
+	 * (as determined by the model object's name() function, which is often overridden)
6162
+	 *
6163
+	 * @param array $query_params like get_all's
6164
+	 * @return string[]
6165
+	 * @throws EE_Error
6166
+	 * @throws ReflectionException
6167
+	 */
6168
+	public function get_all_names($query_params = [])
6169
+	{
6170
+		$objs  = $this->get_all($query_params);
6171
+		$names = [];
6172
+		foreach ($objs as $obj) {
6173
+			$names[ $obj->ID() ] = $obj->name();
6174
+		}
6175
+		return $names;
6176
+	}
6177
+
6178
+
6179
+	/**
6180
+	 * Gets an array of primary keys from the model objects. If you acquired the model objects
6181
+	 * using EEM_Base::get_all() you don't need to call this (and probably shouldn't because
6182
+	 * this is duplicated effort and reduces efficiency) you would be better to use
6183
+	 * array_keys() on $model_objects.
6184
+	 *
6185
+	 * @param \EE_Base_Class[] $model_objects
6186
+	 * @param boolean          $filter_out_empty_ids if a model object has an ID of '' or 0, don't bother including it
6187
+	 *                                               in the returned array
6188
+	 * @return array
6189
+	 * @throws EE_Error
6190
+	 * @throws ReflectionException
6191
+	 */
6192
+	public function get_IDs($model_objects, $filter_out_empty_ids = false)
6193
+	{
6194
+		if (! $this->has_primary_key_field()) {
6195
+			if (WP_DEBUG) {
6196
+				EE_Error::add_error(
6197
+					esc_html__('Trying to get IDs from a model than has no primary key', 'event_espresso'),
6198
+					__FILE__,
6199
+					__FUNCTION__,
6200
+					__LINE__
6201
+				);
6202
+			}
6203
+		}
6204
+		$IDs = [];
6205
+		foreach ($model_objects as $model_object) {
6206
+			$id = $model_object->ID();
6207
+			if (! $id) {
6208
+				if ($filter_out_empty_ids) {
6209
+					continue;
6210
+				}
6211
+				if (WP_DEBUG) {
6212
+					EE_Error::add_error(
6213
+						esc_html__(
6214
+							'Called %1$s on a model object that has no ID and so probably hasn\'t been saved to the database',
6215
+							'event_espresso'
6216
+						),
6217
+						__FILE__,
6218
+						__FUNCTION__,
6219
+						__LINE__
6220
+					);
6221
+				}
6222
+			}
6223
+			$IDs[] = $id;
6224
+		}
6225
+		return $IDs;
6226
+	}
6227
+
6228
+
6229
+	/**
6230
+	 * Returns the string used in capabilities relating to this model. If there
6231
+	 * are no capabilities that relate to this model returns false
6232
+	 *
6233
+	 * @return string|false
6234
+	 */
6235
+	public function cap_slug()
6236
+	{
6237
+		return apply_filters('FHEE__EEM_Base__cap_slug', $this->_caps_slug, $this);
6238
+	}
6239
+
6240
+
6241
+	/**
6242
+	 * Returns the capability-restrictions array (@param string $context
6243
+	 *
6244
+	 * @return EE_Default_Where_Conditions[] indexed by associated capability
6245
+	 * @throws EE_Error
6246
+	 * @see EEM_Base::_cap_restrictions).
6247
+	 *      If $context is provided (which should be set to one of EEM_Base::valid_cap_contexts())
6248
+	 *      only returns the cap restrictions array in that context (ie, the array
6249
+	 *      at that key)
6250
+	 *
6251
+	 */
6252
+	public function cap_restrictions($context = EEM_Base::caps_read)
6253
+	{
6254
+		EEM_Base::verify_is_valid_cap_context($context);
6255
+		// check if we ought to run the restriction generator first
6256
+		if (
6257
+			isset($this->_cap_restriction_generators[ $context ])
6258
+			&& $this->_cap_restriction_generators[ $context ] instanceof EE_Restriction_Generator_Base
6259
+			&& ! $this->_cap_restriction_generators[ $context ]->has_generated_cap_restrictions()
6260
+		) {
6261
+			$this->_cap_restrictions[ $context ] = array_merge(
6262
+				$this->_cap_restrictions[ $context ],
6263
+				$this->_cap_restriction_generators[ $context ]->generate_restrictions()
6264
+			);
6265
+		}
6266
+		// and make sure we've finalized the construction of each restriction
6267
+		foreach ($this->_cap_restrictions[ $context ] as $where_conditions_obj) {
6268
+			if ($where_conditions_obj instanceof EE_Default_Where_Conditions) {
6269
+				$where_conditions_obj->_finalize_construct($this);
6270
+			}
6271
+		}
6272
+		return $this->_cap_restrictions[ $context ];
6273
+	}
6274
+
6275
+
6276
+	/**
6277
+	 * Indicating whether or not this model thinks its a wp core model
6278
+	 *
6279
+	 * @return boolean
6280
+	 */
6281
+	public function is_wp_core_model()
6282
+	{
6283
+		return $this->_wp_core_model;
6284
+	}
6285
+
6286
+
6287
+	/**
6288
+	 * Gets all the caps that are missing which impose a restriction on
6289
+	 * queries made in this context
6290
+	 *
6291
+	 * @param string $context one of EEM_Base::caps_ constants
6292
+	 * @return EE_Default_Where_Conditions[] indexed by capability name
6293
+	 * @throws EE_Error
6294
+	 */
6295
+	public function caps_missing($context = EEM_Base::caps_read)
6296
+	{
6297
+		$missing_caps     = [];
6298
+		$cap_restrictions = $this->cap_restrictions($context);
6299
+		foreach ($cap_restrictions as $cap => $restriction_if_no_cap) {
6300
+			if (
6301
+				! EE_Capabilities::instance()
6302
+								 ->current_user_can($cap, $this->get_this_model_name() . '_model_applying_caps')
6303
+			) {
6304
+				$missing_caps[ $cap ] = $restriction_if_no_cap;
6305
+			}
6306
+		}
6307
+		return $missing_caps;
6308
+	}
6309
+
6310
+
6311
+	/**
6312
+	 * Gets the mapping from capability contexts to action strings used in capability names
6313
+	 *
6314
+	 * @return array keys are one of EEM_Base::valid_cap_contexts(), and values are usually
6315
+	 * one of 'read', 'edit', or 'delete'
6316
+	 */
6317
+	public function cap_contexts_to_cap_action_map()
6318
+	{
6319
+		return apply_filters(
6320
+			'FHEE__EEM_Base__cap_contexts_to_cap_action_map',
6321
+			$this->_cap_contexts_to_cap_action_map,
6322
+			$this
6323
+		);
6324
+	}
6325
+
6326
+
6327
+	/**
6328
+	 * Gets the action string for the specified capability context
6329
+	 *
6330
+	 * @param string $context
6331
+	 * @return string one of EEM_Base::cap_contexts_to_cap_action_map() values
6332
+	 * @throws EE_Error
6333
+	 */
6334
+	public function cap_action_for_context($context)
6335
+	{
6336
+		$mapping = $this->cap_contexts_to_cap_action_map();
6337
+		if (isset($mapping[ $context ])) {
6338
+			return $mapping[ $context ];
6339
+		}
6340
+		if ($action = apply_filters('FHEE__EEM_Base__cap_action_for_context', null, $this, $mapping, $context)) {
6341
+			return $action;
6342
+		}
6343
+		throw new EE_Error(
6344
+			sprintf(
6345
+				esc_html__(
6346
+					'Cannot find capability restrictions for context "%1$s", allowed values are:%2$s',
6347
+					'event_espresso'
6348
+				),
6349
+				$context,
6350
+				implode(',', array_keys($this->cap_contexts_to_cap_action_map()))
6351
+			)
6352
+		);
6353
+	}
6354
+
6355
+
6356
+	/**
6357
+	 * Returns all the capability contexts which are valid when querying models
6358
+	 *
6359
+	 * @return array
6360
+	 */
6361
+	public static function valid_cap_contexts()
6362
+	{
6363
+		return apply_filters('FHEE__EEM_Base__valid_cap_contexts', [
6364
+			self::caps_read,
6365
+			self::caps_read_admin,
6366
+			self::caps_edit,
6367
+			self::caps_delete,
6368
+		]);
6369
+	}
6370
+
6371
+
6372
+	/**
6373
+	 * Returns all valid options for 'default_where_conditions'
6374
+	 *
6375
+	 * @return array
6376
+	 */
6377
+	public static function valid_default_where_conditions()
6378
+	{
6379
+		return [
6380
+			EEM_Base::default_where_conditions_all,
6381
+			EEM_Base::default_where_conditions_this_only,
6382
+			EEM_Base::default_where_conditions_others_only,
6383
+			EEM_Base::default_where_conditions_minimum_all,
6384
+			EEM_Base::default_where_conditions_minimum_others,
6385
+			EEM_Base::default_where_conditions_none,
6386
+		];
6387
+	}
6388
+
6389
+	// public static function default_where_conditions_full
6390
+
6391
+
6392
+	/**
6393
+	 * Verifies $context is one of EEM_Base::valid_cap_contexts(), if not it throws an exception
6394
+	 *
6395
+	 * @param string $context
6396
+	 * @return bool
6397
+	 * @throws EE_Error
6398
+	 */
6399
+	public static function verify_is_valid_cap_context($context)
6400
+	{
6401
+		$valid_cap_contexts = EEM_Base::valid_cap_contexts();
6402
+		if (in_array($context, $valid_cap_contexts)) {
6403
+			return true;
6404
+		}
6405
+		throw new EE_Error(
6406
+			sprintf(
6407
+				esc_html__(
6408
+					'Context "%1$s" passed into model "%2$s" is not a valid context. They are: %3$s',
6409
+					'event_espresso'
6410
+				),
6411
+				$context,
6412
+				'EEM_Base',
6413
+				implode(',', $valid_cap_contexts)
6414
+			)
6415
+		);
6416
+	}
6417
+
6418
+
6419
+	/**
6420
+	 * Clears all the models field caches. This is only useful when a sub-class
6421
+	 * might have added a field or something and these caches might be invalidated
6422
+	 */
6423
+	protected function _invalidate_field_caches()
6424
+	{
6425
+		$this->_cache_foreign_key_to_fields = [];
6426
+		$this->_cached_fields               = null;
6427
+		$this->_cached_fields_non_db_only   = null;
6428
+	}
6429
+
6430
+
6431
+	/**
6432
+	 * Gets the list of all the where query param keys that relate to logic instead of field names
6433
+	 * (eg "and", "or", "not").
6434
+	 *
6435
+	 * @return array
6436
+	 */
6437
+	public function logic_query_param_keys()
6438
+	{
6439
+		return $this->_logic_query_param_keys;
6440
+	}
6441
+
6442
+
6443
+	/**
6444
+	 * Determines whether or not the where query param array key is for a logic query param.
6445
+	 * Eg 'OR', 'not*', and 'and*because-i-say-so' should all return true, whereas
6446
+	 * 'ATT_fname', 'EVT_name*not-you-or-me', and 'ORG_name' should return false
6447
+	 *
6448
+	 * @param $query_param_key
6449
+	 * @return bool
6450
+	 */
6451
+	public function is_logic_query_param_key($query_param_key)
6452
+	{
6453
+		foreach ($this->logic_query_param_keys() as $logic_query_param_key) {
6454
+			if (
6455
+				$query_param_key === $logic_query_param_key
6456
+				|| strpos($query_param_key, $logic_query_param_key . '*') === 0
6457
+			) {
6458
+				return true;
6459
+			}
6460
+		}
6461
+		return false;
6462
+	}
6463
+
6464
+
6465
+	/**
6466
+	 * Returns true if this model has a password field on it (regardless of whether that password field has any content)
6467
+	 *
6468
+	 * @return boolean
6469
+	 * @since 4.9.74.p
6470
+	 */
6471
+	public function hasPassword()
6472
+	{
6473
+		// if we don't yet know if there's a password field, find out and remember it for next time.
6474
+		if ($this->has_password_field === null) {
6475
+			$password_field           = $this->getPasswordField();
6476
+			$this->has_password_field = $password_field instanceof EE_Password_Field ? true : false;
6477
+		}
6478
+		return $this->has_password_field;
6479
+	}
6480
+
6481
+
6482
+	/**
6483
+	 * Returns the password field on this model, if there is one
6484
+	 *
6485
+	 * @return EE_Password_Field|null
6486
+	 * @since 4.9.74.p
6487
+	 */
6488
+	public function getPasswordField()
6489
+	{
6490
+		// if we definetely already know there is a password field or not (because has_password_field is true or false)
6491
+		// there's no need to search for it. If we don't know yet, then find out
6492
+		if ($this->has_password_field === null && $this->password_field === null) {
6493
+			$this->password_field = $this->get_a_field_of_type('EE_Password_Field');
6494
+		}
6495
+		// don't bother setting has_password_field because that's hasPassword()'s job.
6496
+		return $this->password_field;
6497
+	}
6498
+
6499
+
6500
+	/**
6501
+	 * Returns the list of field (as EE_Model_Field_Bases) that are protected by the password
6502
+	 *
6503
+	 * @return EE_Model_Field_Base[]
6504
+	 * @throws EE_Error
6505
+	 * @since 4.9.74.p
6506
+	 */
6507
+	public function getPasswordProtectedFields()
6508
+	{
6509
+		$password_field = $this->getPasswordField();
6510
+		$fields         = [];
6511
+		if ($password_field instanceof EE_Password_Field) {
6512
+			$field_names = $password_field->protectedFields();
6513
+			foreach ($field_names as $field_name) {
6514
+				$fields[ $field_name ] = $this->field_settings_for($field_name);
6515
+			}
6516
+		}
6517
+		return $fields;
6518
+	}
6519
+
6520
+
6521
+	/**
6522
+	 * Checks if the current user can perform the requested action on this model
6523
+	 *
6524
+	 * @param string              $cap_to_check one of the array keys from _cap_contexts_to_cap_action_map
6525
+	 * @param EE_Base_Class|array $model_obj_or_fields_n_values
6526
+	 * @return bool
6527
+	 * @throws EE_Error
6528
+	 * @throws InvalidArgumentException
6529
+	 * @throws InvalidDataTypeException
6530
+	 * @throws InvalidInterfaceException
6531
+	 * @throws ReflectionException
6532
+	 * @throws UnexpectedEntityException
6533
+	 * @since 4.9.74.p
6534
+	 */
6535
+	public function currentUserCan($cap_to_check, $model_obj_or_fields_n_values)
6536
+	{
6537
+		if ($model_obj_or_fields_n_values instanceof EE_Base_Class) {
6538
+			$model_obj_or_fields_n_values = $model_obj_or_fields_n_values->model_field_array();
6539
+		}
6540
+		if (! is_array($model_obj_or_fields_n_values)) {
6541
+			throw new UnexpectedEntityException(
6542
+				$model_obj_or_fields_n_values,
6543
+				'EE_Base_Class',
6544
+				sprintf(
6545
+					esc_html__(
6546
+						'%1$s must be passed an `EE_Base_Class or an array of fields names with their values. You passed in something different.',
6547
+						'event_espresso'
6548
+					),
6549
+					__FUNCTION__
6550
+				)
6551
+			);
6552
+		}
6553
+		return $this->exists(
6554
+			$this->alter_query_params_to_restrict_by_ID(
6555
+				$this->get_index_primary_key_string($model_obj_or_fields_n_values),
6556
+				[
6557
+					'default_where_conditions' => 'none',
6558
+					'caps'                     => $cap_to_check,
6559
+				]
6560
+			)
6561
+		);
6562
+	}
6563
+
6564
+
6565
+	/**
6566
+	 * Returns the query param where conditions key to the password affecting this model.
6567
+	 * Eg on EEM_Event this would just be "password", on EEM_Datetime this would be "Event.password", etc.
6568
+	 *
6569
+	 * @return null|string
6570
+	 * @throws EE_Error
6571
+	 * @throws InvalidArgumentException
6572
+	 * @throws InvalidDataTypeException
6573
+	 * @throws InvalidInterfaceException
6574
+	 * @throws ModelConfigurationException
6575
+	 * @throws ReflectionException
6576
+	 * @since 4.9.74.p
6577
+	 */
6578
+	public function modelChainAndPassword()
6579
+	{
6580
+		if ($this->model_chain_to_password === null) {
6581
+			throw new ModelConfigurationException(
6582
+				$this,
6583
+				esc_html_x(
6584
+				// @codingStandardsIgnoreStart
6585
+					'Cannot exclude protected data because the model has not specified which model has the password.',
6586
+					// @codingStandardsIgnoreEnd
6587
+					'1: model name',
6588
+					'event_espresso'
6589
+				)
6590
+			);
6591
+		}
6592
+		if ($this->model_chain_to_password === '') {
6593
+			$model_with_password = $this;
6594
+		} else {
6595
+			if ($pos_of_period = strrpos($this->model_chain_to_password, '.')) {
6596
+				$last_model_in_chain = substr($this->model_chain_to_password, $pos_of_period + 1);
6597
+			} else {
6598
+				$last_model_in_chain = $this->model_chain_to_password;
6599
+			}
6600
+			$model_with_password = EE_Registry::instance()->load_model($last_model_in_chain);
6601
+		}
6602
+
6603
+		$password_field = $model_with_password->getPasswordField();
6604
+		if ($password_field instanceof EE_Password_Field) {
6605
+			$password_field_name = $password_field->get_name();
6606
+		} else {
6607
+			throw new ModelConfigurationException(
6608
+				$this,
6609
+				sprintf(
6610
+					esc_html_x(
6611
+						'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"',
6612
+						'1: model name, 2: special string',
6613
+						'event_espresso'
6614
+					),
6615
+					$model_with_password->get_this_model_name(),
6616
+					$this->model_chain_to_password
6617
+				)
6618
+			);
6619
+		}
6620
+		return ($this->model_chain_to_password ? $this->model_chain_to_password . '.' : '') . $password_field_name;
6621
+	}
6622
+
6623
+
6624
+	/**
6625
+	 * Returns true if there is a password on a related model which restricts access to some of this model's rows,
6626
+	 * or if this model itself has a password affecting access to some of its other fields.
6627
+	 *
6628
+	 * @return boolean
6629
+	 * @since 4.9.74.p
6630
+	 */
6631
+	public function restrictedByRelatedModelPassword()
6632
+	{
6633
+		return $this->model_chain_to_password !== null;
6634
+	}
6635 6635
 }
Please login to merge, or discard this patch.
Spacing   +230 added lines, -230 removed lines patch added patch discarded remove patch
@@ -564,7 +564,7 @@  discard block
 block discarded – undo
564 564
     protected function __construct($timezone = null)
565 565
     {
566 566
         // check that the model has not been loaded too soon
567
-        if (! did_action('AHEE__EE_System__load_espresso_addons')) {
567
+        if ( ! did_action('AHEE__EE_System__load_espresso_addons')) {
568 568
             throw new EE_Error(
569 569
                 sprintf(
570 570
                     esc_html__(
@@ -587,7 +587,7 @@  discard block
 block discarded – undo
587 587
          *
588 588
          * @var EE_Table_Base[] $_tables
589 589
          */
590
-        $this->_tables = (array) apply_filters('FHEE__' . get_class($this) . '__construct__tables', $this->_tables);
590
+        $this->_tables = (array) apply_filters('FHEE__'.get_class($this).'__construct__tables', $this->_tables);
591 591
         foreach ($this->_tables as $table_alias => $table_obj) {
592 592
             /** @var $table_obj EE_Table_Base */
593 593
             $table_obj->_construct_finalize_with_alias($table_alias);
@@ -601,10 +601,10 @@  discard block
 block discarded – undo
601 601
          *
602 602
          * @param EE_Model_Field_Base[] $_fields
603 603
          */
604
-        $this->_fields = (array) apply_filters('FHEE__' . get_class($this) . '__construct__fields', $this->_fields);
604
+        $this->_fields = (array) apply_filters('FHEE__'.get_class($this).'__construct__fields', $this->_fields);
605 605
         $this->_invalidate_field_caches();
606 606
         foreach ($this->_fields as $table_alias => $fields_for_table) {
607
-            if (! array_key_exists($table_alias, $this->_tables)) {
607
+            if ( ! array_key_exists($table_alias, $this->_tables)) {
608 608
                 throw new EE_Error(
609 609
                     sprintf(
610 610
                         esc_html__(
@@ -641,7 +641,7 @@  discard block
 block discarded – undo
641 641
          * @param EE_Model_Relation_Base[] $_model_relations
642 642
          */
643 643
         $this->_model_relations = (array) apply_filters(
644
-            'FHEE__' . get_class($this) . '__construct__model_relations',
644
+            'FHEE__'.get_class($this).'__construct__model_relations',
645 645
             $this->_model_relations
646 646
         );
647 647
         foreach ($this->_model_relations as $model_name => $relation_obj) {
@@ -653,12 +653,12 @@  discard block
 block discarded – undo
653 653
         }
654 654
         $this->set_timezone($timezone);
655 655
         // finalize default where condition strategy, or set default
656
-        if (! $this->_default_where_conditions_strategy) {
656
+        if ( ! $this->_default_where_conditions_strategy) {
657 657
             // nothing was set during child constructor, so set default
658 658
             $this->_default_where_conditions_strategy = new EE_Default_Where_Conditions();
659 659
         }
660 660
         $this->_default_where_conditions_strategy->_finalize_construct($this);
661
-        if (! $this->_minimum_where_conditions_strategy) {
661
+        if ( ! $this->_minimum_where_conditions_strategy) {
662 662
             // nothing was set during child constructor, so set default
663 663
             $this->_minimum_where_conditions_strategy = new EE_Default_Where_Conditions();
664 664
         }
@@ -671,8 +671,8 @@  discard block
 block discarded – undo
671 671
         // initialize the standard cap restriction generators if none were specified by the child constructor
672 672
         if (is_array($this->_cap_restriction_generators)) {
673 673
             foreach ($this->cap_contexts_to_cap_action_map() as $cap_context => $action) {
674
-                if (! isset($this->_cap_restriction_generators[ $cap_context ])) {
675
-                    $this->_cap_restriction_generators[ $cap_context ] = apply_filters(
674
+                if ( ! isset($this->_cap_restriction_generators[$cap_context])) {
675
+                    $this->_cap_restriction_generators[$cap_context] = apply_filters(
676 676
                         'FHEE__EEM_Base___construct__standard_cap_restriction_generator',
677 677
                         new EE_Restriction_Generator_Protected(),
678 678
                         $cap_context,
@@ -684,10 +684,10 @@  discard block
 block discarded – undo
684 684
         // if there are cap restriction generators, use them to make the default cap restrictions
685 685
         if (is_array($this->_cap_restriction_generators)) {
686 686
             foreach ($this->_cap_restriction_generators as $context => $generator_object) {
687
-                if (! $generator_object) {
687
+                if ( ! $generator_object) {
688 688
                     continue;
689 689
                 }
690
-                if (! $generator_object instanceof EE_Restriction_Generator_Base) {
690
+                if ( ! $generator_object instanceof EE_Restriction_Generator_Base) {
691 691
                     throw new EE_Error(
692 692
                         sprintf(
693 693
                             esc_html__(
@@ -700,12 +700,12 @@  discard block
 block discarded – undo
700 700
                     );
701 701
                 }
702 702
                 $action = $this->cap_action_for_context($context);
703
-                if (! $generator_object->construction_finalized()) {
703
+                if ( ! $generator_object->construction_finalized()) {
704 704
                     $generator_object->_construct_finalize($this, $action);
705 705
                 }
706 706
             }
707 707
         }
708
-        do_action('AHEE__' . get_class($this) . '__construct__end');
708
+        do_action('AHEE__'.get_class($this).'__construct__end');
709 709
     }
710 710
 
711 711
 
@@ -717,7 +717,7 @@  discard block
 block discarded – undo
717 717
      */
718 718
     protected static function getLoader(): LoaderInterface
719 719
     {
720
-        if (! EEM_Base::$loader instanceof LoaderInterface) {
720
+        if ( ! EEM_Base::$loader instanceof LoaderInterface) {
721 721
             EEM_Base::$loader = LoaderFactory::getLoader();
722 722
         }
723 723
         return EEM_Base::$loader;
@@ -730,7 +730,7 @@  discard block
 block discarded – undo
730 730
      */
731 731
     private static function getMirror(): Mirror
732 732
     {
733
-        if (! EEM_Base::$mirror instanceof Mirror) {
733
+        if ( ! EEM_Base::$mirror instanceof Mirror) {
734 734
             EEM_Base::$mirror = EEM_Base::getLoader()->getShared(Mirror::class);
735 735
         }
736 736
         return EEM_Base::$mirror;
@@ -786,7 +786,7 @@  discard block
 block discarded – undo
786 786
     public static function instance($timezone = null)
787 787
     {
788 788
         // check if instance of Espresso_model already exists
789
-        if (! static::$_instance instanceof static) {
789
+        if ( ! static::$_instance instanceof static) {
790 790
             $arguments = EEM_Base::getModelArguments(static::class, (string) $timezone);
791 791
             $model     = new static(...$arguments);
792 792
             EEM_Base::getLoader()->share(static::class, $model, $arguments);
@@ -815,7 +815,7 @@  discard block
 block discarded – undo
815 815
      */
816 816
     public static function reset($timezone = null)
817 817
     {
818
-        if (! static::$_instance instanceof EEM_Base) {
818
+        if ( ! static::$_instance instanceof EEM_Base) {
819 819
             return null;
820 820
         }
821 821
         // Let's NOT swap out the current instance for a new one
@@ -826,7 +826,7 @@  discard block
 block discarded – undo
826 826
         foreach (EEM_Base::getMirror()->getDefaultProperties(static::class) as $property => $value) {
827 827
             // don't set instance to null like it was originally,
828 828
             // but it's static anyways, and we're ignoring static properties (for now at least)
829
-            if (! isset($static_properties[ $property ])) {
829
+            if ( ! isset($static_properties[$property])) {
830 830
                 static::$_instance->{$property} = $value;
831 831
             }
832 832
         }
@@ -875,7 +875,7 @@  discard block
 block discarded – undo
875 875
      */
876 876
     public function status_array($translated = false)
877 877
     {
878
-        if (! array_key_exists('Status', $this->_model_relations)) {
878
+        if ( ! array_key_exists('Status', $this->_model_relations)) {
879 879
             return [];
880 880
         }
881 881
         $model_name   = $this->get_this_model_name();
@@ -883,7 +883,7 @@  discard block
 block discarded – undo
883 883
         $stati        = EEM_Status::instance()->get_all([['STS_type' => $status_type]]);
884 884
         $status_array = [];
885 885
         foreach ($stati as $status) {
886
-            $status_array[ $status->ID() ] = $status->get('STS_code');
886
+            $status_array[$status->ID()] = $status->get('STS_code');
887 887
         }
888 888
         return $translated
889 889
             ? EEM_Status::instance()->localized_status($status_array, false, 'sentence')
@@ -948,7 +948,7 @@  discard block
 block discarded – undo
948 948
     {
949 949
         $wp_user_field_name = $this->wp_user_field_name();
950 950
         if ($wp_user_field_name) {
951
-            $query_params[0][ $wp_user_field_name ] = get_current_user_id();
951
+            $query_params[0][$wp_user_field_name] = get_current_user_id();
952 952
         }
953 953
         return $query_params;
954 954
     }
@@ -967,17 +967,17 @@  discard block
 block discarded – undo
967 967
     public function wp_user_field_name()
968 968
     {
969 969
         try {
970
-            if (! empty($this->_model_chain_to_wp_user)) {
970
+            if ( ! empty($this->_model_chain_to_wp_user)) {
971 971
                 $models_to_follow_to_wp_users = explode('.', $this->_model_chain_to_wp_user);
972 972
                 $last_model_name              = end($models_to_follow_to_wp_users);
973 973
                 $model_with_fk_to_wp_users    = EE_Registry::instance()->load_model($last_model_name);
974
-                $model_chain_to_wp_user       = $this->_model_chain_to_wp_user . '.';
974
+                $model_chain_to_wp_user       = $this->_model_chain_to_wp_user.'.';
975 975
             } else {
976 976
                 $model_with_fk_to_wp_users = $this;
977 977
                 $model_chain_to_wp_user    = '';
978 978
             }
979 979
             $wp_user_field = $model_with_fk_to_wp_users->get_foreign_key_to('WP_User');
980
-            return $model_chain_to_wp_user . $wp_user_field->get_name();
980
+            return $model_chain_to_wp_user.$wp_user_field->get_name();
981 981
         } catch (EE_Error $e) {
982 982
             return false;
983 983
         }
@@ -1052,11 +1052,11 @@  discard block
 block discarded – undo
1052 1052
         if ($this->_custom_selections instanceof CustomSelects) {
1053 1053
             $custom_expressions = $this->_custom_selections->columnsToSelectExpression();
1054 1054
             $select_expressions .= $select_expressions
1055
-                ? ', ' . $custom_expressions
1055
+                ? ', '.$custom_expressions
1056 1056
                 : $custom_expressions;
1057 1057
         }
1058 1058
 
1059
-        $SQL = "SELECT $select_expressions " . $this->_construct_2nd_half_of_select_query($model_query_info);
1059
+        $SQL = "SELECT $select_expressions ".$this->_construct_2nd_half_of_select_query($model_query_info);
1060 1060
         return $this->_do_wpdb_query('get_results', [$SQL, $output]);
1061 1061
     }
1062 1062
 
@@ -1073,7 +1073,7 @@  discard block
 block discarded – undo
1073 1073
      */
1074 1074
     protected function getCustomSelection(array $query_params, $columns_to_select = null)
1075 1075
     {
1076
-        if (! isset($query_params['extra_selects']) && $columns_to_select === null) {
1076
+        if ( ! isset($query_params['extra_selects']) && $columns_to_select === null) {
1077 1077
             return null;
1078 1078
         }
1079 1079
         $selects = isset($query_params['extra_selects']) ? $query_params['extra_selects'] : $columns_to_select;
@@ -1121,7 +1121,7 @@  discard block
 block discarded – undo
1121 1121
         if (is_array($columns_to_select)) {
1122 1122
             $select_sql_array = [];
1123 1123
             foreach ($columns_to_select as $alias => $selection_and_datatype) {
1124
-                if (! is_array($selection_and_datatype) || ! isset($selection_and_datatype[1])) {
1124
+                if ( ! is_array($selection_and_datatype) || ! isset($selection_and_datatype[1])) {
1125 1125
                     throw new EE_Error(
1126 1126
                         sprintf(
1127 1127
                             esc_html__(
@@ -1133,7 +1133,7 @@  discard block
 block discarded – undo
1133 1133
                         )
1134 1134
                     );
1135 1135
                 }
1136
-                if (! in_array($selection_and_datatype[1], $this->_valid_wpdb_data_types, true)) {
1136
+                if ( ! in_array($selection_and_datatype[1], $this->_valid_wpdb_data_types, true)) {
1137 1137
                     throw new EE_Error(
1138 1138
                         sprintf(
1139 1139
                             esc_html__(
@@ -1189,7 +1189,7 @@  discard block
 block discarded – undo
1189 1189
                 ['default_where_conditions' => EEM_Base::default_where_conditions_minimum_all]
1190 1190
             )
1191 1191
         );
1192
-        $className    = $this->_get_class_name();
1192
+        $className = $this->_get_class_name();
1193 1193
         if ($model_object instanceof $className) {
1194 1194
             // make sure valid objects get added to the entity map
1195 1195
             // so that the next call to this method doesn't trigger another trip to the db
@@ -1212,12 +1212,12 @@  discard block
 block discarded – undo
1212 1212
      */
1213 1213
     public function alter_query_params_to_restrict_by_ID($id, $query_params = [])
1214 1214
     {
1215
-        if (! isset($query_params[0])) {
1215
+        if ( ! isset($query_params[0])) {
1216 1216
             $query_params[0] = [];
1217 1217
         }
1218 1218
         $conditions_from_id = $this->parse_index_primary_key_string($id);
1219 1219
         if ($conditions_from_id === null) {
1220
-            $query_params[0][ $this->primary_key_name() ] = $id;
1220
+            $query_params[0][$this->primary_key_name()] = $id;
1221 1221
         } else {
1222 1222
             // no primary key, so the $id must be from the get_index_primary_key_string()
1223 1223
             $query_params[0] = array_replace_recursive($query_params[0], $this->parse_index_primary_key_string($id));
@@ -1236,7 +1236,7 @@  discard block
 block discarded – undo
1236 1236
      */
1237 1237
     public function get_one($query_params = [])
1238 1238
     {
1239
-        if (! is_array($query_params)) {
1239
+        if ( ! is_array($query_params)) {
1240 1240
             EE_Error::doing_it_wrong(
1241 1241
                 'EEM_Base::get_one',
1242 1242
                 sprintf(
@@ -1436,7 +1436,7 @@  discard block
 block discarded – undo
1436 1436
                 return [];
1437 1437
             }
1438 1438
         }
1439
-        if (! is_array($query_params)) {
1439
+        if ( ! is_array($query_params)) {
1440 1440
             EE_Error::doing_it_wrong(
1441 1441
                 'EEM_Base::_get_consecutive',
1442 1442
                 sprintf(
@@ -1448,7 +1448,7 @@  discard block
 block discarded – undo
1448 1448
             $query_params = [];
1449 1449
         }
1450 1450
         // let's add the where query param for consecutive look up.
1451
-        $query_params[0][ $field_to_order_by ] = [$operand, $current_field_value];
1451
+        $query_params[0][$field_to_order_by] = [$operand, $current_field_value];
1452 1452
         $query_params['limit']                 = $limit;
1453 1453
         // set direction
1454 1454
         $incoming_orderby         = isset($query_params['order_by']) ? (array) $query_params['order_by'] : [];
@@ -1526,7 +1526,7 @@  discard block
 block discarded – undo
1526 1526
     {
1527 1527
         $field_settings = $this->field_settings_for($field_name);
1528 1528
         // if not a valid EE_Datetime_Field then throw error
1529
-        if (! $field_settings instanceof EE_Datetime_Field) {
1529
+        if ( ! $field_settings instanceof EE_Datetime_Field) {
1530 1530
             throw new EE_Error(
1531 1531
                 sprintf(
1532 1532
                     esc_html__(
@@ -1677,7 +1677,7 @@  discard block
 block discarded – undo
1677 1677
      */
1678 1678
     public function update($fields_n_values, $query_params, $keep_model_objs_in_sync = true)
1679 1679
     {
1680
-        if (! is_array($query_params)) {
1680
+        if ( ! is_array($query_params)) {
1681 1681
             EE_Error::doing_it_wrong(
1682 1682
                 'EEM_Base::update',
1683 1683
                 sprintf(
@@ -1725,7 +1725,7 @@  discard block
 block discarded – undo
1725 1725
             $wpdb_result = (array) $wpdb_result;
1726 1726
             // get the model object's PK, as we'll want this if we need to insert a row into secondary tables
1727 1727
             if ($this->has_primary_key_field()) {
1728
-                $main_table_pk_value = $wpdb_result[ $this->get_primary_key_field()->get_qualified_column() ];
1728
+                $main_table_pk_value = $wpdb_result[$this->get_primary_key_field()->get_qualified_column()];
1729 1729
             } else {
1730 1730
                 // 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)
1731 1731
                 $main_table_pk_value = null;
@@ -1741,7 +1741,7 @@  discard block
 block discarded – undo
1741 1741
                     // in this table, right? so insert a row in the current table, using any fields available
1742 1742
                     if (
1743 1743
                         ! (array_key_exists($this_table_pk_column, $wpdb_result)
1744
-                           && $wpdb_result[ $this_table_pk_column ])
1744
+                           && $wpdb_result[$this_table_pk_column])
1745 1745
                     ) {
1746 1746
                         $success = $this->_insert_into_specific_table(
1747 1747
                             $table_obj,
@@ -1749,7 +1749,7 @@  discard block
 block discarded – undo
1749 1749
                             $main_table_pk_value
1750 1750
                         );
1751 1751
                         // if we died here, report the error
1752
-                        if (! $success) {
1752
+                        if ( ! $success) {
1753 1753
                             return false;
1754 1754
                         }
1755 1755
                     }
@@ -1777,10 +1777,10 @@  discard block
 block discarded – undo
1777 1777
                 $model_objs_affected_ids     = [];
1778 1778
                 foreach ($models_affected_key_columns as $row) {
1779 1779
                     $combined_index_key                             = $this->get_index_primary_key_string($row);
1780
-                    $model_objs_affected_ids[ $combined_index_key ] = $combined_index_key;
1780
+                    $model_objs_affected_ids[$combined_index_key] = $combined_index_key;
1781 1781
                 }
1782 1782
             }
1783
-            if (! $model_objs_affected_ids) {
1783
+            if ( ! $model_objs_affected_ids) {
1784 1784
                 // wait wait wait- if nothing was affected let's stop here
1785 1785
                 return 0;
1786 1786
             }
@@ -1808,8 +1808,8 @@  discard block
 block discarded – undo
1808 1808
                             . " SET "
1809 1809
                             . $this->_construct_update_sql($fields_n_values)
1810 1810
                             . $model_query_info->get_where_sql(
1811
-                            );// note: doesn't use _construct_2nd_half_of_select_query() because doesn't accept LIMIT, ORDER BY, etc.
1812
-        $rows_affected    = $this->_do_wpdb_query('query', [$SQL]);
1811
+                            ); // note: doesn't use _construct_2nd_half_of_select_query() because doesn't accept LIMIT, ORDER BY, etc.
1812
+        $rows_affected = $this->_do_wpdb_query('query', [$SQL]);
1813 1813
         /**
1814 1814
          * Action called after a model update call has been made.
1815 1815
          *
@@ -1819,7 +1819,7 @@  discard block
 block discarded – undo
1819 1819
          * @param int      $rows_affected
1820 1820
          */
1821 1821
         do_action('AHEE__EEM_Base__update__end', $this, $fields_n_values, $query_params, $rows_affected);
1822
-        return $rows_affected;// how many supposedly got updated
1822
+        return $rows_affected; // how many supposedly got updated
1823 1823
     }
1824 1824
 
1825 1825
 
@@ -1851,7 +1851,7 @@  discard block
 block discarded – undo
1851 1851
         $model_query_info   = $this->_create_model_query_info_carrier($query_params);
1852 1852
         $select_expressions = $field->get_qualified_column();
1853 1853
         $SQL                =
1854
-            "SELECT $select_expressions " . $this->_construct_2nd_half_of_select_query($model_query_info);
1854
+            "SELECT $select_expressions ".$this->_construct_2nd_half_of_select_query($model_query_info);
1855 1855
         return $this->_do_wpdb_query('get_col', [$SQL]);
1856 1856
     }
1857 1857
 
@@ -1869,7 +1869,7 @@  discard block
 block discarded – undo
1869 1869
     {
1870 1870
         $query_params['limit'] = 1;
1871 1871
         $col                   = $this->get_col($query_params, $field_to_select);
1872
-        if (! empty($col)) {
1872
+        if ( ! empty($col)) {
1873 1873
             return reset($col);
1874 1874
         }
1875 1875
         return null;
@@ -1899,7 +1899,7 @@  discard block
 block discarded – undo
1899 1899
             $prepared_value  = $this->_prepare_value_or_use_default($field_obj, $fields_n_values);
1900 1900
             $value_sql       = $prepared_value === null ? 'NULL'
1901 1901
                 : $wpdb->prepare($field_obj->get_wpdb_data_type(), $prepared_value);
1902
-            $cols_n_values[] = $field_obj->get_qualified_column() . "=" . $value_sql;
1902
+            $cols_n_values[] = $field_obj->get_qualified_column()."=".$value_sql;
1903 1903
         }
1904 1904
         return implode(",", $cols_n_values);
1905 1905
     }
@@ -2033,7 +2033,7 @@  discard block
 block discarded – undo
2033 2033
                                 . $model_query_info->get_full_join_sql()
2034 2034
                                 . " WHERE "
2035 2035
                                 . $deletion_where_query_part;
2036
-            $rows_deleted     = $this->_do_wpdb_query('query', [$SQL]);
2036
+            $rows_deleted = $this->_do_wpdb_query('query', [$SQL]);
2037 2037
         } else {
2038 2038
             $rows_deleted = 0;
2039 2039
         }
@@ -2043,12 +2043,12 @@  discard block
 block discarded – undo
2043 2043
         if (
2044 2044
             $this->has_primary_key_field()
2045 2045
             && $rows_deleted !== false
2046
-            && isset($columns_and_ids_for_deleting[ $this->get_primary_key_field()->get_qualified_column() ])
2046
+            && isset($columns_and_ids_for_deleting[$this->get_primary_key_field()->get_qualified_column()])
2047 2047
         ) {
2048
-            $ids_for_removal = $columns_and_ids_for_deleting[ $this->get_primary_key_field()->get_qualified_column() ];
2048
+            $ids_for_removal = $columns_and_ids_for_deleting[$this->get_primary_key_field()->get_qualified_column()];
2049 2049
             foreach ($ids_for_removal as $id) {
2050
-                if (isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])) {
2051
-                    unset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ]);
2050
+                if (isset($this->_entity_map[EEM_Base::$_model_query_blog_id][$id])) {
2051
+                    unset($this->_entity_map[EEM_Base::$_model_query_blog_id][$id]);
2052 2052
                 }
2053 2053
             }
2054 2054
 
@@ -2085,7 +2085,7 @@  discard block
 block discarded – undo
2085 2085
          * @param int      $rows_deleted
2086 2086
          */
2087 2087
         do_action('AHEE__EEM_Base__delete__end', $this, $query_params, $rows_deleted, $columns_and_ids_for_deleting);
2088
-        return $rows_deleted;// how many supposedly got deleted
2088
+        return $rows_deleted; // how many supposedly got deleted
2089 2089
     }
2090 2090
 
2091 2091
 
@@ -2181,15 +2181,15 @@  discard block
 block discarded – undo
2181 2181
                 if (
2182 2182
                     $allow_blocking
2183 2183
                     && $this->delete_is_blocked_by_related_models(
2184
-                        $item_to_delete[ $primary_table->get_fully_qualified_pk_column() ]
2184
+                        $item_to_delete[$primary_table->get_fully_qualified_pk_column()]
2185 2185
                     )
2186 2186
                 ) {
2187 2187
                     continue;
2188 2188
                 }
2189 2189
                 // primary table deletes
2190
-                if (isset($item_to_delete[ $primary_table->get_fully_qualified_pk_column() ])) {
2191
-                    $ids_to_delete_indexed_by_column[ $primary_table->get_fully_qualified_pk_column() ][] =
2192
-                        $item_to_delete[ $primary_table->get_fully_qualified_pk_column() ];
2190
+                if (isset($item_to_delete[$primary_table->get_fully_qualified_pk_column()])) {
2191
+                    $ids_to_delete_indexed_by_column[$primary_table->get_fully_qualified_pk_column()][] =
2192
+                        $item_to_delete[$primary_table->get_fully_qualified_pk_column()];
2193 2193
                 }
2194 2194
             }
2195 2195
         } elseif (count($this->get_combined_primary_key_fields()) > 1) {
@@ -2198,8 +2198,8 @@  discard block
 block discarded – undo
2198 2198
                 $ids_to_delete_indexed_by_column_for_row = [];
2199 2199
                 foreach ($fields as $cpk_field) {
2200 2200
                     if ($cpk_field instanceof EE_Model_Field_Base) {
2201
-                        $ids_to_delete_indexed_by_column_for_row[ $cpk_field->get_qualified_column() ] =
2202
-                            $item_to_delete[ $cpk_field->get_qualified_column() ];
2201
+                        $ids_to_delete_indexed_by_column_for_row[$cpk_field->get_qualified_column()] =
2202
+                            $item_to_delete[$cpk_field->get_qualified_column()];
2203 2203
                     }
2204 2204
                 }
2205 2205
                 $ids_to_delete_indexed_by_column[] = $ids_to_delete_indexed_by_column_for_row;
@@ -2237,7 +2237,7 @@  discard block
 block discarded – undo
2237 2237
         } elseif ($this->has_primary_key_field()) {
2238 2238
             $query = [];
2239 2239
             foreach ($ids_to_delete_indexed_by_column as $column => $ids) {
2240
-                $query[] = $column . ' IN' . $this->_construct_in_value($ids, $this->_primary_key_field);
2240
+                $query[] = $column.' IN'.$this->_construct_in_value($ids, $this->_primary_key_field);
2241 2241
             }
2242 2242
             $query_part = ! empty($query) ? implode(' AND ', $query) : $query_part;
2243 2243
         } elseif (count($this->get_combined_primary_key_fields()) > 1) {
@@ -2245,7 +2245,7 @@  discard block
 block discarded – undo
2245 2245
             foreach ($ids_to_delete_indexed_by_column as $ids_to_delete_indexed_by_column_for_each_row) {
2246 2246
                 $values_for_each_combined_primary_key_for_a_row = [];
2247 2247
                 foreach ($ids_to_delete_indexed_by_column_for_each_row as $column => $id) {
2248
-                    $values_for_each_combined_primary_key_for_a_row[] = $column . '=' . $id;
2248
+                    $values_for_each_combined_primary_key_for_a_row[] = $column.'='.$id;
2249 2249
                 }
2250 2250
                 $ways_to_identify_a_row[] = '('
2251 2251
                                             . implode(' AND ', $values_for_each_combined_primary_key_for_a_row)
@@ -2319,9 +2319,9 @@  discard block
 block discarded – undo
2319 2319
                 $column_to_count = '*';
2320 2320
             }
2321 2321
         }
2322
-        $column_to_count = $distinct ? "DISTINCT " . $column_to_count : $column_to_count;
2322
+        $column_to_count = $distinct ? "DISTINCT ".$column_to_count : $column_to_count;
2323 2323
         $SQL             =
2324
-            "SELECT COUNT(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2324
+            "SELECT COUNT(".$column_to_count.")".$this->_construct_2nd_half_of_select_query($model_query_info);
2325 2325
         return (int) $this->_do_wpdb_query('get_var', [$SQL]);
2326 2326
     }
2327 2327
 
@@ -2345,7 +2345,7 @@  discard block
 block discarded – undo
2345 2345
         }
2346 2346
         $column_to_count = $field_obj->get_qualified_column();
2347 2347
         $SQL             =
2348
-            "SELECT SUM(" . $column_to_count . ")" . $this->_construct_2nd_half_of_select_query($model_query_info);
2348
+            "SELECT SUM(".$column_to_count.")".$this->_construct_2nd_half_of_select_query($model_query_info);
2349 2349
         $return_value    = $this->_do_wpdb_query('get_var', [$SQL]);
2350 2350
         $data_type       = $field_obj->get_wpdb_data_type();
2351 2351
         if ($data_type === '%d' || $data_type === '%s') {
@@ -2371,7 +2371,7 @@  discard block
 block discarded – undo
2371 2371
         // if we're in maintenance mode level 2, DON'T run any queries
2372 2372
         // because level 2 indicates the database needs updating and
2373 2373
         // is probably out of sync with the code
2374
-        if (! EE_Maintenance_Mode::instance()->models_can_query()) {
2374
+        if ( ! EE_Maintenance_Mode::instance()->models_can_query()) {
2375 2375
             throw new EE_Error(
2376 2376
                 sprintf(
2377 2377
                     esc_html__(
@@ -2383,7 +2383,7 @@  discard block
 block discarded – undo
2383 2383
         }
2384 2384
         /** @type WPDB $wpdb */
2385 2385
         global $wpdb;
2386
-        if (! method_exists($wpdb, $wpdb_method)) {
2386
+        if ( ! method_exists($wpdb, $wpdb_method)) {
2387 2387
             throw new EE_Error(
2388 2388
                 sprintf(
2389 2389
                     esc_html__(
@@ -2402,7 +2402,7 @@  discard block
 block discarded – undo
2402 2402
         $this->show_db_query_if_previously_requested($wpdb->last_query);
2403 2403
         if (WP_DEBUG) {
2404 2404
             $wpdb->show_errors($old_show_errors_value);
2405
-            if (! empty($wpdb->last_error)) {
2405
+            if ( ! empty($wpdb->last_error)) {
2406 2406
                 throw new EE_Error(sprintf(esc_html__('WPDB Error: "%s"', 'event_espresso'), $wpdb->last_error));
2407 2407
             }
2408 2408
             if ($result === false) {
@@ -2472,7 +2472,7 @@  discard block
 block discarded – undo
2472 2472
                     // ummmm... you in trouble
2473 2473
                     return $result;
2474 2474
             }
2475
-            if (! empty($error_message)) {
2475
+            if ( ! empty($error_message)) {
2476 2476
                 EE_Log::instance()->log(__FILE__, __FUNCTION__, $error_message, 'error');
2477 2477
                 trigger_error($error_message);
2478 2478
             }
@@ -2549,11 +2549,11 @@  discard block
 block discarded – undo
2549 2549
      */
2550 2550
     private function _construct_2nd_half_of_select_query(EE_Model_Query_Info_Carrier $model_query_info)
2551 2551
     {
2552
-        return " FROM " . $model_query_info->get_full_join_sql() .
2553
-               $model_query_info->get_where_sql() .
2554
-               $model_query_info->get_group_by_sql() .
2555
-               $model_query_info->get_having_sql() .
2556
-               $model_query_info->get_order_by_sql() .
2552
+        return " FROM ".$model_query_info->get_full_join_sql().
2553
+               $model_query_info->get_where_sql().
2554
+               $model_query_info->get_group_by_sql().
2555
+               $model_query_info->get_having_sql().
2556
+               $model_query_info->get_order_by_sql().
2557 2557
                $model_query_info->get_limit_sql();
2558 2558
     }
2559 2559
 
@@ -2745,12 +2745,12 @@  discard block
 block discarded – undo
2745 2745
         $related_model = $this->get_related_model_obj($model_name);
2746 2746
         // we're just going to use the query params on the related model's normal get_all query,
2747 2747
         // except add a condition to say to match the current mod
2748
-        if (! isset($query_params['default_where_conditions'])) {
2748
+        if ( ! isset($query_params['default_where_conditions'])) {
2749 2749
             $query_params['default_where_conditions'] = EEM_Base::default_where_conditions_none;
2750 2750
         }
2751 2751
         $this_model_name                                                 = $this->get_this_model_name();
2752 2752
         $this_pk_field_name                                              = $this->get_primary_key_field()->get_name();
2753
-        $query_params[0][ $this_model_name . "." . $this_pk_field_name ] = $id_or_obj;
2753
+        $query_params[0][$this_model_name.".".$this_pk_field_name] = $id_or_obj;
2754 2754
         return $related_model->count($query_params, $field_to_count, $distinct);
2755 2755
     }
2756 2756
 
@@ -2770,7 +2770,7 @@  discard block
 block discarded – undo
2770 2770
     public function sum_related($id_or_obj, $model_name, $query_params, $field_to_sum = null)
2771 2771
     {
2772 2772
         $related_model = $this->get_related_model_obj($model_name);
2773
-        if (! is_array($query_params)) {
2773
+        if ( ! is_array($query_params)) {
2774 2774
             EE_Error::doing_it_wrong(
2775 2775
                 'EEM_Base::sum_related',
2776 2776
                 sprintf(
@@ -2783,12 +2783,12 @@  discard block
 block discarded – undo
2783 2783
         }
2784 2784
         // we're just going to use the query params on the related model's normal get_all query,
2785 2785
         // except add a condition to say to match the current mod
2786
-        if (! isset($query_params['default_where_conditions'])) {
2786
+        if ( ! isset($query_params['default_where_conditions'])) {
2787 2787
             $query_params['default_where_conditions'] = EEM_Base::default_where_conditions_none;
2788 2788
         }
2789 2789
         $this_model_name                                                 = $this->get_this_model_name();
2790 2790
         $this_pk_field_name                                              = $this->get_primary_key_field()->get_name();
2791
-        $query_params[0][ $this_model_name . "." . $this_pk_field_name ] = $id_or_obj;
2791
+        $query_params[0][$this_model_name.".".$this_pk_field_name] = $id_or_obj;
2792 2792
         return $related_model->sum($query_params, $field_to_sum);
2793 2793
     }
2794 2794
 
@@ -2839,7 +2839,7 @@  discard block
 block discarded – undo
2839 2839
                 $field_with_model_name = $field;
2840 2840
             }
2841 2841
         }
2842
-        if (! isset($field_with_model_name) || ! $field_with_model_name) {
2842
+        if ( ! isset($field_with_model_name) || ! $field_with_model_name) {
2843 2843
             throw new EE_Error(
2844 2844
                 sprintf(
2845 2845
                     esc_html__("There is no EE_Any_Foreign_Model_Name field on model %s", "event_espresso"),
@@ -2976,14 +2976,14 @@  discard block
 block discarded – undo
2976 2976
                 || $this->get_primary_key_field()
2977 2977
                    instanceof
2978 2978
                    EE_Primary_Key_String_Field)
2979
-            && isset($fields_n_values[ $this->primary_key_name() ])
2979
+            && isset($fields_n_values[$this->primary_key_name()])
2980 2980
         ) {
2981
-            $query_params[0]['OR'][ $this->primary_key_name() ] = $fields_n_values[ $this->primary_key_name() ];
2981
+            $query_params[0]['OR'][$this->primary_key_name()] = $fields_n_values[$this->primary_key_name()];
2982 2982
         }
2983 2983
         foreach ($this->unique_indexes() as $unique_index_name => $unique_index) {
2984 2984
             $uniqueness_where_params                              =
2985 2985
                 array_intersect_key($fields_n_values, $unique_index->fields());
2986
-            $query_params[0]['OR'][ 'AND*' . $unique_index_name ] = $uniqueness_where_params;
2986
+            $query_params[0]['OR']['AND*'.$unique_index_name] = $uniqueness_where_params;
2987 2987
         }
2988 2988
         // if there is nothing to base this search on, then we shouldn't find anything
2989 2989
         if (empty($query_params)) {
@@ -3058,16 +3058,16 @@  discard block
 block discarded – undo
3058 3058
             $prepared_value = $this->_prepare_value_or_use_default($field_obj, $fields_n_values);
3059 3059
             // if the value we want to assign it to is NULL, just don't mention it for the insertion
3060 3060
             if ($prepared_value !== null) {
3061
-                $insertion_col_n_values[ $field_obj->get_table_column() ] = $prepared_value;
3061
+                $insertion_col_n_values[$field_obj->get_table_column()] = $prepared_value;
3062 3062
                 $format_for_insertion[]                                   = $field_obj->get_wpdb_data_type();
3063 3063
             }
3064 3064
         }
3065 3065
         if ($table instanceof EE_Secondary_Table && $new_id) {
3066 3066
             // its not the main table, so we should have already saved the main table's PK which we just inserted
3067 3067
             // so add the fk to the main table as a column
3068
-            $insertion_col_n_values[ $table->get_fk_on_table() ] = $new_id;
3068
+            $insertion_col_n_values[$table->get_fk_on_table()] = $new_id;
3069 3069
             $format_for_insertion[]                              =
3070
-                '%d';// yes right now we're only allowing these foreign keys to be INTs
3070
+                '%d'; // yes right now we're only allowing these foreign keys to be INTs
3071 3071
         }
3072 3072
 
3073 3073
         // insert the new entry
@@ -3085,7 +3085,7 @@  discard block
 block discarded – undo
3085 3085
             }
3086 3086
             // it's not an auto-increment primary key, so
3087 3087
             // it must have been supplied
3088
-            return $fields_n_values[ $this->get_primary_key_field()->get_name() ];
3088
+            return $fields_n_values[$this->get_primary_key_field()->get_name()];
3089 3089
         }
3090 3090
         // we can't return a  primary key because there is none. instead return
3091 3091
         // a unique string indicating this model
@@ -3107,10 +3107,10 @@  discard block
 block discarded – undo
3107 3107
     {
3108 3108
         $field_name = $field_obj->get_name();
3109 3109
         // if this field doesn't allow nullable, don't allow it
3110
-        if (! $field_obj->is_nullable() && ! isset($fields_n_values[ $field_name ])) {
3111
-            $fields_n_values[ $field_name ] = $field_obj->get_default_value();
3110
+        if ( ! $field_obj->is_nullable() && ! isset($fields_n_values[$field_name])) {
3111
+            $fields_n_values[$field_name] = $field_obj->get_default_value();
3112 3112
         }
3113
-        $unprepared_value = $fields_n_values[ $field_name ] ?? null;
3113
+        $unprepared_value = $fields_n_values[$field_name] ?? null;
3114 3114
         return $this->_prepare_value_for_use_in_db($unprepared_value, $field_obj);
3115 3115
     }
3116 3116
 
@@ -3211,7 +3211,7 @@  discard block
 block discarded – undo
3211 3211
      */
3212 3212
     public function get_table_obj_by_alias($table_alias = '')
3213 3213
     {
3214
-        return isset($this->_tables[ $table_alias ]) ? $this->_tables[ $table_alias ] : null;
3214
+        return isset($this->_tables[$table_alias]) ? $this->_tables[$table_alias] : null;
3215 3215
     }
3216 3216
 
3217 3217
 
@@ -3225,7 +3225,7 @@  discard block
 block discarded – undo
3225 3225
         $other_tables = [];
3226 3226
         foreach ($this->_tables as $table_alias => $table) {
3227 3227
             if ($table instanceof EE_Secondary_Table) {
3228
-                $other_tables[ $table_alias ] = $table;
3228
+                $other_tables[$table_alias] = $table;
3229 3229
             }
3230 3230
         }
3231 3231
         return $other_tables;
@@ -3240,7 +3240,7 @@  discard block
 block discarded – undo
3240 3240
      */
3241 3241
     public function _get_fields_for_table($table_alias)
3242 3242
     {
3243
-        return $this->_fields[ $table_alias ];
3243
+        return $this->_fields[$table_alias];
3244 3244
     }
3245 3245
 
3246 3246
 
@@ -3269,7 +3269,7 @@  discard block
 block discarded – undo
3269 3269
                     $query_info_carrier,
3270 3270
                     'group_by'
3271 3271
                 );
3272
-            } elseif (! empty($query_params['group_by'])) {
3272
+            } elseif ( ! empty($query_params['group_by'])) {
3273 3273
                 $this->_extract_related_model_info_from_query_param(
3274 3274
                     $query_params['group_by'],
3275 3275
                     $query_info_carrier,
@@ -3291,7 +3291,7 @@  discard block
 block discarded – undo
3291 3291
                     $query_info_carrier,
3292 3292
                     'order_by'
3293 3293
                 );
3294
-            } elseif (! empty($query_params['order_by'])) {
3294
+            } elseif ( ! empty($query_params['order_by'])) {
3295 3295
                 $this->_extract_related_model_info_from_query_param(
3296 3296
                     $query_params['order_by'],
3297 3297
                     $query_info_carrier,
@@ -3326,7 +3326,7 @@  discard block
 block discarded – undo
3326 3326
         EE_Model_Query_Info_Carrier $model_query_info_carrier,
3327 3327
         $query_param_type
3328 3328
     ) {
3329
-        if (! empty($sub_query_params)) {
3329
+        if ( ! empty($sub_query_params)) {
3330 3330
             $sub_query_params = (array) $sub_query_params;
3331 3331
             foreach ($sub_query_params as $param => $possibly_array_of_params) {
3332 3332
                 // $param could be simply 'EVT_ID', or it could be 'Registrations.REG_ID', or even 'Registrations.Transactions.Payments.PAY_amount'
@@ -3342,7 +3342,7 @@  discard block
 block discarded – undo
3342 3342
                 $query_param_sans_stars =
3343 3343
                     $this->_remove_stars_and_anything_after_from_condition_query_param_key($param);
3344 3344
                 if (in_array($query_param_sans_stars, $this->_logic_query_param_keys, true)) {
3345
-                    if (! is_array($possibly_array_of_params)) {
3345
+                    if ( ! is_array($possibly_array_of_params)) {
3346 3346
                         throw new EE_Error(
3347 3347
                             sprintf(
3348 3348
                                 esc_html__(
@@ -3368,7 +3368,7 @@  discard block
 block discarded – undo
3368 3368
                     // then $possible_array_of_params looks something like array('<','DTT_sold',true)
3369 3369
                     // indicating that $possible_array_of_params[1] is actually a field name,
3370 3370
                     // from which we should extract query parameters!
3371
-                    if (! isset($possibly_array_of_params[0], $possibly_array_of_params[1])) {
3371
+                    if ( ! isset($possibly_array_of_params[0], $possibly_array_of_params[1])) {
3372 3372
                         throw new EE_Error(
3373 3373
                             sprintf(
3374 3374
                                 esc_html__(
@@ -3408,8 +3408,8 @@  discard block
 block discarded – undo
3408 3408
         EE_Model_Query_Info_Carrier $model_query_info_carrier,
3409 3409
         $query_param_type
3410 3410
     ) {
3411
-        if (! empty($sub_query_params)) {
3412
-            if (! is_array($sub_query_params)) {
3411
+        if ( ! empty($sub_query_params)) {
3412
+            if ( ! is_array($sub_query_params)) {
3413 3413
                 throw new EE_Error(
3414 3414
                     sprintf(
3415 3415
                         esc_html__("Query parameter %s should be an array, but it isn't.", "event_espresso"),
@@ -3447,7 +3447,7 @@  discard block
 block discarded – undo
3447 3447
      */
3448 3448
     public function _create_model_query_info_carrier($query_params)
3449 3449
     {
3450
-        if (! is_array($query_params)) {
3450
+        if ( ! is_array($query_params)) {
3451 3451
             EE_Error::doing_it_wrong(
3452 3452
                 'EEM_Base::_create_model_query_info_carrier',
3453 3453
                 sprintf(
@@ -3478,7 +3478,7 @@  discard block
 block discarded – undo
3478 3478
             // only include if related to a cpt where no password has been set
3479 3479
             $query_params[0]['OR*nopassword'] = [
3480 3480
                 $where_param_key_for_password       => '',
3481
-                $where_param_key_for_password . '*' => ['IS_NULL'],
3481
+                $where_param_key_for_password.'*' => ['IS_NULL'],
3482 3482
             ];
3483 3483
         }
3484 3484
         $query_object = $this->_extract_related_models_from_query($query_params);
@@ -3532,7 +3532,7 @@  discard block
 block discarded – undo
3532 3532
         // set limit
3533 3533
         if (array_key_exists('limit', $query_params)) {
3534 3534
             if (is_array($query_params['limit'])) {
3535
-                if (! isset($query_params['limit'][0], $query_params['limit'][1])) {
3535
+                if ( ! isset($query_params['limit'][0], $query_params['limit'][1])) {
3536 3536
                     $e = sprintf(
3537 3537
                         esc_html__(
3538 3538
                             "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)",
@@ -3540,12 +3540,12 @@  discard block
 block discarded – undo
3540 3540
                         ),
3541 3541
                         http_build_query($query_params['limit'])
3542 3542
                     );
3543
-                    throw new EE_Error($e . "|" . $e);
3543
+                    throw new EE_Error($e."|".$e);
3544 3544
                 }
3545 3545
                 // they passed us an array for the limit. Assume it's like array(50,25), meaning offset by 50, and get 25
3546
-                $query_object->set_limit_sql(" LIMIT " . $query_params['limit'][0] . "," . $query_params['limit'][1]);
3547
-            } elseif (! empty($query_params['limit'])) {
3548
-                $query_object->set_limit_sql(" LIMIT " . $query_params['limit']);
3546
+                $query_object->set_limit_sql(" LIMIT ".$query_params['limit'][0].",".$query_params['limit'][1]);
3547
+            } elseif ( ! empty($query_params['limit'])) {
3548
+                $query_object->set_limit_sql(" LIMIT ".$query_params['limit']);
3549 3549
             }
3550 3550
         }
3551 3551
         // set order by
@@ -3577,10 +3577,10 @@  discard block
 block discarded – undo
3577 3577
                 $order_array = [];
3578 3578
                 foreach ($query_params['order_by'] as $field_name_to_order_by => $order) {
3579 3579
                     $order         = $this->_extract_order($order);
3580
-                    $order_array[] = $this->_deduce_column_name_from_query_param($field_name_to_order_by) . SP . $order;
3580
+                    $order_array[] = $this->_deduce_column_name_from_query_param($field_name_to_order_by).SP.$order;
3581 3581
                 }
3582
-                $query_object->set_order_by_sql(" ORDER BY " . implode(",", $order_array));
3583
-            } elseif (! empty($query_params['order_by'])) {
3582
+                $query_object->set_order_by_sql(" ORDER BY ".implode(",", $order_array));
3583
+            } elseif ( ! empty($query_params['order_by'])) {
3584 3584
                 $this->_extract_related_model_info_from_query_param(
3585 3585
                     $query_params['order_by'],
3586 3586
                     $query_object,
@@ -3591,7 +3591,7 @@  discard block
 block discarded – undo
3591 3591
                     ? $this->_extract_order($query_params['order'])
3592 3592
                     : 'DESC';
3593 3593
                 $query_object->set_order_by_sql(
3594
-                    " ORDER BY " . $this->_deduce_column_name_from_query_param($query_params['order_by']) . SP . $order
3594
+                    " ORDER BY ".$this->_deduce_column_name_from_query_param($query_params['order_by']).SP.$order
3595 3595
                 );
3596 3596
             }
3597 3597
         }
@@ -3603,7 +3603,7 @@  discard block
 block discarded – undo
3603 3603
         ) {
3604 3604
             $pk_field = $this->get_primary_key_field();
3605 3605
             $order    = $this->_extract_order($query_params['order']);
3606
-            $query_object->set_order_by_sql(" ORDER BY " . $pk_field->get_qualified_column() . SP . $order);
3606
+            $query_object->set_order_by_sql(" ORDER BY ".$pk_field->get_qualified_column().SP.$order);
3607 3607
         }
3608 3608
         // set group by
3609 3609
         if (array_key_exists('group_by', $query_params)) {
@@ -3613,10 +3613,10 @@  discard block
 block discarded – undo
3613 3613
                 foreach ($query_params['group_by'] as $field_name_to_group_by) {
3614 3614
                     $group_by_array[] = $this->_deduce_column_name_from_query_param($field_name_to_group_by);
3615 3615
                 }
3616
-                $query_object->set_group_by_sql(" GROUP BY " . implode(", ", $group_by_array));
3617
-            } elseif (! empty($query_params['group_by'])) {
3616
+                $query_object->set_group_by_sql(" GROUP BY ".implode(", ", $group_by_array));
3617
+            } elseif ( ! empty($query_params['group_by'])) {
3618 3618
                 $query_object->set_group_by_sql(
3619
-                    " GROUP BY " . $this->_deduce_column_name_from_query_param($query_params['group_by'])
3619
+                    " GROUP BY ".$this->_deduce_column_name_from_query_param($query_params['group_by'])
3620 3620
                 );
3621 3621
             }
3622 3622
         }
@@ -3626,7 +3626,7 @@  discard block
 block discarded – undo
3626 3626
         }
3627 3627
         // now, just verify they didn't pass anything wack
3628 3628
         foreach ($query_params as $query_key => $query_value) {
3629
-            if (! in_array($query_key, $this->_allowed_query_params, true)) {
3629
+            if ( ! in_array($query_key, $this->_allowed_query_params, true)) {
3630 3630
                 throw new EE_Error(
3631 3631
                     sprintf(
3632 3632
                         esc_html__(
@@ -3732,7 +3732,7 @@  discard block
 block discarded – undo
3732 3732
         $where_query_params = []
3733 3733
     ) {
3734 3734
         $allowed_used_default_where_conditions_values = EEM_Base::valid_default_where_conditions();
3735
-        if (! in_array($use_default_where_conditions, $allowed_used_default_where_conditions_values)) {
3735
+        if ( ! in_array($use_default_where_conditions, $allowed_used_default_where_conditions_values)) {
3736 3736
             throw new EE_Error(
3737 3737
                 sprintf(
3738 3738
                     esc_html__(
@@ -3762,7 +3762,7 @@  discard block
 block discarded – undo
3762 3762
                 // we don't want to add full or even minimum default where conditions from this model, so just continue
3763 3763
                 continue;
3764 3764
             }
3765
-            $overrides              = $this->_override_defaults_or_make_null_friendly(
3765
+            $overrides = $this->_override_defaults_or_make_null_friendly(
3766 3766
                 $related_model_universal_where_params,
3767 3767
                 $where_query_params,
3768 3768
                 $related_model,
@@ -3871,19 +3871,19 @@  discard block
 block discarded – undo
3871 3871
     ) {
3872 3872
         $null_friendly_where_conditions = [];
3873 3873
         $none_overridden                = true;
3874
-        $or_condition_key_for_defaults  = 'OR*' . get_class($model);
3874
+        $or_condition_key_for_defaults  = 'OR*'.get_class($model);
3875 3875
         foreach ($default_where_conditions as $key => $val) {
3876
-            if (isset($provided_where_conditions[ $key ])) {
3876
+            if (isset($provided_where_conditions[$key])) {
3877 3877
                 $none_overridden = false;
3878 3878
             } else {
3879
-                $null_friendly_where_conditions[ $or_condition_key_for_defaults ]['AND'][ $key ] = $val;
3879
+                $null_friendly_where_conditions[$or_condition_key_for_defaults]['AND'][$key] = $val;
3880 3880
             }
3881 3881
         }
3882 3882
         if ($none_overridden && $default_where_conditions) {
3883 3883
             if ($model->has_primary_key_field()) {
3884
-                $null_friendly_where_conditions[ $or_condition_key_for_defaults ][ $model_relation_path
3884
+                $null_friendly_where_conditions[$or_condition_key_for_defaults][$model_relation_path
3885 3885
                                                                                    . "."
3886
-                                                                                   . $model->primary_key_name() ] =
3886
+                                                                                   . $model->primary_key_name()] =
3887 3887
                     ['IS NULL'];
3888 3888
             }/*else{
3889 3889
                 //@todo NO PK, use other defaults
@@ -3994,7 +3994,7 @@  discard block
 block discarded – undo
3994 3994
             foreach ($tables as $table_obj) {
3995 3995
                 $qualified_pk_column = $table_alias_with_model_relation_chain_prefix
3996 3996
                                        . $table_obj->get_fully_qualified_pk_column();
3997
-                if (! in_array($qualified_pk_column, $selects)) {
3997
+                if ( ! in_array($qualified_pk_column, $selects)) {
3998 3998
                     $selects[] = "$qualified_pk_column AS '$qualified_pk_column'";
3999 3999
                 }
4000 4000
             }
@@ -4146,9 +4146,9 @@  discard block
 block discarded – undo
4146 4146
         $query_parameter_type
4147 4147
     ) {
4148 4148
         foreach ($this->_model_relations as $valid_related_model_name => $relation_obj) {
4149
-            if (strpos($possible_join_string, $valid_related_model_name . ".") === 0) {
4149
+            if (strpos($possible_join_string, $valid_related_model_name.".") === 0) {
4150 4150
                 $this->_add_join_to_model($valid_related_model_name, $query_info_carrier, $original_query_param);
4151
-                $possible_join_string = substr($possible_join_string, strlen($valid_related_model_name . "."));
4151
+                $possible_join_string = substr($possible_join_string, strlen($valid_related_model_name."."));
4152 4152
                 if ($possible_join_string === '') {
4153 4153
                     // nothing left to $query_param
4154 4154
                     // we should actually end in a field name, not a model like this!
@@ -4281,7 +4281,7 @@  discard block
 block discarded – undo
4281 4281
     {
4282 4282
         $SQL = $this->_construct_condition_clause_recursive($where_params, ' AND ');
4283 4283
         if ($SQL) {
4284
-            return " WHERE " . $SQL;
4284
+            return " WHERE ".$SQL;
4285 4285
         }
4286 4286
         return '';
4287 4287
     }
@@ -4299,7 +4299,7 @@  discard block
 block discarded – undo
4299 4299
     {
4300 4300
         $SQL = $this->_construct_condition_clause_recursive($having_params, ' AND ');
4301 4301
         if ($SQL) {
4302
-            return " HAVING " . $SQL;
4302
+            return " HAVING ".$SQL;
4303 4303
         }
4304 4304
         return '';
4305 4305
     }
@@ -4353,7 +4353,7 @@  discard block
 block discarded – undo
4353 4353
             } else {
4354 4354
                 $field_obj = $this->_deduce_field_from_query_param($query_param);
4355 4355
                 // if it's not a normal field, maybe it's a custom selection?
4356
-                if (! $field_obj) {
4356
+                if ( ! $field_obj) {
4357 4357
                     if ($this->_custom_selections instanceof CustomSelects) {
4358 4358
                         $field_obj = $this->_custom_selections->getDataTypeForAlias($query_param);
4359 4359
                     } else {
@@ -4369,7 +4369,7 @@  discard block
 block discarded – undo
4369 4369
                     }
4370 4370
                 }
4371 4371
                 $op_and_value_sql = $this->_construct_op_and_value($op_and_value_or_sub_condition, $field_obj);
4372
-                $where_clauses[]  = $this->_deduce_column_name_from_query_param($query_param) . SP . $op_and_value_sql;
4372
+                $where_clauses[]  = $this->_deduce_column_name_from_query_param($query_param).SP.$op_and_value_sql;
4373 4373
             }
4374 4374
         }
4375 4375
         return $where_clauses ? implode($glue, $where_clauses) : '';
@@ -4391,7 +4391,7 @@  discard block
 block discarded – undo
4391 4391
                 $field->get_model_name(),
4392 4392
                 $query_param
4393 4393
             );
4394
-            return $table_alias_prefix . $field->get_qualified_column();
4394
+            return $table_alias_prefix.$field->get_qualified_column();
4395 4395
         }
4396 4396
         if (
4397 4397
             $this->_custom_selections instanceof CustomSelects
@@ -4449,7 +4449,7 @@  discard block
 block discarded – undo
4449 4449
     {
4450 4450
         if (is_array($op_and_value)) {
4451 4451
             $operator = isset($op_and_value[0]) ? $this->_prepare_operator_for_sql($op_and_value[0]) : null;
4452
-            if (! $operator) {
4452
+            if ( ! $operator) {
4453 4453
                 $php_array_like_string = [];
4454 4454
                 foreach ($op_and_value as $key => $value) {
4455 4455
                     $php_array_like_string[] = "$key=>$value";
@@ -4471,14 +4471,14 @@  discard block
 block discarded – undo
4471 4471
         }
4472 4472
         // check to see if the value is actually another field
4473 4473
         if (is_array($op_and_value) && isset($op_and_value[2]) && $op_and_value[2] == true) {
4474
-            return $operator . SP . $this->_deduce_column_name_from_query_param($value);
4474
+            return $operator.SP.$this->_deduce_column_name_from_query_param($value);
4475 4475
         }
4476 4476
         if (in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4477 4477
             // in this case, the value should be an array, or at least a comma-separated list
4478 4478
             // it will need to handle a little differently
4479 4479
             $cleaned_value = $this->_construct_in_value($value, $field_obj);
4480 4480
             // note: $cleaned_value has already been run through $wpdb->prepare()
4481
-            return $operator . SP . $cleaned_value;
4481
+            return $operator.SP.$cleaned_value;
4482 4482
         }
4483 4483
         if (in_array($operator, $this->valid_between_style_operators()) && is_array($value)) {
4484 4484
             // the value should be an array with count of two.
@@ -4494,7 +4494,7 @@  discard block
 block discarded – undo
4494 4494
                 );
4495 4495
             }
4496 4496
             $cleaned_value = $this->_construct_between_value($value, $field_obj);
4497
-            return $operator . SP . $cleaned_value;
4497
+            return $operator.SP.$cleaned_value;
4498 4498
         }
4499 4499
         if (in_array($operator, $this->valid_null_style_operators())) {
4500 4500
             if ($value !== null) {
@@ -4514,10 +4514,10 @@  discard block
 block discarded – undo
4514 4514
         if (in_array($operator, $this->valid_like_style_operators()) && ! is_array($value)) {
4515 4515
             // if the operator is 'LIKE', we want to allow percent signs (%) and not
4516 4516
             // remove other junk. So just treat it as a string.
4517
-            return $operator . SP . $this->_wpdb_prepare_using_field($value, '%s');
4517
+            return $operator.SP.$this->_wpdb_prepare_using_field($value, '%s');
4518 4518
         }
4519
-        if (! in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4520
-            return $operator . SP . $this->_wpdb_prepare_using_field($value, $field_obj);
4519
+        if ( ! in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4520
+            return $operator.SP.$this->_wpdb_prepare_using_field($value, $field_obj);
4521 4521
         }
4522 4522
         if (in_array($operator, $this->valid_in_style_operators()) && ! is_array($value)) {
4523 4523
             throw new EE_Error(
@@ -4531,7 +4531,7 @@  discard block
 block discarded – undo
4531 4531
                 )
4532 4532
             );
4533 4533
         }
4534
-        if (! in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4534
+        if ( ! in_array($operator, $this->valid_in_style_operators()) && is_array($value)) {
4535 4535
             throw new EE_Error(
4536 4536
                 sprintf(
4537 4537
                     esc_html__(
@@ -4570,7 +4570,7 @@  discard block
 block discarded – undo
4570 4570
         foreach ($values as $value) {
4571 4571
             $cleaned_values[] = $this->_wpdb_prepare_using_field($value, $field_obj);
4572 4572
         }
4573
-        return $cleaned_values[0] . " AND " . $cleaned_values[1];
4573
+        return $cleaned_values[0]." AND ".$cleaned_values[1];
4574 4574
     }
4575 4575
 
4576 4576
 
@@ -4608,7 +4608,7 @@  discard block
 block discarded – undo
4608 4608
             $main_table  = $this->_get_main_table();
4609 4609
             $prepped[]   = "SELECT {$first_field->get_table_column()} FROM {$main_table->get_table_name()} WHERE FALSE";
4610 4610
         }
4611
-        return '(' . implode(',', $prepped) . ')';
4611
+        return '('.implode(',', $prepped).')';
4612 4612
     }
4613 4613
 
4614 4614
 
@@ -4628,7 +4628,7 @@  discard block
 block discarded – undo
4628 4628
                 $this->_prepare_value_for_use_in_db($value, $field_obj)
4629 4629
             );
4630 4630
         } //$field_obj should really just be a data type
4631
-        if (! in_array($field_obj, $this->_valid_wpdb_data_types)) {
4631
+        if ( ! in_array($field_obj, $this->_valid_wpdb_data_types)) {
4632 4632
             throw new EE_Error(
4633 4633
                 sprintf(
4634 4634
                     esc_html__("%s is not a valid wpdb datatype. Valid ones are %s", "event_espresso"),
@@ -4665,14 +4665,14 @@  discard block
 block discarded – undo
4665 4665
             );
4666 4666
         }
4667 4667
         $number_of_parts       = count($query_param_parts);
4668
-        $last_query_param_part = $query_param_parts[ count($query_param_parts) - 1 ];
4668
+        $last_query_param_part = $query_param_parts[count($query_param_parts) - 1];
4669 4669
         if ($number_of_parts === 1) {
4670 4670
             $field_name = $last_query_param_part;
4671 4671
             $model_obj  = $this;
4672 4672
         } else {// $number_of_parts >= 2
4673 4673
             // the last part is the column name, and there are only 2parts. therefore...
4674 4674
             $field_name = $last_query_param_part;
4675
-            $model_obj  = $this->get_related_model_obj($query_param_parts[ $number_of_parts - 2 ]);
4675
+            $model_obj  = $this->get_related_model_obj($query_param_parts[$number_of_parts - 2]);
4676 4676
         }
4677 4677
         try {
4678 4678
             return $model_obj->field_settings_for($field_name);
@@ -4693,7 +4693,7 @@  discard block
 block discarded – undo
4693 4693
     public function _get_qualified_column_for_field($field_name)
4694 4694
     {
4695 4695
         $all_fields = $this->field_settings();
4696
-        $field      = isset($all_fields[ $field_name ]) ? $all_fields[ $field_name ] : false;
4696
+        $field      = isset($all_fields[$field_name]) ? $all_fields[$field_name] : false;
4697 4697
         if ($field) {
4698 4698
             return $field->get_qualified_column();
4699 4699
         }
@@ -4763,10 +4763,10 @@  discard block
 block discarded – undo
4763 4763
      */
4764 4764
     public function get_qualified_columns_for_all_fields($model_relation_chain = '', $return_string = true)
4765 4765
     {
4766
-        $table_prefix      = str_replace('.', '__', $model_relation_chain) . (empty($model_relation_chain) ? '' : '__');
4766
+        $table_prefix      = str_replace('.', '__', $model_relation_chain).(empty($model_relation_chain) ? '' : '__');
4767 4767
         $qualified_columns = [];
4768 4768
         foreach ($this->field_settings() as $field_name => $field) {
4769
-            $qualified_columns[] = $table_prefix . $field->get_qualified_column();
4769
+            $qualified_columns[] = $table_prefix.$field->get_qualified_column();
4770 4770
         }
4771 4771
         return $return_string ? implode(', ', $qualified_columns) : $qualified_columns;
4772 4772
     }
@@ -4791,11 +4791,11 @@  discard block
 block discarded – undo
4791 4791
             if ($table_obj instanceof EE_Primary_Table) {
4792 4792
                 $SQL .= $table_alias === $table_obj->get_table_alias()
4793 4793
                     ? $table_obj->get_select_join_limit($limit)
4794
-                    : SP . $table_obj->get_table_name() . " AS " . $table_obj->get_table_alias() . SP;
4794
+                    : SP.$table_obj->get_table_name()." AS ".$table_obj->get_table_alias().SP;
4795 4795
             } elseif ($table_obj instanceof EE_Secondary_Table) {
4796 4796
                 $SQL .= $table_alias === $table_obj->get_table_alias()
4797 4797
                     ? $table_obj->get_select_join_limit_join($limit)
4798
-                    : SP . $table_obj->get_join_sql($table_alias) . SP;
4798
+                    : SP.$table_obj->get_join_sql($table_alias).SP;
4799 4799
             }
4800 4800
         }
4801 4801
         return $SQL;
@@ -4866,7 +4866,7 @@  discard block
 block discarded – undo
4866 4866
         foreach ($this->field_settings() as $field_obj) {
4867 4867
             // $data_types[$field_obj->get_table_column()] = $field_obj->get_wpdb_data_type();
4868 4868
             /** @var $field_obj EE_Model_Field_Base */
4869
-            $data_types[ $field_obj->get_qualified_column() ] = $field_obj->get_wpdb_data_type();
4869
+            $data_types[$field_obj->get_qualified_column()] = $field_obj->get_wpdb_data_type();
4870 4870
         }
4871 4871
         return $data_types;
4872 4872
     }
@@ -4881,8 +4881,8 @@  discard block
 block discarded – undo
4881 4881
      */
4882 4882
     public function get_related_model_obj($model_name)
4883 4883
     {
4884
-        $model_classname = "EEM_" . $model_name;
4885
-        if (! class_exists($model_classname)) {
4884
+        $model_classname = "EEM_".$model_name;
4885
+        if ( ! class_exists($model_classname)) {
4886 4886
             throw new EE_Error(
4887 4887
                 sprintf(
4888 4888
                     esc_html__(
@@ -4894,7 +4894,7 @@  discard block
 block discarded – undo
4894 4894
                 )
4895 4895
             );
4896 4896
         }
4897
-        return call_user_func($model_classname . "::instance");
4897
+        return call_user_func($model_classname."::instance");
4898 4898
     }
4899 4899
 
4900 4900
 
@@ -4921,7 +4921,7 @@  discard block
 block discarded – undo
4921 4921
         $belongs_to_relations = [];
4922 4922
         foreach ($this->relation_settings() as $model_name => $relation_obj) {
4923 4923
             if ($relation_obj instanceof EE_Belongs_To_Relation) {
4924
-                $belongs_to_relations[ $model_name ] = $relation_obj;
4924
+                $belongs_to_relations[$model_name] = $relation_obj;
4925 4925
             }
4926 4926
         }
4927 4927
         return $belongs_to_relations;
@@ -4938,7 +4938,7 @@  discard block
 block discarded – undo
4938 4938
     public function related_settings_for($relation_name)
4939 4939
     {
4940 4940
         $relatedModels = $this->relation_settings();
4941
-        if (! array_key_exists($relation_name, $relatedModels)) {
4941
+        if ( ! array_key_exists($relation_name, $relatedModels)) {
4942 4942
             throw new EE_Error(
4943 4943
                 sprintf(
4944 4944
                     esc_html__(
@@ -4951,7 +4951,7 @@  discard block
 block discarded – undo
4951 4951
                 )
4952 4952
             );
4953 4953
         }
4954
-        return $relatedModels[ $relation_name ];
4954
+        return $relatedModels[$relation_name];
4955 4955
     }
4956 4956
 
4957 4957
 
@@ -4967,7 +4967,7 @@  discard block
 block discarded – undo
4967 4967
     public function field_settings_for($fieldName, $include_db_only_fields = true)
4968 4968
     {
4969 4969
         $fieldSettings = $this->field_settings($include_db_only_fields);
4970
-        if (! array_key_exists($fieldName, $fieldSettings)) {
4970
+        if ( ! array_key_exists($fieldName, $fieldSettings)) {
4971 4971
             throw new EE_Error(
4972 4972
                 sprintf(
4973 4973
                     esc_html__("There is no field/column '%s' on '%s'", 'event_espresso'),
@@ -4976,7 +4976,7 @@  discard block
 block discarded – undo
4976 4976
                 )
4977 4977
             );
4978 4978
         }
4979
-        return $fieldSettings[ $fieldName ];
4979
+        return $fieldSettings[$fieldName];
4980 4980
     }
4981 4981
 
4982 4982
 
@@ -4989,7 +4989,7 @@  discard block
 block discarded – undo
4989 4989
     public function has_field($fieldName)
4990 4990
     {
4991 4991
         $fieldSettings = $this->field_settings(true);
4992
-        if (isset($fieldSettings[ $fieldName ])) {
4992
+        if (isset($fieldSettings[$fieldName])) {
4993 4993
             return true;
4994 4994
         }
4995 4995
         return false;
@@ -5005,7 +5005,7 @@  discard block
 block discarded – undo
5005 5005
     public function has_relation($relation_name)
5006 5006
     {
5007 5007
         $relations = $this->relation_settings();
5008
-        if (isset($relations[ $relation_name ])) {
5008
+        if (isset($relations[$relation_name])) {
5009 5009
             return true;
5010 5010
         }
5011 5011
         return false;
@@ -5041,7 +5041,7 @@  discard block
 block discarded – undo
5041 5041
                     break;
5042 5042
                 }
5043 5043
             }
5044
-            if (! $this->_primary_key_field instanceof EE_Primary_Key_Field_Base) {
5044
+            if ( ! $this->_primary_key_field instanceof EE_Primary_Key_Field_Base) {
5045 5045
                 throw new EE_Error(
5046 5046
                     sprintf(
5047 5047
                         esc_html__("There is no Primary Key defined on model %s", 'event_espresso'),
@@ -5101,17 +5101,17 @@  discard block
 block discarded – undo
5101 5101
      */
5102 5102
     public function get_foreign_key_to($model_name)
5103 5103
     {
5104
-        if (! isset($this->_cache_foreign_key_to_fields[ $model_name ])) {
5104
+        if ( ! isset($this->_cache_foreign_key_to_fields[$model_name])) {
5105 5105
             foreach ($this->field_settings() as $field) {
5106 5106
                 if (
5107 5107
                     $field instanceof EE_Foreign_Key_Field_Base
5108 5108
                     && in_array($model_name, $field->get_model_names_pointed_to())
5109 5109
                 ) {
5110
-                    $this->_cache_foreign_key_to_fields[ $model_name ] = $field;
5110
+                    $this->_cache_foreign_key_to_fields[$model_name] = $field;
5111 5111
                     break;
5112 5112
                 }
5113 5113
             }
5114
-            if (! isset($this->_cache_foreign_key_to_fields[ $model_name ])) {
5114
+            if ( ! isset($this->_cache_foreign_key_to_fields[$model_name])) {
5115 5115
                 throw new EE_Error(
5116 5116
                     sprintf(
5117 5117
                         esc_html__(
@@ -5124,7 +5124,7 @@  discard block
 block discarded – undo
5124 5124
                 );
5125 5125
             }
5126 5126
         }
5127
-        return $this->_cache_foreign_key_to_fields[ $model_name ];
5127
+        return $this->_cache_foreign_key_to_fields[$model_name];
5128 5128
     }
5129 5129
 
5130 5130
 
@@ -5140,7 +5140,7 @@  discard block
 block discarded – undo
5140 5140
     {
5141 5141
         $table_alias_sans_model_relation_chain_prefix =
5142 5142
             EE_Model_Parser::remove_table_alias_model_relation_chain_prefix($table_alias);
5143
-        return $this->_tables[ $table_alias_sans_model_relation_chain_prefix ]->get_table_name();
5143
+        return $this->_tables[$table_alias_sans_model_relation_chain_prefix]->get_table_name();
5144 5144
     }
5145 5145
 
5146 5146
 
@@ -5158,7 +5158,7 @@  discard block
 block discarded – undo
5158 5158
                 $this->_cached_fields = [];
5159 5159
                 foreach ($this->_fields as $fields_corresponding_to_table) {
5160 5160
                     foreach ($fields_corresponding_to_table as $field_name => $field_obj) {
5161
-                        $this->_cached_fields[ $field_name ] = $field_obj;
5161
+                        $this->_cached_fields[$field_name] = $field_obj;
5162 5162
                     }
5163 5163
                 }
5164 5164
             }
@@ -5169,8 +5169,8 @@  discard block
 block discarded – undo
5169 5169
             foreach ($this->_fields as $fields_corresponding_to_table) {
5170 5170
                 foreach ($fields_corresponding_to_table as $field_name => $field_obj) {
5171 5171
                     /** @var $field_obj EE_Model_Field_Base */
5172
-                    if (! $field_obj->is_db_only_field()) {
5173
-                        $this->_cached_fields_non_db_only[ $field_name ] = $field_obj;
5172
+                    if ( ! $field_obj->is_db_only_field()) {
5173
+                        $this->_cached_fields_non_db_only[$field_name] = $field_obj;
5174 5174
                     }
5175 5175
                 }
5176 5176
             }
@@ -5211,12 +5211,12 @@  discard block
 block discarded – undo
5211 5211
                     $primary_key_field->get_qualified_column(),
5212 5212
                     $primary_key_field->get_table_column()
5213 5213
                 );
5214
-                if ($table_pk_value && isset($array_of_objects[ $table_pk_value ])) {
5214
+                if ($table_pk_value && isset($array_of_objects[$table_pk_value])) {
5215 5215
                     continue;
5216 5216
                 }
5217 5217
             }
5218 5218
             $classInstance = $this->instantiate_class_from_array_or_object($row);
5219
-            if (! $classInstance) {
5219
+            if ( ! $classInstance) {
5220 5220
                 throw new EE_Error(
5221 5221
                     sprintf(
5222 5222
                         esc_html__('Could not create instance of class %s from row %s', 'event_espresso'),
@@ -5229,7 +5229,7 @@  discard block
 block discarded – undo
5229 5229
             $classInstance->set_timezone($this->_timezone);
5230 5230
             // make sure if there is any timezone setting present that we set the timezone for the object
5231 5231
             $key                      = $has_primary_key ? $classInstance->ID() : $count_if_model_has_no_primary_key++;
5232
-            $array_of_objects[ $key ] = $classInstance;
5232
+            $array_of_objects[$key] = $classInstance;
5233 5233
             // also, for all the relations of type BelongsTo, see if we can cache
5234 5234
             // those related models
5235 5235
             // (we could do this for other relations too, but if there are conditions
@@ -5273,9 +5273,9 @@  discard block
 block discarded – undo
5273 5273
         $results = [];
5274 5274
         if ($this->_custom_selections instanceof CustomSelects) {
5275 5275
             foreach ($this->_custom_selections->columnAliases() as $alias) {
5276
-                if (isset($db_results_row[ $alias ])) {
5277
-                    $results[ $alias ] = $this->convertValueToDataType(
5278
-                        $db_results_row[ $alias ],
5276
+                if (isset($db_results_row[$alias])) {
5277
+                    $results[$alias] = $this->convertValueToDataType(
5278
+                        $db_results_row[$alias],
5279 5279
                         $this->_custom_selections->getDataTypeForAlias($alias)
5280 5280
                     );
5281 5281
                 }
@@ -5320,9 +5320,9 @@  discard block
 block discarded – undo
5320 5320
         $this_model_fields_and_values = [];
5321 5321
         // setup the row using default values;
5322 5322
         foreach ($this->field_settings() as $field_name => $field_obj) {
5323
-            $this_model_fields_and_values[ $field_name ] = $field_obj->get_default_value();
5323
+            $this_model_fields_and_values[$field_name] = $field_obj->get_default_value();
5324 5324
         }
5325
-        $className     = $this->_get_class_name();
5325
+        $className = $this->_get_class_name();
5326 5326
         return EE_Registry::instance()->load_class($className, [$this_model_fields_and_values], false, false);
5327 5327
     }
5328 5328
 
@@ -5336,20 +5336,20 @@  discard block
 block discarded – undo
5336 5336
      */
5337 5337
     public function instantiate_class_from_array_or_object($cols_n_values)
5338 5338
     {
5339
-        if (! is_array($cols_n_values) && is_object($cols_n_values)) {
5339
+        if ( ! is_array($cols_n_values) && is_object($cols_n_values)) {
5340 5340
             $cols_n_values = get_object_vars($cols_n_values);
5341 5341
         }
5342 5342
         $primary_key = null;
5343 5343
         // make sure the array only has keys that are fields/columns on this model
5344 5344
         $this_model_fields_n_values = $this->_deduce_fields_n_values_from_cols_n_values($cols_n_values);
5345
-        if ($this->has_primary_key_field() && isset($this_model_fields_n_values[ $this->primary_key_name() ])) {
5346
-            $primary_key = $this_model_fields_n_values[ $this->primary_key_name() ];
5345
+        if ($this->has_primary_key_field() && isset($this_model_fields_n_values[$this->primary_key_name()])) {
5346
+            $primary_key = $this_model_fields_n_values[$this->primary_key_name()];
5347 5347
         }
5348 5348
         $className = $this->_get_class_name();
5349 5349
         // check we actually found results that we can use to build our model object
5350 5350
         // if not, return null
5351 5351
         if ($this->has_primary_key_field()) {
5352
-            if (empty($this_model_fields_n_values[ $this->primary_key_name() ])) {
5352
+            if (empty($this_model_fields_n_values[$this->primary_key_name()])) {
5353 5353
                 return null;
5354 5354
             }
5355 5355
         } elseif ($this->unique_indexes()) {
@@ -5361,7 +5361,7 @@  discard block
 block discarded – undo
5361 5361
         // if there is no primary key or the object doesn't already exist in the entity map, then create a new instance
5362 5362
         if ($primary_key) {
5363 5363
             $classInstance = $this->get_from_entity_map($primary_key);
5364
-            if (! $classInstance) {
5364
+            if ( ! $classInstance) {
5365 5365
                 $classInstance = EE_Registry::instance()
5366 5366
                                             ->load_class(
5367 5367
                                                 $className,
@@ -5393,8 +5393,8 @@  discard block
 block discarded – undo
5393 5393
      */
5394 5394
     public function get_from_entity_map($id)
5395 5395
     {
5396
-        return isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])
5397
-            ? $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ] : null;
5396
+        return isset($this->_entity_map[EEM_Base::$_model_query_blog_id][$id])
5397
+            ? $this->_entity_map[EEM_Base::$_model_query_blog_id][$id] : null;
5398 5398
     }
5399 5399
 
5400 5400
 
@@ -5417,7 +5417,7 @@  discard block
 block discarded – undo
5417 5417
     public function add_to_entity_map(EE_Base_Class $object)
5418 5418
     {
5419 5419
         $className = $this->_get_class_name();
5420
-        if (! $object instanceof $className) {
5420
+        if ( ! $object instanceof $className) {
5421 5421
             throw new EE_Error(
5422 5422
                 sprintf(
5423 5423
                     esc_html__("You tried adding a %s to a mapping of %ss", "event_espresso"),
@@ -5427,7 +5427,7 @@  discard block
 block discarded – undo
5427 5427
             );
5428 5428
         }
5429 5429
         /** @var $object EE_Base_Class */
5430
-        if (! $object->ID()) {
5430
+        if ( ! $object->ID()) {
5431 5431
             throw new EE_Error(
5432 5432
                 sprintf(
5433 5433
                     esc_html__(
@@ -5443,7 +5443,7 @@  discard block
 block discarded – undo
5443 5443
         if ($classInstance) {
5444 5444
             return $classInstance;
5445 5445
         }
5446
-        $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $object->ID() ] = $object;
5446
+        $this->_entity_map[EEM_Base::$_model_query_blog_id][$object->ID()] = $object;
5447 5447
         return $object;
5448 5448
     }
5449 5449
 
@@ -5458,11 +5458,11 @@  discard block
 block discarded – undo
5458 5458
     public function clear_entity_map($id = null)
5459 5459
     {
5460 5460
         if (empty($id)) {
5461
-            $this->_entity_map[ EEM_Base::$_model_query_blog_id ] = [];
5461
+            $this->_entity_map[EEM_Base::$_model_query_blog_id] = [];
5462 5462
             return true;
5463 5463
         }
5464
-        if (isset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ])) {
5465
-            unset($this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ]);
5464
+        if (isset($this->_entity_map[EEM_Base::$_model_query_blog_id][$id])) {
5465
+            unset($this->_entity_map[EEM_Base::$_model_query_blog_id][$id]);
5466 5466
             return true;
5467 5467
         }
5468 5468
         return false;
@@ -5510,18 +5510,18 @@  discard block
 block discarded – undo
5510 5510
             // there is a primary key on this table and its not set. Use defaults for all its columns
5511 5511
             if ($table_pk_value === null && $table_obj->get_pk_column()) {
5512 5512
                 foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5513
-                    if (! $field_obj->is_db_only_field()) {
5513
+                    if ( ! $field_obj->is_db_only_field()) {
5514 5514
                         // prepare field as if its coming from db
5515 5515
                         $prepared_value                            =
5516 5516
                             $field_obj->prepare_for_set($field_obj->get_default_value());
5517
-                        $this_model_fields_n_values[ $field_name ] = $field_obj->prepare_for_use_in_db($prepared_value);
5517
+                        $this_model_fields_n_values[$field_name] = $field_obj->prepare_for_use_in_db($prepared_value);
5518 5518
                     }
5519 5519
                 }
5520 5520
             } else {
5521 5521
                 // the table's rows existed. Use their values
5522 5522
                 foreach ($this->_get_fields_for_table($table_alias) as $field_name => $field_obj) {
5523
-                    if (! $field_obj->is_db_only_field()) {
5524
-                        $this_model_fields_n_values[ $field_name ] = $this->_get_column_value_with_table_alias_or_not(
5523
+                    if ( ! $field_obj->is_db_only_field()) {
5524
+                        $this_model_fields_n_values[$field_name] = $this->_get_column_value_with_table_alias_or_not(
5525 5525
                             $cols_n_values,
5526 5526
                             $field_obj->get_qualified_column(),
5527 5527
                             $field_obj->get_table_column()
@@ -5548,17 +5548,17 @@  discard block
 block discarded – undo
5548 5548
         // ask the field what it think it's table_name.column_name should be, and call it the "qualified column"
5549 5549
         // does the field on the model relate to this column retrieved from the db?
5550 5550
         // or is it a db-only field? (not relating to the model)
5551
-        if (isset($cols_n_values[ $qualified_column ])) {
5552
-            $value = $cols_n_values[ $qualified_column ];
5553
-        } elseif (isset($cols_n_values[ $regular_column ])) {
5554
-            $value = $cols_n_values[ $regular_column ];
5555
-        } elseif (! empty($this->foreign_key_aliases)) {
5551
+        if (isset($cols_n_values[$qualified_column])) {
5552
+            $value = $cols_n_values[$qualified_column];
5553
+        } elseif (isset($cols_n_values[$regular_column])) {
5554
+            $value = $cols_n_values[$regular_column];
5555
+        } elseif ( ! empty($this->foreign_key_aliases)) {
5556 5556
             // no PK?  ok check if there is a foreign key alias set for this table
5557 5557
             // then check if that alias exists in the incoming data
5558 5558
             // AND that the actual PK the $FK_alias represents matches the $qualified_column (full PK)
5559 5559
             foreach ($this->foreign_key_aliases as $FK_alias => $PK_column) {
5560
-                if ($PK_column === $qualified_column && isset($cols_n_values[ $FK_alias ])) {
5561
-                    $value = $cols_n_values[ $FK_alias ];
5560
+                if ($PK_column === $qualified_column && isset($cols_n_values[$FK_alias])) {
5561
+                    $value = $cols_n_values[$FK_alias];
5562 5562
                     [$pk_class] = explode('.', $PK_column);
5563 5563
                     $pk_model_name = "EEM_{$pk_class}";
5564 5564
                     /** @var EEM_Base $pk_model */
@@ -5602,7 +5602,7 @@  discard block
 block discarded – undo
5602 5602
                     $obj_in_map->clear_cache($relation_name, null, true);
5603 5603
                 }
5604 5604
             }
5605
-            $this->_entity_map[ EEM_Base::$_model_query_blog_id ][ $id ] = $obj_in_map;
5605
+            $this->_entity_map[EEM_Base::$_model_query_blog_id][$id] = $obj_in_map;
5606 5606
             return $obj_in_map;
5607 5607
         }
5608 5608
         return $this->get_one_by_ID($id);
@@ -5654,7 +5654,7 @@  discard block
 block discarded – undo
5654 5654
      */
5655 5655
     private function _get_class_name()
5656 5656
     {
5657
-        return "EE_" . $this->get_this_model_name();
5657
+        return "EE_".$this->get_this_model_name();
5658 5658
     }
5659 5659
 
5660 5660
 
@@ -5706,7 +5706,7 @@  discard block
 block discarded – undo
5706 5706
     {
5707 5707
         $className = get_class($this);
5708 5708
         $tagName   = "FHEE__{$className}__{$methodName}";
5709
-        if (! has_filter($tagName)) {
5709
+        if ( ! has_filter($tagName)) {
5710 5710
             throw new EE_Error(
5711 5711
                 sprintf(
5712 5712
                     esc_html__(
@@ -5877,7 +5877,7 @@  discard block
 block discarded – undo
5877 5877
         $unique_indexes = [];
5878 5878
         foreach ($this->_indexes as $name => $index) {
5879 5879
             if ($index instanceof EE_Unique_Index) {
5880
-                $unique_indexes [ $name ] = $index;
5880
+                $unique_indexes [$name] = $index;
5881 5881
             }
5882 5882
         }
5883 5883
         return $unique_indexes;
@@ -5941,7 +5941,7 @@  discard block
 block discarded – undo
5941 5941
         $key_vals_in_combined_pk = [];
5942 5942
         parse_str($index_primary_key_string, $key_vals_in_combined_pk);
5943 5943
         foreach ($key_fields as $key_field_name => $field_obj) {
5944
-            if (! isset($key_vals_in_combined_pk[ $key_field_name ])) {
5944
+            if ( ! isset($key_vals_in_combined_pk[$key_field_name])) {
5945 5945
                 return null;
5946 5946
             }
5947 5947
         }
@@ -5961,7 +5961,7 @@  discard block
 block discarded – undo
5961 5961
     {
5962 5962
         $keys_it_should_have = array_keys($this->get_combined_primary_key_fields());
5963 5963
         foreach ($keys_it_should_have as $key) {
5964
-            if (! isset($key_vals[ $key ])) {
5964
+            if ( ! isset($key_vals[$key])) {
5965 5965
                 return false;
5966 5966
             }
5967 5967
         }
@@ -6000,8 +6000,8 @@  discard block
 block discarded – undo
6000 6000
         }
6001 6001
         // even copies obviously won't have the same ID, so remove the primary key
6002 6002
         // from the WHERE conditions for finding copies (if there is a primary key, of course)
6003
-        if ($this->has_primary_key_field() && isset($attributes_array[ $this->primary_key_name() ])) {
6004
-            unset($attributes_array[ $this->primary_key_name() ]);
6003
+        if ($this->has_primary_key_field() && isset($attributes_array[$this->primary_key_name()])) {
6004
+            unset($attributes_array[$this->primary_key_name()]);
6005 6005
         }
6006 6006
         if (isset($query_params[0])) {
6007 6007
             $query_params[0] = array_merge($attributes_array, $query_params);
@@ -6023,7 +6023,7 @@  discard block
 block discarded – undo
6023 6023
      */
6024 6024
     public function get_one_copy($model_object_or_attributes_array, $query_params = [])
6025 6025
     {
6026
-        if (! is_array($query_params)) {
6026
+        if ( ! is_array($query_params)) {
6027 6027
             EE_Error::doing_it_wrong(
6028 6028
                 'EEM_Base::get_one_copy',
6029 6029
                 sprintf(
@@ -6072,7 +6072,7 @@  discard block
 block discarded – undo
6072 6072
      */
6073 6073
     private function _prepare_operator_for_sql($operator_supplied)
6074 6074
     {
6075
-        $sql_operator = $this->_valid_operators[ $operator_supplied ] ?? null;
6075
+        $sql_operator = $this->_valid_operators[$operator_supplied] ?? null;
6076 6076
         if ($sql_operator) {
6077 6077
             return $sql_operator;
6078 6078
         }
@@ -6170,7 +6170,7 @@  discard block
 block discarded – undo
6170 6170
         $objs  = $this->get_all($query_params);
6171 6171
         $names = [];
6172 6172
         foreach ($objs as $obj) {
6173
-            $names[ $obj->ID() ] = $obj->name();
6173
+            $names[$obj->ID()] = $obj->name();
6174 6174
         }
6175 6175
         return $names;
6176 6176
     }
@@ -6191,7 +6191,7 @@  discard block
 block discarded – undo
6191 6191
      */
6192 6192
     public function get_IDs($model_objects, $filter_out_empty_ids = false)
6193 6193
     {
6194
-        if (! $this->has_primary_key_field()) {
6194
+        if ( ! $this->has_primary_key_field()) {
6195 6195
             if (WP_DEBUG) {
6196 6196
                 EE_Error::add_error(
6197 6197
                     esc_html__('Trying to get IDs from a model than has no primary key', 'event_espresso'),
@@ -6204,7 +6204,7 @@  discard block
 block discarded – undo
6204 6204
         $IDs = [];
6205 6205
         foreach ($model_objects as $model_object) {
6206 6206
             $id = $model_object->ID();
6207
-            if (! $id) {
6207
+            if ( ! $id) {
6208 6208
                 if ($filter_out_empty_ids) {
6209 6209
                     continue;
6210 6210
                 }
@@ -6254,22 +6254,22 @@  discard block
 block discarded – undo
6254 6254
         EEM_Base::verify_is_valid_cap_context($context);
6255 6255
         // check if we ought to run the restriction generator first
6256 6256
         if (
6257
-            isset($this->_cap_restriction_generators[ $context ])
6258
-            && $this->_cap_restriction_generators[ $context ] instanceof EE_Restriction_Generator_Base
6259
-            && ! $this->_cap_restriction_generators[ $context ]->has_generated_cap_restrictions()
6257
+            isset($this->_cap_restriction_generators[$context])
6258
+            && $this->_cap_restriction_generators[$context] instanceof EE_Restriction_Generator_Base
6259
+            && ! $this->_cap_restriction_generators[$context]->has_generated_cap_restrictions()
6260 6260
         ) {
6261
-            $this->_cap_restrictions[ $context ] = array_merge(
6262
-                $this->_cap_restrictions[ $context ],
6263
-                $this->_cap_restriction_generators[ $context ]->generate_restrictions()
6261
+            $this->_cap_restrictions[$context] = array_merge(
6262
+                $this->_cap_restrictions[$context],
6263
+                $this->_cap_restriction_generators[$context]->generate_restrictions()
6264 6264
             );
6265 6265
         }
6266 6266
         // and make sure we've finalized the construction of each restriction
6267
-        foreach ($this->_cap_restrictions[ $context ] as $where_conditions_obj) {
6267
+        foreach ($this->_cap_restrictions[$context] as $where_conditions_obj) {
6268 6268
             if ($where_conditions_obj instanceof EE_Default_Where_Conditions) {
6269 6269
                 $where_conditions_obj->_finalize_construct($this);
6270 6270
             }
6271 6271
         }
6272
-        return $this->_cap_restrictions[ $context ];
6272
+        return $this->_cap_restrictions[$context];
6273 6273
     }
6274 6274
 
6275 6275
 
@@ -6299,9 +6299,9 @@  discard block
 block discarded – undo
6299 6299
         foreach ($cap_restrictions as $cap => $restriction_if_no_cap) {
6300 6300
             if (
6301 6301
                 ! EE_Capabilities::instance()
6302
-                                 ->current_user_can($cap, $this->get_this_model_name() . '_model_applying_caps')
6302
+                                 ->current_user_can($cap, $this->get_this_model_name().'_model_applying_caps')
6303 6303
             ) {
6304
-                $missing_caps[ $cap ] = $restriction_if_no_cap;
6304
+                $missing_caps[$cap] = $restriction_if_no_cap;
6305 6305
             }
6306 6306
         }
6307 6307
         return $missing_caps;
@@ -6334,8 +6334,8 @@  discard block
 block discarded – undo
6334 6334
     public function cap_action_for_context($context)
6335 6335
     {
6336 6336
         $mapping = $this->cap_contexts_to_cap_action_map();
6337
-        if (isset($mapping[ $context ])) {
6338
-            return $mapping[ $context ];
6337
+        if (isset($mapping[$context])) {
6338
+            return $mapping[$context];
6339 6339
         }
6340 6340
         if ($action = apply_filters('FHEE__EEM_Base__cap_action_for_context', null, $this, $mapping, $context)) {
6341 6341
             return $action;
@@ -6453,7 +6453,7 @@  discard block
 block discarded – undo
6453 6453
         foreach ($this->logic_query_param_keys() as $logic_query_param_key) {
6454 6454
             if (
6455 6455
                 $query_param_key === $logic_query_param_key
6456
-                || strpos($query_param_key, $logic_query_param_key . '*') === 0
6456
+                || strpos($query_param_key, $logic_query_param_key.'*') === 0
6457 6457
             ) {
6458 6458
                 return true;
6459 6459
             }
@@ -6511,7 +6511,7 @@  discard block
 block discarded – undo
6511 6511
         if ($password_field instanceof EE_Password_Field) {
6512 6512
             $field_names = $password_field->protectedFields();
6513 6513
             foreach ($field_names as $field_name) {
6514
-                $fields[ $field_name ] = $this->field_settings_for($field_name);
6514
+                $fields[$field_name] = $this->field_settings_for($field_name);
6515 6515
             }
6516 6516
         }
6517 6517
         return $fields;
@@ -6537,7 +6537,7 @@  discard block
 block discarded – undo
6537 6537
         if ($model_obj_or_fields_n_values instanceof EE_Base_Class) {
6538 6538
             $model_obj_or_fields_n_values = $model_obj_or_fields_n_values->model_field_array();
6539 6539
         }
6540
-        if (! is_array($model_obj_or_fields_n_values)) {
6540
+        if ( ! is_array($model_obj_or_fields_n_values)) {
6541 6541
             throw new UnexpectedEntityException(
6542 6542
                 $model_obj_or_fields_n_values,
6543 6543
                 'EE_Base_Class',
@@ -6617,7 +6617,7 @@  discard block
 block discarded – undo
6617 6617
                 )
6618 6618
             );
6619 6619
         }
6620
-        return ($this->model_chain_to_password ? $this->model_chain_to_password . '.' : '') . $password_field_name;
6620
+        return ($this->model_chain_to_password ? $this->model_chain_to_password.'.' : '').$password_field_name;
6621 6621
     }
6622 6622
 
6623 6623
 
Please login to merge, or discard this patch.
core/admin/EE_Admin_Hooks.core.php 1 patch
Indentation   +763 added lines, -763 removed lines patch added patch discarded remove patch
@@ -14,767 +14,767 @@
 block discarded – undo
14 14
  */
15 15
 abstract class EE_Admin_Hooks extends EE_Base
16 16
 {
17
-    /**
18
-     * we're just going to use this to hold the name of the caller class (child class name)
19
-     *
20
-     * @var string
21
-     */
22
-    public $caller;
23
-
24
-
25
-    /**
26
-     * this is just a flag set automatically to indicate whether we've got an extended hook class running (i.e.
27
-     * espresso_events_Registration_Form_Hooks_Extend extends espresso_events_Registration_Form_Hooks).  This flag is
28
-     * used later to make sure we require the needed files.
29
-     *
30
-     * @var bool
31
-     */
32
-    protected $_extend;
33
-
34
-
35
-    /**
36
-     * child classes MUST set this property so that the page object can be loaded correctly
37
-     *
38
-     * @var string
39
-     */
40
-    protected $_name;
41
-
42
-
43
-    /**
44
-     * This is set by child classes and is an associative array of ajax hooks in the format:
45
-     * array(
46
-     *    'ajax_action_ref' => 'executing_method'; //must be public
47
-     * )
48
-     *
49
-     * @var array
50
-     */
51
-    protected $_ajax_func;
52
-
53
-
54
-    /**
55
-     * This is an array of methods that get executed on a page routes admin_init hook. Use the following format:
56
-     * array(
57
-     *    'page_route' => 'executing_method' //must be public
58
-     * )
59
-     *
60
-     * @var array
61
-     */
62
-    protected $_init_func;
63
-
64
-
65
-    /**
66
-     * This is an array of methods that output metabox content for the given page route.  Use the following format:
67
-     * [
68
-     *      0 => [
69
-     *          'page_route' => 'string_for_page_route',    must correspond to a page route in the class being connected
70
-     *                                                      with (i.e. "edit_event") If this is in an array then the
71
-     *                                                      same params below will be used but the metabox will be
72
-     *                                                      added to each route.
73
-     *          'func' =>  'executing_method',              must be public (i.e. public function executing_method
74
-     *                                                      ($post, $callback_args){} ).
75
-     *                                                      Note if you include callback args in the array then you
76
-     *                                                      need to declare them in the method arguments.
77
-     *          'id' => 'identifier_for_metabox',           so it can be removed by addons
78
-     *                                                      (optional, class will set it automatically)
79
-     *          'priority' => 'default',                    default 'default' (optional)
80
-     *          'label' => esc_html__('Localized Title', 'event_espresso'),
81
-     *          'context' => 'advanced'                     advanced is default (optional),
82
-     *      ]
83
-     *      'callback_args' => array() //any callback args to include (optional)
84
-     * ]
85
-     * Why are we indexing numerically?  Because it's possible there may be more than one metabox per page_route.
86
-     *
87
-     * @var array
88
-     */
89
-    protected $_metaboxes;
90
-
91
-
92
-    /**
93
-     * This is an array of values that indicate any metaboxes we want removed from a given page route.  Usually this is
94
-     * used when caffeinated functionality is replacing decaffeinated functionality.  Use the following format for the
95
-     * array: array(
96
-     *    0 => array(
97
-     *        'page_route' => 'string_for_page_route' //can be string or array of strings that match a page_route(s)
98
-     *        that are in the class being connected with (i.e. 'edit', or 'create_new').
99
-     *        'id' => 'identifier_for_metabox', //what the id is of the metabox being removed
100
-     *        'context' => 'normal', //the context for the metabox being removed (has to match)
101
-     *        'screen' => 'screen_id', //(optional), if not included then this class will attempt to remove the metabox
102
-     *        using the currently loaded screen object->id  however, there may be cases where you have to specify the
103
-     *        id for the screen the metabox is on.
104
-     *    )
105
-     * )
106
-     *
107
-     * @var array
108
-     */
109
-    protected $_remove_metaboxes;
110
-
111
-
112
-    /**
113
-     * This parent class takes care of loading the scripts and styles if the child class has set the properties for
114
-     * them in the following format.  Note, the first array index ('register') is for defining all the registers.  The
115
-     * second array index is for indicating what routes each script/style loads on. array(
116
-     * 'registers' => array(
117
-     *        'script_ref' => array( // if more than one script is to be loaded its best to use the 'dependency'
118
-     *        argument to link scripts together.
119
-     *            'type' => 'js' // 'js' or 'css' (defaults to js).  This tells us what type of wp_function to use
120
-     *            'url' => 'http://urltoscript.css.js',
121
-     *            'depends' => array('jquery'), //an array of dependencies for the scripts. REMEMBER, if a script has
122
-     *            already been registered elsewhere in the system.  You can just use the depends array to make sure it
123
-     *            gets loaded before the one you are setting here.
124
-     *            'footer' => TRUE //defaults to true (styles don't use this parameter)
125
-     *        ),
126
-     *    'enqueues' => array( //this time each key corresponds to the script ref followed by an array of page routes
127
-     *    the script gets enqueued on.
128
-     *        'script_ref' => array('route_one', 'route_two')
129
-     *    ),
130
-     *    'localize' => array( //this allows you to set a localized object.  Indicate which script the object is being
131
-     *    attached to and then include an array indexed by the name of the object and the array of key/value pairs for
132
-     *    the object.
133
-     *        'scrip_ref' => array(
134
-     *            'NAME_OF_JS_OBJECT' => array(
135
-     *                'translate_ref' => esc_html__('localized_string', 'event_espresso'),
136
-     *                'some_data' => 5
137
-     *            )
138
-     *        )
139
-     *    )
140
-     * )
141
-     *
142
-     * @var array
143
-     */
144
-    protected $_scripts_styles;
145
-
146
-
147
-    /**
148
-     * This is a property that will contain the current route.
149
-     *
150
-     * @var string;
151
-     */
152
-    protected $_current_route;
153
-
154
-
155
-    /**
156
-     * this optional property can be set by child classes to override the priority for the automatic action/filter hook
157
-     * loading in the `_load_routed_hooks()` method.  Please follow this format: array(
158
-     *    'wp_hook_reference' => 1
159
-     *    )
160
-     * )
161
-     *
162
-     * @var array
163
-     */
164
-    protected $_wp_action_filters_priority;
165
-
166
-
167
-    /**
168
-     * This just holds a merged array of the request vars
169
-     *
170
-     * @var array
171
-     */
172
-    protected $_req_data;
173
-
174
-    /**
175
-     * @var array
176
-     */
177
-    protected $_scripts;
178
-
179
-    /**
180
-     * @var array
181
-     */
182
-    protected $_styles;
183
-
184
-    /**
185
-     * This just holds an instance of the page object for this hook
186
-     *
187
-     * @var EE_Admin_Page
188
-     */
189
-    protected $_page_object;
190
-
191
-
192
-    /**
193
-     * This holds the EE_Admin_Page object from the calling admin page that this object hooks into.
194
-     *
195
-     * @var EE_Admin_Page|EE_Admin_Page_CPT
196
-     */
197
-    protected $_adminpage_obj;
198
-
199
-
200
-    /**
201
-     * Holds EE_Registry object
202
-     *
203
-     * @var EE_Registry
204
-     */
205
-    protected $EE = null;
206
-
207
-    /**
208
-     * @var RequestInterface
209
-     */
210
-    protected $request;
211
-
212
-
213
-    /**
214
-     * constructor
215
-     *
216
-     * @param EE_Admin_Page $admin_page
217
-     * @throws EE_Error
218
-     */
219
-    public function __construct(EE_Admin_Page $admin_page)
220
-    {
221
-        $this->_adminpage_obj = $admin_page;
222
-        $this->request        = LoaderFactory::getLoader()->getShared(RequestInterface::class);
223
-        $this->_req_data      = $this->request->requestParams();
224
-        $current_page = $this->request->getRequestParam('page');
225
-        $current_page = $this->request->getRequestParam('current_page', $current_page);
226
-        // first let's verify we're on the right page
227
-        if ($current_page !== $this->_adminpage_obj->page_slug) {
228
-            return;
229
-        }
230
-        $this->_set_defaults();
231
-        $this->_set_hooks_properties();
232
-        // get out nothing more to be done here.
233
-        // allow for extends to modify properties
234
-        if (method_exists($this, '_extend_properties')) {
235
-            $this->_extend_properties();
236
-        }
237
-        $this->_set_page_object();
238
-        $this->_init_hooks();
239
-        $this->_load_custom_methods();
240
-        $this->_load_routed_hooks();
241
-        add_action('admin_enqueue_scripts', [$this, 'enqueue_scripts_styles']);
242
-        add_action('admin_enqueue_scripts', [$this, 'add_metaboxes'], 20);
243
-        add_action('admin_enqueue_scripts', [$this, 'remove_metaboxes'], 15);
244
-        $this->_ajax_hooks();
245
-    }
246
-
247
-
248
-    /**
249
-     * used by child classes to set the following properties:
250
-     * $_ajax_func (optional)
251
-     * $_init_func (optional)
252
-     * $_metaboxes (optional)
253
-     * $_scripts (optional)
254
-     * $_styles (optional)
255
-     * $_name (required)
256
-     * Also in this method will be registered any scripts or styles loaded on the targeted page (as indicated in the
257
-     * _scripts/_styles properties) Also children should place in this method any filters/actions that have to happen
258
-     * really early on page load (just after admin_init) if they want to have them registered for handling early.
259
-     *
260
-     * @abstract
261
-     * @return void
262
-     */
263
-    abstract protected function _set_hooks_properties();
264
-
265
-
266
-    /**
267
-     * The hooks for enqueue_scripts and enqueue_styles will be run in here.  Child classes need to define their
268
-     * scripts and styles in the relevant $_scripts and $_styles properties.  Child classes must have also already
269
-     * registered the scripts and styles using wp_register_script and wp_register_style functions.
270
-     *
271
-     * @return void
272
-     * @throws EE_Error
273
-     */
274
-    public function enqueue_scripts_styles()
275
-    {
276
-        if (! empty($this->_scripts_styles)) {
277
-            // first let's do all the registrations
278
-            if (! isset($this->_scripts_styles['registers'])) {
279
-                $msg[] = esc_html__(
280
-                    'There is no "registers" index in the <code>$this->_scripts_styles</code> property.',
281
-                    'event_espresso'
282
-                );
283
-                $msg[] = sprintf(
284
-                    esc_html__(
285
-                        'Make sure you read the phpdoc comments above the definition of the $_scripts_styles property in the <code>EE_Admin_Hooks</code> class and modify according in the %s child',
286
-                        'event_espresso'
287
-                    ),
288
-                    '<strong>' . $this->caller . '</strong>'
289
-                );
290
-                throw new EE_Error(implode('||', $msg));
291
-            }
292
-            $defaults = [
293
-                'type'    => 'js',
294
-                'url'     => '',
295
-                'depends' => [],
296
-                'version' => EVENT_ESPRESSO_VERSION,
297
-                'footer'  => true,
298
-            ];
299
-            foreach ($this->_scripts_styles['registers'] as $ref => $details) {
300
-                $details = wp_parse_args($details, $defaults);
301
-                $type    = $details['type'];
302
-                $url     = $details['url'];
303
-                $depends = $details['depends'];
304
-                $version = $details['version'];
305
-                $footer  = $details['footer'];
306
-                // let's make sure that we set the 'registers' type if it's not set!
307
-                // We need it later to determine which enqueue we do
308
-                $this->_scripts_styles['registers'][ $ref ]['type'] = $type;
309
-                // let's make sure we're not missing any REQUIRED parameters
310
-                if (empty($url)) {
311
-                    $msg[] = sprintf(
312
-                        esc_html__('Missing the url for the requested %s', 'event_espresso'),
313
-                        $type == 'js' ? 'script' : 'stylesheet'
314
-                    );
315
-                    $msg[] = sprintf(
316
-                        esc_html__(
317
-                            'Doublecheck your <code>$this->_scripts_styles</code> array in %s and make sure that there is a "url" set for the %s ref',
318
-                            'event_espresso'
319
-                        ),
320
-                        '<strong>' . $this->caller . '</strong>',
321
-                        $ref
322
-                    );
323
-                    throw new EE_Error(implode('||', $msg));
324
-                }
325
-                // made it here so let's do the appropriate registration
326
-                $type == 'js'
327
-                    ? wp_register_script($ref, $url, $depends, $version, $footer)
328
-                    : wp_register_style(
329
-                        $ref,
330
-                        $url,
331
-                        $depends,
332
-                        $version
333
-                    );
334
-            }
335
-            // k now let's do the enqueues
336
-            if (! isset($this->_scripts_styles['enqueues'])) {
337
-                return;
338
-            }  //not sure if we should throw an error here or not.
339
-
340
-            foreach ($this->_scripts_styles['enqueues'] as $ref => $routes) {
341
-                // make sure $routes is an array
342
-                $routes = (array) $routes;
343
-                if (in_array($this->_current_route, $routes)) {
344
-                    $this->_scripts_styles['registers'][ $ref ]['type'] == 'js' ? wp_enqueue_script($ref)
345
-                        : wp_enqueue_style($ref);
346
-                    // if we have a localization for the script let's do that too.
347
-                    if (isset($this->_scripts_styles['localize'][ $ref ])) {
348
-                        foreach ($this->_scripts_styles['localize'][ $ref ] as $object_name => $indexes) {
349
-                            wp_localize_script(
350
-                                $ref,
351
-                                $object_name,
352
-                                $this->_scripts_styles['localize'][ $ref ][ $object_name ]
353
-                            );
354
-                        }
355
-                    }
356
-                }
357
-            }
358
-            // let's do the deregisters
359
-            if (! isset($this->_scripts_styles['deregisters'])) {
360
-                return;
361
-            }
362
-            foreach ($this->_scripts_styles['deregisters'] as $ref => $details) {
363
-                $defaults = ['type' => 'js'];
364
-                $details  = wp_parse_args($details, $defaults);
365
-                $details['type'] === 'js' ? wp_deregister_script($ref) : wp_deregister_style($ref);
366
-            }
367
-        }
368
-    }
369
-
370
-
371
-    /**
372
-     * just set the defaults for the hooks properties.
373
-     *
374
-     * @return void
375
-     */
376
-    private function _set_defaults()
377
-    {
378
-        $this->_ajax_func                  = [];
379
-        $this->_init_func                  = [];
380
-        $this->_metaboxes                  = [];
381
-        $this->_scripts                    = [];
382
-        $this->_styles                     = [];
383
-        $this->_wp_action_filters_priority = [];
384
-        $this->_current_route              = $this->getCurrentRoute();
385
-        $this->caller                      = get_class($this);
386
-        $this->_extend                     = (bool) stripos($this->caller, 'Extend');
387
-    }
388
-
389
-
390
-    /**
391
-     * A helper for determining the current route.
392
-     *
393
-     * @return string
394
-     */
395
-    private function getCurrentRoute()
396
-    {
397
-        $action = $this->request->getRequestParam('action');
398
-        // list tables do something else with 'action' for bulk actions.
399
-        $action = $action !== '-1' && $action !== '' ? $action : 'default';
400
-        $route  = $this->request->getRequestParam('route');
401
-        // we set a 'route' variable in some cases where action is being used by something else.
402
-        return $action === 'default' && $route !== '' ? $route : $action;
403
-    }
404
-
405
-
406
-    /**
407
-     * this sets the _page_object property
408
-     *
409
-     * @return void
410
-     * @throws EE_Error
411
-     */
412
-    protected function _set_page_object()
413
-    {
414
-        if ($this->_page_object instanceof EE_Admin_Page) {
415
-            return;
416
-        }
417
-        // first make sure $this->_name is set
418
-        if (empty($this->_name)) {
419
-            $msg[] = esc_html__('We can\'t load the page object', 'event_espresso');
420
-            $msg[] = sprintf(
421
-                esc_html__("This is because the %s child class has not set the '_name' property", 'event_espresso'),
422
-                $this->caller
423
-            );
424
-            throw new EE_Error(implode('||', $msg));
425
-        }
426
-        // change "the_message" to "the message"
427
-        $class_name = str_replace('_', ' ', $this->_name);
428
-        // change "the message" to "The_Message_Admin_Page"
429
-        $class_name = str_replace(' ', '_', ucwords($class_name)) . '_Admin_Page';
430
-        // first default file (if exists)
431
-        $decaf_file = EE_ADMIN_PAGES . $this->_name . '/' . $class_name . '.core.php';
432
-        if (is_readable($decaf_file)) {
433
-            require_once($decaf_file);
434
-        }
435
-        // now we have to do require for extended file (if needed)
436
-        if ($this->_extend) {
437
-            require_once(EE_CORE_CAF_ADMIN_EXTEND . $this->_name . '/Extend_' . $class_name . '.core.php');
438
-            // and extend the class name as well
439
-            $class_name = 'Extend_' . $class_name;
440
-        }
441
-        // let's make sure the class exists
442
-        if (! class_exists($class_name)) {
443
-            $msg[] = esc_html__('We can\'t load the page object', 'event_espresso');
444
-            $msg[] = sprintf(
445
-                esc_html__(
446
-                    'The class name that was given is %s. Check the spelling and make sure its correct, also there needs to be an autoloader setup for the class',
447
-                    'event_espresso'
448
-                ),
449
-                $class_name
450
-            );
451
-            throw new EE_Error(implode('||', $msg));
452
-        }
453
-        $this->_page_object = LoaderFactory::getLoader()->getShared($class_name, [false]);
454
-        $this->_page_object->initializePage();
455
-    }
456
-
457
-
458
-    /**
459
-     * Child "hook" classes can declare any methods that they want executed when a specific page route is loaded.  The
460
-     * advantage of this is when doing things like running our own db interactions on saves etc.  Remember that
461
-     * $this->_req_data (all the _POST and _GET data) is available to your methods.
462
-     *
463
-     * @return void
464
-     */
465
-    private function _load_custom_methods()
466
-    {
467
-        /**
468
-         * method cannot be named 'default' (@see http://us3.php
469
-         * .net/manual/en/reserved.keywords.php) so need to
470
-         * handle routes that are "default"
471
-         *
472
-         * @since 4.3.0
473
-         */
474
-        $method_callback = $this->_current_route == 'default' ? 'default_callback' : $this->_current_route;
475
-        // these run before the Admin_Page route executes.
476
-        if (method_exists($this, $method_callback)) {
477
-            call_user_func([$this, $method_callback]);
478
-        }
479
-        // these run via the _redirect_after_action method in EE_Admin_Page which usually happens after non_UI methods in EE_Admin_Page classes.  There are two redirect actions, the first fires before $query_args might be manipulated by "save and close" actions and the seond fires right before the actual redirect happens.
480
-        // first the actions
481
-        // note that these action hooks will have the $query_args value available.
482
-        $admin_class_name = get_class($this->_adminpage_obj);
483
-        if (method_exists($this, '_redirect_action_early_' . $this->_current_route)) {
484
-            add_action(
485
-                'AHEE__'
486
-                . $admin_class_name
487
-                . '___redirect_after_action__before_redirect_modification_'
488
-                . $this->_current_route,
489
-                [$this, '_redirect_action_early_' . $this->_current_route],
490
-                10
491
-            );
492
-        }
493
-        if (method_exists($this, '_redirect_action_' . $this->_current_route)) {
494
-            add_action(
495
-                'AHEE_redirect_' . $admin_class_name . $this->_current_route,
496
-                [$this, '_redirect_action_' . $this->_current_route],
497
-                10
498
-            );
499
-        }
500
-        // let's hook into the _redirect itself and allow for changing where the user goes after redirect.  This will have $query_args and $redirect_url available.
501
-        if (method_exists($this, '_redirect_filter_' . $this->_current_route)) {
502
-            add_filter(
503
-                'FHEE_redirect_' . $admin_class_name . $this->_current_route,
504
-                [$this, '_redirect_filter_' . $this->_current_route],
505
-                10,
506
-                2
507
-            );
508
-        }
509
-    }
510
-
511
-
512
-    /**
513
-     * This method will search for a corresponding method with a name matching the route and the wp_hook to run.  This
514
-     * allows child hook classes to target hooking into a specific wp action or filter hook ONLY on a certain route.
515
-     * just remember, methods MUST be public Future hooks should be added in here to be access by child classes.
516
-     *
517
-     * @return void
518
-     */
519
-    private function _load_routed_hooks()
520
-    {
521
-
522
-        // this array provides the hook action names that will be referenced.  Key is the action. Value is an array with the type (action or filter) and the number of parameters for the hook.  We'll default all priorities for automatic hooks to 10.
523
-        $hook_filter_array = [
524
-            'admin_footer'                                                                            => [
525
-                'type'     => 'action',
526
-                'argnum'   => 1,
527
-                'priority' => 10,
528
-            ],
529
-            'FHEE_list_table_views_' . $this->_adminpage_obj->page_slug . '_' . $this->_current_route => [
530
-                'type'     => 'filter',
531
-                'argnum'   => 1,
532
-                'priority' => 10,
533
-            ],
534
-            'FHEE_list_table_views_' . $this->_adminpage_obj->page_slug                               => [
535
-                'type'     => 'filter',
536
-                'argnum'   => 1,
537
-                'priority' => 10,
538
-            ],
539
-            'FHEE_list_table_views'                                                                   => [
540
-                'type'     => 'filter',
541
-                'argnum'   => 1,
542
-                'priority' => 10,
543
-            ],
544
-            'AHEE__EE_Admin_Page___display_admin_page__modify_metaboxes'                              => [
545
-                'type'     => 'action',
546
-                'argnum'   => 1,
547
-                'priority' => 10,
548
-            ],
549
-        ];
550
-        foreach ($hook_filter_array as $hook => $args) {
551
-            if (method_exists($this, $this->_current_route . '_' . $hook)) {
552
-                if (isset($this->_wp_action_filters_priority[ $hook ])) {
553
-                    $args['priority'] = $this->_wp_action_filters_priority[ $hook ];
554
-                }
555
-                if ($args['type'] == 'action') {
556
-                    add_action(
557
-                        $hook,
558
-                        [$this, $this->_current_route . '_' . $hook],
559
-                        $args['priority'],
560
-                        $args['argnum']
561
-                    );
562
-                } else {
563
-                    add_filter(
564
-                        $hook,
565
-                        [$this, $this->_current_route . '_' . $hook],
566
-                        $args['priority'],
567
-                        $args['argnum']
568
-                    );
569
-                }
570
-            }
571
-        }
572
-    }
573
-
574
-
575
-    /**
576
-     * Loop throught the $_ajax_func array and add_actions for the array.
577
-     *
578
-     * @return void
579
-     * @throws EE_Error
580
-     */
581
-    private function _ajax_hooks()
582
-    {
583
-        if (empty($this->_ajax_func)) {
584
-            return;
585
-        } //get out there's nothing to take care of.
586
-        foreach ($this->_ajax_func as $action => $method) {
587
-            // make sure method exists
588
-            if (! method_exists($this, $method)) {
589
-                $msg[] = esc_html__(
590
-                    'There is no corresponding method for the hook labeled in the _ajax_func array',
591
-                    'event_espresso'
592
-                ) . '<br />';
593
-                $msg[] = sprintf(
594
-                    esc_html__(
595
-                        'The method name given in the array is %s, check the spelling and make sure it exists in the %s class',
596
-                        'event_espresso'
597
-                    ),
598
-                    $method,
599
-                    $this->caller
600
-                );
601
-                throw new EE_Error(implode('||', $msg));
602
-            }
603
-            add_action('wp_ajax_' . $action, [$this, $method]);
604
-        }
605
-    }
606
-
607
-
608
-    /**
609
-     * Loop throught the $_init_func array and add_actions for the array.
610
-     *
611
-     * @return void
612
-     * @throws EE_Error
613
-     */
614
-    protected function _init_hooks()
615
-    {
616
-        if (empty($this->_init_func)) {
617
-            return;
618
-        }
619
-        // get out there's nothing to take care of.
620
-        // We need to determine what page_route we are on!
621
-        foreach ($this->_init_func as $route => $method) {
622
-            // make sure method exists
623
-            if (! method_exists($this, $method)) {
624
-                $msg[] = esc_html__(
625
-                    'There is no corresponding method for the hook labeled in the _init_func array',
626
-                    'event_espresso'
627
-                ) . '<br />';
628
-                $msg[] = sprintf(
629
-                    esc_html__(
630
-                        'The method name given in the array is %s, check the spelling and make sure it exists in the %s class',
631
-                        'event_espresso'
632
-                    ),
633
-                    $method,
634
-                    $this->caller
635
-                );
636
-                throw new EE_Error(implode('||', $msg));
637
-            }
638
-            if ($route == $this->_current_route) {
639
-                add_action('admin_init', [$this, $method]);
640
-            }
641
-        }
642
-    }
643
-
644
-
645
-    /**
646
-     * Loop through the _metaboxes property and add_metaboxes accordingly
647
-     * //todo we could eventually make this a config component class (i.e. new EE_Metabox);
648
-     *
649
-     * @return void
650
-     * @throws EE_Error
651
-     */
652
-    public function add_metaboxes()
653
-    {
654
-        if (empty($this->_metaboxes)) {
655
-            return;
656
-        } //get out we don't have any metaboxes to set for this connection
657
-        $this->_handle_metabox_array($this->_metaboxes);
658
-    }
659
-
660
-
661
-    /**
662
-     * @param array $boxes
663
-     * @param bool  $add
664
-     * @throws EE_Error
665
-     */
666
-    private function _handle_metabox_array(array $boxes, $add = true)
667
-    {
668
-        foreach ($boxes as $box) {
669
-            if (! isset($box['page_route'])) {
670
-                continue;
671
-            }
672
-            // we don't have a valid array
673
-            // let's make sure $box['page_route'] is an array so the "foreach" will work.
674
-            $box['page_route'] = (array) $box['page_route'];
675
-            foreach ($box['page_route'] as $route) {
676
-                if ($route != $this->_current_route) {
677
-                    continue;
678
-                } //get out we only add metaboxes for set route.
679
-                if ($add) {
680
-                    $this->_add_metabox($box);
681
-                } else {
682
-                    $this->_remove_metabox($box);
683
-                }
684
-            }
685
-        }
686
-    }
687
-
688
-
689
-    /**
690
-     * Loop through the _remove_metaboxes property and remove metaboxes accordingly.
691
-     *
692
-     * @return void
693
-     * @throws EE_Error
694
-     */
695
-    public function remove_metaboxes()
696
-    {
697
-        if (empty($this->_remove_metaboxes)) {
698
-            return;
699
-        } //get out there are no metaboxes to remove
700
-        $this->_handle_metabox_array($this->_remove_metaboxes, false);
701
-    }
702
-
703
-
704
-    /**
705
-     * This just handles adding a metabox
706
-     *
707
-     * @param array $args an array of args that have been set for this metabox by the child class
708
-     * @throws EE_Error
709
-     */
710
-    private function _add_metabox($args)
711
-    {
712
-        $current_screen = get_current_screen();
713
-        $screen_id      = is_object($current_screen) ? $current_screen->id : null;
714
-        $callback       = $args['func'] ?? 'some_invalid_callback';
715
-        $callback_function = is_array($callback) ? end($callback) : $callback;
716
-        // set defaults
717
-        $defaults      = [
718
-            'callback_args' => [],
719
-            'context'       => 'advanced',
720
-            'func'          => $callback,
721
-            'id'            => $this->caller . '_' . $callback_function . '_metabox',
722
-            'label'         => $this->caller,
723
-            'page'          => isset($args['page']) ? $args['page'] : $screen_id,
724
-            'priority'      => 'default',
725
-        ];
726
-        $args          = wp_parse_args($args, $defaults);
727
-        $callback_args = $args['callback_args'];
728
-        $context       = $args['context'];
729
-        $id            = $args['id'];
730
-        $label         = $args['label'];
731
-        $page          = $args['page'];
732
-        $priority      = $args['priority'];
733
-        // make sure method exists
734
-        if (! method_exists($this, $callback_function)) {
735
-            $msg[] =
736
-                esc_html__('There is no corresponding method to display the metabox content', 'event_espresso')
737
-                . '<br />';
738
-            $msg[] = sprintf(
739
-                esc_html__(
740
-                    'The method name given in the array is %s, check the spelling and make sure it exists in the %s class',
741
-                    'event_espresso'
742
-                ),
743
-                $callback_function,
744
-                $this->caller
745
-            );
746
-            throw new EE_Error(implode('||', $msg));
747
-        }
748
-        // everything checks out so let's add the metabox
749
-        add_meta_box($id, $label, [$this, $callback_function], $page, $context, $priority, $callback_args);
750
-        add_filter(
751
-            "postbox_classes_{$page}_{$id}",
752
-            function ($classes) {
753
-                array_push($classes, 'ee-admin-container');
754
-                return $classes;
755
-            }
756
-        );
757
-    }
758
-
759
-
760
-    private function _remove_metabox($args)
761
-    {
762
-        $current_screen = get_current_screen();
763
-        $screen_id      = is_object($current_screen) ? $current_screen->id : null;
764
-        $func           = isset($args['func']) ? $args['func'] : 'some_invalid_callback';
765
-        // set defaults
766
-        $defaults = [
767
-            'context' => 'default',
768
-            'id'      => isset($args['id'])
769
-                ? $args['id']
770
-                : $this->_current_route . '_' . $this->caller . '_' . $func . '_metabox',
771
-            'screen'  => isset($args['screen']) ? $args['screen'] : $screen_id,
772
-        ];
773
-        $args     = wp_parse_args($args, $defaults);
774
-        $context  = $args['context'];
775
-        $id       = $args['id'];
776
-        $screen   = $args['screen'];
777
-        // everything checks out so lets remove the box!
778
-        remove_meta_box($id, $screen, $context);
779
-    }
17
+	/**
18
+	 * we're just going to use this to hold the name of the caller class (child class name)
19
+	 *
20
+	 * @var string
21
+	 */
22
+	public $caller;
23
+
24
+
25
+	/**
26
+	 * this is just a flag set automatically to indicate whether we've got an extended hook class running (i.e.
27
+	 * espresso_events_Registration_Form_Hooks_Extend extends espresso_events_Registration_Form_Hooks).  This flag is
28
+	 * used later to make sure we require the needed files.
29
+	 *
30
+	 * @var bool
31
+	 */
32
+	protected $_extend;
33
+
34
+
35
+	/**
36
+	 * child classes MUST set this property so that the page object can be loaded correctly
37
+	 *
38
+	 * @var string
39
+	 */
40
+	protected $_name;
41
+
42
+
43
+	/**
44
+	 * This is set by child classes and is an associative array of ajax hooks in the format:
45
+	 * array(
46
+	 *    'ajax_action_ref' => 'executing_method'; //must be public
47
+	 * )
48
+	 *
49
+	 * @var array
50
+	 */
51
+	protected $_ajax_func;
52
+
53
+
54
+	/**
55
+	 * This is an array of methods that get executed on a page routes admin_init hook. Use the following format:
56
+	 * array(
57
+	 *    'page_route' => 'executing_method' //must be public
58
+	 * )
59
+	 *
60
+	 * @var array
61
+	 */
62
+	protected $_init_func;
63
+
64
+
65
+	/**
66
+	 * This is an array of methods that output metabox content for the given page route.  Use the following format:
67
+	 * [
68
+	 *      0 => [
69
+	 *          'page_route' => 'string_for_page_route',    must correspond to a page route in the class being connected
70
+	 *                                                      with (i.e. "edit_event") If this is in an array then the
71
+	 *                                                      same params below will be used but the metabox will be
72
+	 *                                                      added to each route.
73
+	 *          'func' =>  'executing_method',              must be public (i.e. public function executing_method
74
+	 *                                                      ($post, $callback_args){} ).
75
+	 *                                                      Note if you include callback args in the array then you
76
+	 *                                                      need to declare them in the method arguments.
77
+	 *          'id' => 'identifier_for_metabox',           so it can be removed by addons
78
+	 *                                                      (optional, class will set it automatically)
79
+	 *          'priority' => 'default',                    default 'default' (optional)
80
+	 *          'label' => esc_html__('Localized Title', 'event_espresso'),
81
+	 *          'context' => 'advanced'                     advanced is default (optional),
82
+	 *      ]
83
+	 *      'callback_args' => array() //any callback args to include (optional)
84
+	 * ]
85
+	 * Why are we indexing numerically?  Because it's possible there may be more than one metabox per page_route.
86
+	 *
87
+	 * @var array
88
+	 */
89
+	protected $_metaboxes;
90
+
91
+
92
+	/**
93
+	 * This is an array of values that indicate any metaboxes we want removed from a given page route.  Usually this is
94
+	 * used when caffeinated functionality is replacing decaffeinated functionality.  Use the following format for the
95
+	 * array: array(
96
+	 *    0 => array(
97
+	 *        'page_route' => 'string_for_page_route' //can be string or array of strings that match a page_route(s)
98
+	 *        that are in the class being connected with (i.e. 'edit', or 'create_new').
99
+	 *        'id' => 'identifier_for_metabox', //what the id is of the metabox being removed
100
+	 *        'context' => 'normal', //the context for the metabox being removed (has to match)
101
+	 *        'screen' => 'screen_id', //(optional), if not included then this class will attempt to remove the metabox
102
+	 *        using the currently loaded screen object->id  however, there may be cases where you have to specify the
103
+	 *        id for the screen the metabox is on.
104
+	 *    )
105
+	 * )
106
+	 *
107
+	 * @var array
108
+	 */
109
+	protected $_remove_metaboxes;
110
+
111
+
112
+	/**
113
+	 * This parent class takes care of loading the scripts and styles if the child class has set the properties for
114
+	 * them in the following format.  Note, the first array index ('register') is for defining all the registers.  The
115
+	 * second array index is for indicating what routes each script/style loads on. array(
116
+	 * 'registers' => array(
117
+	 *        'script_ref' => array( // if more than one script is to be loaded its best to use the 'dependency'
118
+	 *        argument to link scripts together.
119
+	 *            'type' => 'js' // 'js' or 'css' (defaults to js).  This tells us what type of wp_function to use
120
+	 *            'url' => 'http://urltoscript.css.js',
121
+	 *            'depends' => array('jquery'), //an array of dependencies for the scripts. REMEMBER, if a script has
122
+	 *            already been registered elsewhere in the system.  You can just use the depends array to make sure it
123
+	 *            gets loaded before the one you are setting here.
124
+	 *            'footer' => TRUE //defaults to true (styles don't use this parameter)
125
+	 *        ),
126
+	 *    'enqueues' => array( //this time each key corresponds to the script ref followed by an array of page routes
127
+	 *    the script gets enqueued on.
128
+	 *        'script_ref' => array('route_one', 'route_two')
129
+	 *    ),
130
+	 *    'localize' => array( //this allows you to set a localized object.  Indicate which script the object is being
131
+	 *    attached to and then include an array indexed by the name of the object and the array of key/value pairs for
132
+	 *    the object.
133
+	 *        'scrip_ref' => array(
134
+	 *            'NAME_OF_JS_OBJECT' => array(
135
+	 *                'translate_ref' => esc_html__('localized_string', 'event_espresso'),
136
+	 *                'some_data' => 5
137
+	 *            )
138
+	 *        )
139
+	 *    )
140
+	 * )
141
+	 *
142
+	 * @var array
143
+	 */
144
+	protected $_scripts_styles;
145
+
146
+
147
+	/**
148
+	 * This is a property that will contain the current route.
149
+	 *
150
+	 * @var string;
151
+	 */
152
+	protected $_current_route;
153
+
154
+
155
+	/**
156
+	 * this optional property can be set by child classes to override the priority for the automatic action/filter hook
157
+	 * loading in the `_load_routed_hooks()` method.  Please follow this format: array(
158
+	 *    'wp_hook_reference' => 1
159
+	 *    )
160
+	 * )
161
+	 *
162
+	 * @var array
163
+	 */
164
+	protected $_wp_action_filters_priority;
165
+
166
+
167
+	/**
168
+	 * This just holds a merged array of the request vars
169
+	 *
170
+	 * @var array
171
+	 */
172
+	protected $_req_data;
173
+
174
+	/**
175
+	 * @var array
176
+	 */
177
+	protected $_scripts;
178
+
179
+	/**
180
+	 * @var array
181
+	 */
182
+	protected $_styles;
183
+
184
+	/**
185
+	 * This just holds an instance of the page object for this hook
186
+	 *
187
+	 * @var EE_Admin_Page
188
+	 */
189
+	protected $_page_object;
190
+
191
+
192
+	/**
193
+	 * This holds the EE_Admin_Page object from the calling admin page that this object hooks into.
194
+	 *
195
+	 * @var EE_Admin_Page|EE_Admin_Page_CPT
196
+	 */
197
+	protected $_adminpage_obj;
198
+
199
+
200
+	/**
201
+	 * Holds EE_Registry object
202
+	 *
203
+	 * @var EE_Registry
204
+	 */
205
+	protected $EE = null;
206
+
207
+	/**
208
+	 * @var RequestInterface
209
+	 */
210
+	protected $request;
211
+
212
+
213
+	/**
214
+	 * constructor
215
+	 *
216
+	 * @param EE_Admin_Page $admin_page
217
+	 * @throws EE_Error
218
+	 */
219
+	public function __construct(EE_Admin_Page $admin_page)
220
+	{
221
+		$this->_adminpage_obj = $admin_page;
222
+		$this->request        = LoaderFactory::getLoader()->getShared(RequestInterface::class);
223
+		$this->_req_data      = $this->request->requestParams();
224
+		$current_page = $this->request->getRequestParam('page');
225
+		$current_page = $this->request->getRequestParam('current_page', $current_page);
226
+		// first let's verify we're on the right page
227
+		if ($current_page !== $this->_adminpage_obj->page_slug) {
228
+			return;
229
+		}
230
+		$this->_set_defaults();
231
+		$this->_set_hooks_properties();
232
+		// get out nothing more to be done here.
233
+		// allow for extends to modify properties
234
+		if (method_exists($this, '_extend_properties')) {
235
+			$this->_extend_properties();
236
+		}
237
+		$this->_set_page_object();
238
+		$this->_init_hooks();
239
+		$this->_load_custom_methods();
240
+		$this->_load_routed_hooks();
241
+		add_action('admin_enqueue_scripts', [$this, 'enqueue_scripts_styles']);
242
+		add_action('admin_enqueue_scripts', [$this, 'add_metaboxes'], 20);
243
+		add_action('admin_enqueue_scripts', [$this, 'remove_metaboxes'], 15);
244
+		$this->_ajax_hooks();
245
+	}
246
+
247
+
248
+	/**
249
+	 * used by child classes to set the following properties:
250
+	 * $_ajax_func (optional)
251
+	 * $_init_func (optional)
252
+	 * $_metaboxes (optional)
253
+	 * $_scripts (optional)
254
+	 * $_styles (optional)
255
+	 * $_name (required)
256
+	 * Also in this method will be registered any scripts or styles loaded on the targeted page (as indicated in the
257
+	 * _scripts/_styles properties) Also children should place in this method any filters/actions that have to happen
258
+	 * really early on page load (just after admin_init) if they want to have them registered for handling early.
259
+	 *
260
+	 * @abstract
261
+	 * @return void
262
+	 */
263
+	abstract protected function _set_hooks_properties();
264
+
265
+
266
+	/**
267
+	 * The hooks for enqueue_scripts and enqueue_styles will be run in here.  Child classes need to define their
268
+	 * scripts and styles in the relevant $_scripts and $_styles properties.  Child classes must have also already
269
+	 * registered the scripts and styles using wp_register_script and wp_register_style functions.
270
+	 *
271
+	 * @return void
272
+	 * @throws EE_Error
273
+	 */
274
+	public function enqueue_scripts_styles()
275
+	{
276
+		if (! empty($this->_scripts_styles)) {
277
+			// first let's do all the registrations
278
+			if (! isset($this->_scripts_styles['registers'])) {
279
+				$msg[] = esc_html__(
280
+					'There is no "registers" index in the <code>$this->_scripts_styles</code> property.',
281
+					'event_espresso'
282
+				);
283
+				$msg[] = sprintf(
284
+					esc_html__(
285
+						'Make sure you read the phpdoc comments above the definition of the $_scripts_styles property in the <code>EE_Admin_Hooks</code> class and modify according in the %s child',
286
+						'event_espresso'
287
+					),
288
+					'<strong>' . $this->caller . '</strong>'
289
+				);
290
+				throw new EE_Error(implode('||', $msg));
291
+			}
292
+			$defaults = [
293
+				'type'    => 'js',
294
+				'url'     => '',
295
+				'depends' => [],
296
+				'version' => EVENT_ESPRESSO_VERSION,
297
+				'footer'  => true,
298
+			];
299
+			foreach ($this->_scripts_styles['registers'] as $ref => $details) {
300
+				$details = wp_parse_args($details, $defaults);
301
+				$type    = $details['type'];
302
+				$url     = $details['url'];
303
+				$depends = $details['depends'];
304
+				$version = $details['version'];
305
+				$footer  = $details['footer'];
306
+				// let's make sure that we set the 'registers' type if it's not set!
307
+				// We need it later to determine which enqueue we do
308
+				$this->_scripts_styles['registers'][ $ref ]['type'] = $type;
309
+				// let's make sure we're not missing any REQUIRED parameters
310
+				if (empty($url)) {
311
+					$msg[] = sprintf(
312
+						esc_html__('Missing the url for the requested %s', 'event_espresso'),
313
+						$type == 'js' ? 'script' : 'stylesheet'
314
+					);
315
+					$msg[] = sprintf(
316
+						esc_html__(
317
+							'Doublecheck your <code>$this->_scripts_styles</code> array in %s and make sure that there is a "url" set for the %s ref',
318
+							'event_espresso'
319
+						),
320
+						'<strong>' . $this->caller . '</strong>',
321
+						$ref
322
+					);
323
+					throw new EE_Error(implode('||', $msg));
324
+				}
325
+				// made it here so let's do the appropriate registration
326
+				$type == 'js'
327
+					? wp_register_script($ref, $url, $depends, $version, $footer)
328
+					: wp_register_style(
329
+						$ref,
330
+						$url,
331
+						$depends,
332
+						$version
333
+					);
334
+			}
335
+			// k now let's do the enqueues
336
+			if (! isset($this->_scripts_styles['enqueues'])) {
337
+				return;
338
+			}  //not sure if we should throw an error here or not.
339
+
340
+			foreach ($this->_scripts_styles['enqueues'] as $ref => $routes) {
341
+				// make sure $routes is an array
342
+				$routes = (array) $routes;
343
+				if (in_array($this->_current_route, $routes)) {
344
+					$this->_scripts_styles['registers'][ $ref ]['type'] == 'js' ? wp_enqueue_script($ref)
345
+						: wp_enqueue_style($ref);
346
+					// if we have a localization for the script let's do that too.
347
+					if (isset($this->_scripts_styles['localize'][ $ref ])) {
348
+						foreach ($this->_scripts_styles['localize'][ $ref ] as $object_name => $indexes) {
349
+							wp_localize_script(
350
+								$ref,
351
+								$object_name,
352
+								$this->_scripts_styles['localize'][ $ref ][ $object_name ]
353
+							);
354
+						}
355
+					}
356
+				}
357
+			}
358
+			// let's do the deregisters
359
+			if (! isset($this->_scripts_styles['deregisters'])) {
360
+				return;
361
+			}
362
+			foreach ($this->_scripts_styles['deregisters'] as $ref => $details) {
363
+				$defaults = ['type' => 'js'];
364
+				$details  = wp_parse_args($details, $defaults);
365
+				$details['type'] === 'js' ? wp_deregister_script($ref) : wp_deregister_style($ref);
366
+			}
367
+		}
368
+	}
369
+
370
+
371
+	/**
372
+	 * just set the defaults for the hooks properties.
373
+	 *
374
+	 * @return void
375
+	 */
376
+	private function _set_defaults()
377
+	{
378
+		$this->_ajax_func                  = [];
379
+		$this->_init_func                  = [];
380
+		$this->_metaboxes                  = [];
381
+		$this->_scripts                    = [];
382
+		$this->_styles                     = [];
383
+		$this->_wp_action_filters_priority = [];
384
+		$this->_current_route              = $this->getCurrentRoute();
385
+		$this->caller                      = get_class($this);
386
+		$this->_extend                     = (bool) stripos($this->caller, 'Extend');
387
+	}
388
+
389
+
390
+	/**
391
+	 * A helper for determining the current route.
392
+	 *
393
+	 * @return string
394
+	 */
395
+	private function getCurrentRoute()
396
+	{
397
+		$action = $this->request->getRequestParam('action');
398
+		// list tables do something else with 'action' for bulk actions.
399
+		$action = $action !== '-1' && $action !== '' ? $action : 'default';
400
+		$route  = $this->request->getRequestParam('route');
401
+		// we set a 'route' variable in some cases where action is being used by something else.
402
+		return $action === 'default' && $route !== '' ? $route : $action;
403
+	}
404
+
405
+
406
+	/**
407
+	 * this sets the _page_object property
408
+	 *
409
+	 * @return void
410
+	 * @throws EE_Error
411
+	 */
412
+	protected function _set_page_object()
413
+	{
414
+		if ($this->_page_object instanceof EE_Admin_Page) {
415
+			return;
416
+		}
417
+		// first make sure $this->_name is set
418
+		if (empty($this->_name)) {
419
+			$msg[] = esc_html__('We can\'t load the page object', 'event_espresso');
420
+			$msg[] = sprintf(
421
+				esc_html__("This is because the %s child class has not set the '_name' property", 'event_espresso'),
422
+				$this->caller
423
+			);
424
+			throw new EE_Error(implode('||', $msg));
425
+		}
426
+		// change "the_message" to "the message"
427
+		$class_name = str_replace('_', ' ', $this->_name);
428
+		// change "the message" to "The_Message_Admin_Page"
429
+		$class_name = str_replace(' ', '_', ucwords($class_name)) . '_Admin_Page';
430
+		// first default file (if exists)
431
+		$decaf_file = EE_ADMIN_PAGES . $this->_name . '/' . $class_name . '.core.php';
432
+		if (is_readable($decaf_file)) {
433
+			require_once($decaf_file);
434
+		}
435
+		// now we have to do require for extended file (if needed)
436
+		if ($this->_extend) {
437
+			require_once(EE_CORE_CAF_ADMIN_EXTEND . $this->_name . '/Extend_' . $class_name . '.core.php');
438
+			// and extend the class name as well
439
+			$class_name = 'Extend_' . $class_name;
440
+		}
441
+		// let's make sure the class exists
442
+		if (! class_exists($class_name)) {
443
+			$msg[] = esc_html__('We can\'t load the page object', 'event_espresso');
444
+			$msg[] = sprintf(
445
+				esc_html__(
446
+					'The class name that was given is %s. Check the spelling and make sure its correct, also there needs to be an autoloader setup for the class',
447
+					'event_espresso'
448
+				),
449
+				$class_name
450
+			);
451
+			throw new EE_Error(implode('||', $msg));
452
+		}
453
+		$this->_page_object = LoaderFactory::getLoader()->getShared($class_name, [false]);
454
+		$this->_page_object->initializePage();
455
+	}
456
+
457
+
458
+	/**
459
+	 * Child "hook" classes can declare any methods that they want executed when a specific page route is loaded.  The
460
+	 * advantage of this is when doing things like running our own db interactions on saves etc.  Remember that
461
+	 * $this->_req_data (all the _POST and _GET data) is available to your methods.
462
+	 *
463
+	 * @return void
464
+	 */
465
+	private function _load_custom_methods()
466
+	{
467
+		/**
468
+		 * method cannot be named 'default' (@see http://us3.php
469
+		 * .net/manual/en/reserved.keywords.php) so need to
470
+		 * handle routes that are "default"
471
+		 *
472
+		 * @since 4.3.0
473
+		 */
474
+		$method_callback = $this->_current_route == 'default' ? 'default_callback' : $this->_current_route;
475
+		// these run before the Admin_Page route executes.
476
+		if (method_exists($this, $method_callback)) {
477
+			call_user_func([$this, $method_callback]);
478
+		}
479
+		// these run via the _redirect_after_action method in EE_Admin_Page which usually happens after non_UI methods in EE_Admin_Page classes.  There are two redirect actions, the first fires before $query_args might be manipulated by "save and close" actions and the seond fires right before the actual redirect happens.
480
+		// first the actions
481
+		// note that these action hooks will have the $query_args value available.
482
+		$admin_class_name = get_class($this->_adminpage_obj);
483
+		if (method_exists($this, '_redirect_action_early_' . $this->_current_route)) {
484
+			add_action(
485
+				'AHEE__'
486
+				. $admin_class_name
487
+				. '___redirect_after_action__before_redirect_modification_'
488
+				. $this->_current_route,
489
+				[$this, '_redirect_action_early_' . $this->_current_route],
490
+				10
491
+			);
492
+		}
493
+		if (method_exists($this, '_redirect_action_' . $this->_current_route)) {
494
+			add_action(
495
+				'AHEE_redirect_' . $admin_class_name . $this->_current_route,
496
+				[$this, '_redirect_action_' . $this->_current_route],
497
+				10
498
+			);
499
+		}
500
+		// let's hook into the _redirect itself and allow for changing where the user goes after redirect.  This will have $query_args and $redirect_url available.
501
+		if (method_exists($this, '_redirect_filter_' . $this->_current_route)) {
502
+			add_filter(
503
+				'FHEE_redirect_' . $admin_class_name . $this->_current_route,
504
+				[$this, '_redirect_filter_' . $this->_current_route],
505
+				10,
506
+				2
507
+			);
508
+		}
509
+	}
510
+
511
+
512
+	/**
513
+	 * This method will search for a corresponding method with a name matching the route and the wp_hook to run.  This
514
+	 * allows child hook classes to target hooking into a specific wp action or filter hook ONLY on a certain route.
515
+	 * just remember, methods MUST be public Future hooks should be added in here to be access by child classes.
516
+	 *
517
+	 * @return void
518
+	 */
519
+	private function _load_routed_hooks()
520
+	{
521
+
522
+		// this array provides the hook action names that will be referenced.  Key is the action. Value is an array with the type (action or filter) and the number of parameters for the hook.  We'll default all priorities for automatic hooks to 10.
523
+		$hook_filter_array = [
524
+			'admin_footer'                                                                            => [
525
+				'type'     => 'action',
526
+				'argnum'   => 1,
527
+				'priority' => 10,
528
+			],
529
+			'FHEE_list_table_views_' . $this->_adminpage_obj->page_slug . '_' . $this->_current_route => [
530
+				'type'     => 'filter',
531
+				'argnum'   => 1,
532
+				'priority' => 10,
533
+			],
534
+			'FHEE_list_table_views_' . $this->_adminpage_obj->page_slug                               => [
535
+				'type'     => 'filter',
536
+				'argnum'   => 1,
537
+				'priority' => 10,
538
+			],
539
+			'FHEE_list_table_views'                                                                   => [
540
+				'type'     => 'filter',
541
+				'argnum'   => 1,
542
+				'priority' => 10,
543
+			],
544
+			'AHEE__EE_Admin_Page___display_admin_page__modify_metaboxes'                              => [
545
+				'type'     => 'action',
546
+				'argnum'   => 1,
547
+				'priority' => 10,
548
+			],
549
+		];
550
+		foreach ($hook_filter_array as $hook => $args) {
551
+			if (method_exists($this, $this->_current_route . '_' . $hook)) {
552
+				if (isset($this->_wp_action_filters_priority[ $hook ])) {
553
+					$args['priority'] = $this->_wp_action_filters_priority[ $hook ];
554
+				}
555
+				if ($args['type'] == 'action') {
556
+					add_action(
557
+						$hook,
558
+						[$this, $this->_current_route . '_' . $hook],
559
+						$args['priority'],
560
+						$args['argnum']
561
+					);
562
+				} else {
563
+					add_filter(
564
+						$hook,
565
+						[$this, $this->_current_route . '_' . $hook],
566
+						$args['priority'],
567
+						$args['argnum']
568
+					);
569
+				}
570
+			}
571
+		}
572
+	}
573
+
574
+
575
+	/**
576
+	 * Loop throught the $_ajax_func array and add_actions for the array.
577
+	 *
578
+	 * @return void
579
+	 * @throws EE_Error
580
+	 */
581
+	private function _ajax_hooks()
582
+	{
583
+		if (empty($this->_ajax_func)) {
584
+			return;
585
+		} //get out there's nothing to take care of.
586
+		foreach ($this->_ajax_func as $action => $method) {
587
+			// make sure method exists
588
+			if (! method_exists($this, $method)) {
589
+				$msg[] = esc_html__(
590
+					'There is no corresponding method for the hook labeled in the _ajax_func array',
591
+					'event_espresso'
592
+				) . '<br />';
593
+				$msg[] = sprintf(
594
+					esc_html__(
595
+						'The method name given in the array is %s, check the spelling and make sure it exists in the %s class',
596
+						'event_espresso'
597
+					),
598
+					$method,
599
+					$this->caller
600
+				);
601
+				throw new EE_Error(implode('||', $msg));
602
+			}
603
+			add_action('wp_ajax_' . $action, [$this, $method]);
604
+		}
605
+	}
606
+
607
+
608
+	/**
609
+	 * Loop throught the $_init_func array and add_actions for the array.
610
+	 *
611
+	 * @return void
612
+	 * @throws EE_Error
613
+	 */
614
+	protected function _init_hooks()
615
+	{
616
+		if (empty($this->_init_func)) {
617
+			return;
618
+		}
619
+		// get out there's nothing to take care of.
620
+		// We need to determine what page_route we are on!
621
+		foreach ($this->_init_func as $route => $method) {
622
+			// make sure method exists
623
+			if (! method_exists($this, $method)) {
624
+				$msg[] = esc_html__(
625
+					'There is no corresponding method for the hook labeled in the _init_func array',
626
+					'event_espresso'
627
+				) . '<br />';
628
+				$msg[] = sprintf(
629
+					esc_html__(
630
+						'The method name given in the array is %s, check the spelling and make sure it exists in the %s class',
631
+						'event_espresso'
632
+					),
633
+					$method,
634
+					$this->caller
635
+				);
636
+				throw new EE_Error(implode('||', $msg));
637
+			}
638
+			if ($route == $this->_current_route) {
639
+				add_action('admin_init', [$this, $method]);
640
+			}
641
+		}
642
+	}
643
+
644
+
645
+	/**
646
+	 * Loop through the _metaboxes property and add_metaboxes accordingly
647
+	 * //todo we could eventually make this a config component class (i.e. new EE_Metabox);
648
+	 *
649
+	 * @return void
650
+	 * @throws EE_Error
651
+	 */
652
+	public function add_metaboxes()
653
+	{
654
+		if (empty($this->_metaboxes)) {
655
+			return;
656
+		} //get out we don't have any metaboxes to set for this connection
657
+		$this->_handle_metabox_array($this->_metaboxes);
658
+	}
659
+
660
+
661
+	/**
662
+	 * @param array $boxes
663
+	 * @param bool  $add
664
+	 * @throws EE_Error
665
+	 */
666
+	private function _handle_metabox_array(array $boxes, $add = true)
667
+	{
668
+		foreach ($boxes as $box) {
669
+			if (! isset($box['page_route'])) {
670
+				continue;
671
+			}
672
+			// we don't have a valid array
673
+			// let's make sure $box['page_route'] is an array so the "foreach" will work.
674
+			$box['page_route'] = (array) $box['page_route'];
675
+			foreach ($box['page_route'] as $route) {
676
+				if ($route != $this->_current_route) {
677
+					continue;
678
+				} //get out we only add metaboxes for set route.
679
+				if ($add) {
680
+					$this->_add_metabox($box);
681
+				} else {
682
+					$this->_remove_metabox($box);
683
+				}
684
+			}
685
+		}
686
+	}
687
+
688
+
689
+	/**
690
+	 * Loop through the _remove_metaboxes property and remove metaboxes accordingly.
691
+	 *
692
+	 * @return void
693
+	 * @throws EE_Error
694
+	 */
695
+	public function remove_metaboxes()
696
+	{
697
+		if (empty($this->_remove_metaboxes)) {
698
+			return;
699
+		} //get out there are no metaboxes to remove
700
+		$this->_handle_metabox_array($this->_remove_metaboxes, false);
701
+	}
702
+
703
+
704
+	/**
705
+	 * This just handles adding a metabox
706
+	 *
707
+	 * @param array $args an array of args that have been set for this metabox by the child class
708
+	 * @throws EE_Error
709
+	 */
710
+	private function _add_metabox($args)
711
+	{
712
+		$current_screen = get_current_screen();
713
+		$screen_id      = is_object($current_screen) ? $current_screen->id : null;
714
+		$callback       = $args['func'] ?? 'some_invalid_callback';
715
+		$callback_function = is_array($callback) ? end($callback) : $callback;
716
+		// set defaults
717
+		$defaults      = [
718
+			'callback_args' => [],
719
+			'context'       => 'advanced',
720
+			'func'          => $callback,
721
+			'id'            => $this->caller . '_' . $callback_function . '_metabox',
722
+			'label'         => $this->caller,
723
+			'page'          => isset($args['page']) ? $args['page'] : $screen_id,
724
+			'priority'      => 'default',
725
+		];
726
+		$args          = wp_parse_args($args, $defaults);
727
+		$callback_args = $args['callback_args'];
728
+		$context       = $args['context'];
729
+		$id            = $args['id'];
730
+		$label         = $args['label'];
731
+		$page          = $args['page'];
732
+		$priority      = $args['priority'];
733
+		// make sure method exists
734
+		if (! method_exists($this, $callback_function)) {
735
+			$msg[] =
736
+				esc_html__('There is no corresponding method to display the metabox content', 'event_espresso')
737
+				. '<br />';
738
+			$msg[] = sprintf(
739
+				esc_html__(
740
+					'The method name given in the array is %s, check the spelling and make sure it exists in the %s class',
741
+					'event_espresso'
742
+				),
743
+				$callback_function,
744
+				$this->caller
745
+			);
746
+			throw new EE_Error(implode('||', $msg));
747
+		}
748
+		// everything checks out so let's add the metabox
749
+		add_meta_box($id, $label, [$this, $callback_function], $page, $context, $priority, $callback_args);
750
+		add_filter(
751
+			"postbox_classes_{$page}_{$id}",
752
+			function ($classes) {
753
+				array_push($classes, 'ee-admin-container');
754
+				return $classes;
755
+			}
756
+		);
757
+	}
758
+
759
+
760
+	private function _remove_metabox($args)
761
+	{
762
+		$current_screen = get_current_screen();
763
+		$screen_id      = is_object($current_screen) ? $current_screen->id : null;
764
+		$func           = isset($args['func']) ? $args['func'] : 'some_invalid_callback';
765
+		// set defaults
766
+		$defaults = [
767
+			'context' => 'default',
768
+			'id'      => isset($args['id'])
769
+				? $args['id']
770
+				: $this->_current_route . '_' . $this->caller . '_' . $func . '_metabox',
771
+			'screen'  => isset($args['screen']) ? $args['screen'] : $screen_id,
772
+		];
773
+		$args     = wp_parse_args($args, $defaults);
774
+		$context  = $args['context'];
775
+		$id       = $args['id'];
776
+		$screen   = $args['screen'];
777
+		// everything checks out so lets remove the box!
778
+		remove_meta_box($id, $screen, $context);
779
+	}
780 780
 }
Please login to merge, or discard this patch.
core/admin/EE_Admin_Page.core.php 2 patches
Indentation   +4195 added lines, -4195 removed lines patch added patch discarded remove patch
@@ -23,4283 +23,4283 @@
 block discarded – undo
23 23
  */
24 24
 abstract class EE_Admin_Page extends EE_Base implements InterminableInterface
25 25
 {
26
-    /**
27
-     * @var EE_Admin_Config
28
-     */
29
-    protected $admin_config;
26
+	/**
27
+	 * @var EE_Admin_Config
28
+	 */
29
+	protected $admin_config;
30 30
 
31
-    /**
32
-     * @var LoaderInterface
33
-     */
34
-    protected $loader;
31
+	/**
32
+	 * @var LoaderInterface
33
+	 */
34
+	protected $loader;
35 35
 
36
-    /**
37
-     * @var RequestInterface
38
-     */
39
-    protected $request;
36
+	/**
37
+	 * @var RequestInterface
38
+	 */
39
+	protected $request;
40 40
 
41
-    // set in _init_page_props()
42
-    public $page_slug;
41
+	// set in _init_page_props()
42
+	public $page_slug;
43 43
 
44
-    public $page_label;
44
+	public $page_label;
45 45
 
46
-    public $page_folder;
46
+	public $page_folder;
47 47
 
48
-    // set in define_page_props()
49
-    protected $_admin_base_url;
48
+	// set in define_page_props()
49
+	protected $_admin_base_url;
50 50
 
51
-    protected $_admin_base_path;
51
+	protected $_admin_base_path;
52 52
 
53
-    protected $_admin_page_title;
53
+	protected $_admin_page_title;
54 54
 
55
-    protected $_labels;
55
+	protected $_labels;
56 56
 
57 57
 
58
-    // set early within EE_Admin_Init
59
-    protected $_wp_page_slug;
58
+	// set early within EE_Admin_Init
59
+	protected $_wp_page_slug;
60 60
 
61
-    // nav tabs
62
-    protected $_nav_tabs;
61
+	// nav tabs
62
+	protected $_nav_tabs;
63 63
 
64
-    protected $_default_nav_tab_name;
64
+	protected $_default_nav_tab_name;
65 65
 
66 66
 
67
-    // template variables (used by templates)
68
-    protected $_template_path;
67
+	// template variables (used by templates)
68
+	protected $_template_path;
69 69
 
70
-    protected $_column_template_path;
70
+	protected $_column_template_path;
71 71
 
72
-    /**
73
-     * @var array $_template_args
74
-     */
75
-    protected $_template_args = [];
72
+	/**
73
+	 * @var array $_template_args
74
+	 */
75
+	protected $_template_args = [];
76 76
 
77
-    /**
78
-     * this will hold the list table object for a given view.
79
-     *
80
-     * @var EE_Admin_List_Table $_list_table_object
81
-     */
82
-    protected $_list_table_object;
77
+	/**
78
+	 * this will hold the list table object for a given view.
79
+	 *
80
+	 * @var EE_Admin_List_Table $_list_table_object
81
+	 */
82
+	protected $_list_table_object;
83 83
 
84
-    // boolean
85
-    protected $_is_UI_request; // this starts at null so we can have no header routes progress through two states.
84
+	// boolean
85
+	protected $_is_UI_request; // this starts at null so we can have no header routes progress through two states.
86 86
 
87
-    protected $_routing;
87
+	protected $_routing;
88 88
 
89
-    // list table args
90
-    protected $_view;
89
+	// list table args
90
+	protected $_view;
91 91
 
92
-    protected $_views;
92
+	protected $_views;
93 93
 
94 94
 
95
-    // action => method pairs used for routing incoming requests
96
-    protected $_page_routes;
95
+	// action => method pairs used for routing incoming requests
96
+	protected $_page_routes;
97 97
 
98
-    /**
99
-     * @var array $_page_config
100
-     */
101
-    protected $_page_config;
98
+	/**
99
+	 * @var array $_page_config
100
+	 */
101
+	protected $_page_config;
102 102
 
103
-    /**
104
-     * the current page route and route config
105
-     *
106
-     * @var string $_route
107
-     */
108
-    protected $_route;
103
+	/**
104
+	 * the current page route and route config
105
+	 *
106
+	 * @var string $_route
107
+	 */
108
+	protected $_route;
109 109
 
110
-    /**
111
-     * @var string $_cpt_route
112
-     */
113
-    protected $_cpt_route;
110
+	/**
111
+	 * @var string $_cpt_route
112
+	 */
113
+	protected $_cpt_route;
114 114
 
115
-    /**
116
-     * @var array $_route_config
117
-     */
118
-    protected $_route_config;
115
+	/**
116
+	 * @var array $_route_config
117
+	 */
118
+	protected $_route_config;
119 119
 
120
-    /**
121
-     * Used to hold default query args for list table routes to help preserve stickiness of filters for carried out
122
-     * actions.
123
-     *
124
-     * @since 4.6.x
125
-     * @var array.
126
-     */
127
-    protected $_default_route_query_args;
120
+	/**
121
+	 * Used to hold default query args for list table routes to help preserve stickiness of filters for carried out
122
+	 * actions.
123
+	 *
124
+	 * @since 4.6.x
125
+	 * @var array.
126
+	 */
127
+	protected $_default_route_query_args;
128 128
 
129
-    // set via request page and action args.
130
-    protected $_current_page;
129
+	// set via request page and action args.
130
+	protected $_current_page;
131 131
 
132
-    protected $_current_view;
132
+	protected $_current_view;
133 133
 
134
-    protected $_current_page_view_url;
134
+	protected $_current_page_view_url;
135 135
 
136
-    /**
137
-     * unprocessed value for the 'action' request param (default '')
138
-     *
139
-     * @var string
140
-     */
141
-    protected $raw_req_action = '';
136
+	/**
137
+	 * unprocessed value for the 'action' request param (default '')
138
+	 *
139
+	 * @var string
140
+	 */
141
+	protected $raw_req_action = '';
142 142
 
143
-    /**
144
-     * unprocessed value for the 'page' request param (default '')
145
-     *
146
-     * @var string
147
-     */
148
-    protected $raw_req_page = '';
149
-
150
-    /**
151
-     * sanitized request action (and nonce)
152
-     *
153
-     * @var string
154
-     */
155
-    protected $_req_action = '';
156
-
157
-    /**
158
-     * sanitized request action nonce
159
-     *
160
-     * @var string
161
-     */
162
-    protected $_req_nonce = '';
163
-
164
-    /**
165
-     * @var string
166
-     */
167
-    protected $_search_btn_label = '';
168
-
169
-    /**
170
-     * @var string
171
-     */
172
-    protected $_search_box_callback = '';
173
-
174
-    /**
175
-     * @var WP_Screen
176
-     */
177
-    protected $_current_screen;
178
-
179
-    // for holding EE_Admin_Hooks object when needed (set via set_hook_object())
180
-    protected $_hook_obj;
181
-
182
-    // for holding incoming request data
183
-    protected $_req_data = [];
184
-
185
-    // yes / no array for admin form fields
186
-    protected $_yes_no_values = [];
187
-
188
-    // some default things shared by all child classes
189
-    protected $_default_espresso_metaboxes = [
190
-        '_espresso_news_post_box',
191
-        '_espresso_links_post_box',
192
-        '_espresso_ratings_request',
193
-        '_espresso_sponsors_post_box',
194
-    ];
195
-
196
-    /**
197
-     * @var EE_Registry
198
-     */
199
-    protected $EE;
200
-
201
-
202
-    /**
203
-     * This is just a property that flags whether the given route is a caffeinated route or not.
204
-     *
205
-     * @var boolean
206
-     */
207
-    protected $_is_caf = false;
208
-
209
-    /**
210
-     * whether or not initializePage() has run
211
-     *
212
-     * @var boolean
213
-     */
214
-    protected $initialized = false;
215
-
216
-    /**
217
-     * @var FeatureFlags
218
-     */
219
-    protected $feature;
220
-
221
-
222
-    /**
223
-     * @var string
224
-     */
225
-    protected $class_name;
226
-
227
-    /**
228
-     * if the current class is an admin page extension, like: Extend_Events_Admin_Page,
229
-     * then this would be the parent classname: Events_Admin_Page
230
-     *
231
-     * @var string
232
-     */
233
-    protected $base_class_name;
234
-
235
-
236
-    /**
237
-     * @Constructor
238
-     * @param bool $routing indicate whether we want to just load the object and handle routing or just load the object.
239
-     * @throws InvalidArgumentException
240
-     * @throws InvalidDataTypeException
241
-     * @throws InvalidInterfaceException
242
-     * @throws ReflectionException
243
-     */
244
-    public function __construct($routing = true)
245
-    {
246
-        $this->loader = LoaderFactory::getLoader();
247
-        $this->admin_config = $this->loader->getShared('EE_Admin_Config');
248
-        $this->feature = $this->loader->getShared(FeatureFlags::class);
249
-        $this->request = $this->loader->getShared(RequestInterface::class);
250
-        // routing enabled?
251
-        $this->_routing = $routing;
252
-
253
-        $this->class_name = get_class($this);
254
-        $this->base_class_name = strpos($this->class_name, 'Extend_') === 0
255
-            ? str_replace('Extend_', '', $this->class_name)
256
-            : '';
257
-
258
-        if (strpos($this->_get_dir(), 'caffeinated') !== false) {
259
-            $this->_is_caf = true;
260
-        }
261
-        $this->_yes_no_values = [
262
-            ['id' => true, 'text' => esc_html__('Yes', 'event_espresso')],
263
-            ['id' => false, 'text' => esc_html__('No', 'event_espresso')],
264
-        ];
265
-        // set the _req_data property.
266
-        $this->_req_data = $this->request->requestParams();
267
-    }
268
-
269
-
270
-    /**
271
-     * @return EE_Admin_Config
272
-     */
273
-    public function adminConfig(): EE_Admin_Config
274
-    {
275
-        return $this->admin_config;
276
-    }
277
-
278
-
279
-    /**
280
-     * @return FeatureFlags
281
-     */
282
-    public function feature(): FeatureFlags
283
-    {
284
-        return $this->feature;
285
-    }
286
-
287
-
288
-    /**
289
-     * This logic used to be in the constructor, but that caused a chicken <--> egg scenario
290
-     * for child classes that needed to set properties prior to these methods getting called,
291
-     * but also needed the parent class to have its construction completed as well.
292
-     * Bottom line is that constructors should ONLY be used for setting initial properties
293
-     * and any complex initialization logic should only run after instantiation is complete.
294
-     *
295
-     * This method gets called immediately after construction from within
296
-     *      EE_Admin_Page_Init::_initialize_admin_page()
297
-     *
298
-     * @throws EE_Error
299
-     * @throws InvalidArgumentException
300
-     * @throws InvalidDataTypeException
301
-     * @throws InvalidInterfaceException
302
-     * @throws ReflectionException
303
-     * @since $VID:$
304
-     */
305
-    public function initializePage()
306
-    {
307
-        if ($this->initialized) {
308
-            return;
309
-        }
310
-        // set initial page props (child method)
311
-        $this->_init_page_props();
312
-        // set global defaults
313
-        $this->_set_defaults();
314
-        // set early because incoming requests could be ajax related and we need to register those hooks.
315
-        $this->_global_ajax_hooks();
316
-        $this->_ajax_hooks();
317
-        // other_page_hooks have to be early too.
318
-        $this->_do_other_page_hooks();
319
-        // set up page dependencies
320
-        $this->_before_page_setup();
321
-        $this->_page_setup();
322
-        $this->initialized = true;
323
-    }
324
-
325
-
326
-    /**
327
-     * _init_page_props
328
-     * Child classes use to set at least the following properties:
329
-     * $page_slug.
330
-     * $page_label.
331
-     *
332
-     * @abstract
333
-     * @return void
334
-     */
335
-    abstract protected function _init_page_props();
336
-
337
-
338
-    /**
339
-     * _ajax_hooks
340
-     * child classes put all their add_action('wp_ajax_{name_of_hook}') hooks in here.
341
-     * Note: within the ajax callback methods.
342
-     *
343
-     * @abstract
344
-     * @return void
345
-     */
346
-    abstract protected function _ajax_hooks();
347
-
348
-
349
-    /**
350
-     * _define_page_props
351
-     * child classes define page properties in here.  Must include at least:
352
-     * $_admin_base_url = base_url for all admin pages
353
-     * $_admin_page_title = default admin_page_title for admin pages
354
-     * $_labels = array of default labels for various automatically generated elements:
355
-     *    array(
356
-     *        'buttons' => array(
357
-     *            'add' => esc_html__('label for add new button'),
358
-     *            'edit' => esc_html__('label for edit button'),
359
-     *            'delete' => esc_html__('label for delete button')
360
-     *            )
361
-     *        )
362
-     *
363
-     * @abstract
364
-     * @return void
365
-     */
366
-    abstract protected function _define_page_props();
367
-
368
-
369
-    /**
370
-     * _set_page_routes
371
-     * child classes use this to define the page routes for all subpages handled by the class.  Page routes are
372
-     * assigned to a action => method pairs in an array and to the $_page_routes property.  Each page route must also
373
-     * have a 'default' route. Here's the format
374
-     * $this->_page_routes = array(
375
-     *        'default' => array(
376
-     *            'func' => '_default_method_handling_route',
377
-     *            'args' => array('array','of','args'),
378
-     *            'noheader' => true, //add this in if this page route is processed before any headers are loaded (i.e.
379
-     *            ajax request, backend processing)
380
-     *            'headers_sent_route'=>'headers_route_reference', //add this if noheader=>true, and you want to load a
381
-     *            headers route after.  The string you enter here should match the defined route reference for a
382
-     *            headers sent route.
383
-     *            'capability' => 'route_capability', //indicate a string for minimum capability required to access
384
-     *            this route.
385
-     *            'obj_id' => 10 // if this route has an object id, then this can include it (used for capability
386
-     *            checks).
387
-     *        ),
388
-     *        'insert_item' => '_method_for_handling_insert_item' //this can be used if all we need to have is a
389
-     *        handling method.
390
-     *        )
391
-     * )
392
-     *
393
-     * @abstract
394
-     * @return void
395
-     */
396
-    abstract protected function _set_page_routes();
397
-
398
-
399
-    /**
400
-     * _set_page_config
401
-     * child classes use this to define the _page_config array for all subpages handled by the class. Each key in the
402
-     * array corresponds to the page_route for the loaded page. Format:
403
-     * $this->_page_config = array(
404
-     *        'default' => array(
405
-     *            'labels' => array(
406
-     *                'buttons' => array(
407
-     *                    'add' => esc_html__('label for adding item'),
408
-     *                    'edit' => esc_html__('label for editing item'),
409
-     *                    'delete' => esc_html__('label for deleting item')
410
-     *                ),
411
-     *                'publishbox' => esc_html__('Localized Title for Publish metabox', 'event_espresso')
412
-     *            ), //optional an array of custom labels for various automatically generated elements to use on the
413
-     *            page. If this isn't present then the defaults will be used as set for the $this->_labels in
414
-     *            _define_page_props() method
415
-     *            'nav' => array(
416
-     *                'label' => esc_html__('Label for Tab', 'event_espresso').
417
-     *                'url' => 'http://someurl', //automatically generated UNLESS you define
418
-     *                'css_class' => 'css-class', //automatically generated UNLESS you define
419
-     *                'order' => 10, //required to indicate tab position.
420
-     *                'persistent' => false //if you want the nav tab to ONLY display when the specific route is
421
-     *                displayed then add this parameter.
422
-     *            'list_table' => 'name_of_list_table' //string for list table class to be loaded for this admin_page.
423
-     *            'metaboxes' => array('metabox1', 'metabox2'), //if present this key indicates we want to load
424
-     *            metaboxes set for eventespresso admin pages.
425
-     *            'has_metaboxes' => true, //this boolean flag can simply be used to indicate if the route will have
426
-     *            metaboxes.  Typically this is used if the 'metaboxes' index is not used because metaboxes are added
427
-     *            later.  We just use this flag to make sure the necessary js gets enqueued on page load.
428
-     *            'has_help_popups' => false //defaults(true) //this boolean flag can simply be used to indicate if the
429
-     *            given route has help popups setup and if it does then we need to make sure thickbox is enqueued.
430
-     *            'columns' => array(4, 2), //this key triggers the setup of a page that uses columns (metaboxes).  The
431
-     *            array indicates the max number of columns (4) and the default number of columns on page load (2).
432
-     *            There is an option in the "screen_options" dropdown that is setup so users can pick what columns they
433
-     *            want to display.
434
-     *            'help_tabs' => array( //this is used for adding help tabs to a page
435
-     *                'tab_id' => array(
436
-     *                    'title' => 'tab_title',
437
-     *                    'filename' => 'name_of_file_containing_content', //this is the primary method for setting
438
-     *                    help tab content.  The fallback if it isn't present is to try a the callback.  Filename
439
-     *                    should match a file in the admin folder's "help_tabs" dir (ie..
440
-     *                    events/help_tabs/name_of_file_containing_content.help_tab.php)
441
-     *                    'callback' => 'callback_method_for_content', //if 'filename' isn't present then system will
442
-     *                    attempt to use the callback which should match the name of a method in the class
443
-     *                    ),
444
-     *                'tab2_id' => array(
445
-     *                    'title' => 'tab2 title',
446
-     *                    'filename' => 'file_name_2'
447
-     *                    'callback' => 'callback_method_for_content',
448
-     *                 ),
449
-     *            'help_sidebar' => 'callback_for_sidebar_content', //this is used for setting up the sidebar in the
450
-     *            help tab area on an admin page. @return void
451
-     *
452
-     * @abstract
453
-     */
454
-    abstract protected function _set_page_config();
455
-
456
-
457
-    /**
458
-     * _add_screen_options
459
-     * Child classes can add any extra wp_screen_options within this method using built-in WP functions/methods for
460
-     * doing so. Note child classes can also define _add_screen_options_($this->_current_view) to limit screen options
461
-     * to a particular view.
462
-     *
463
-     * @link   http://chrismarslender.com/wp-tutorials/wordpress-screen-options-tutorial/
464
-     *         see also WP_Screen object documents...
465
-     * @link   http://codex.wordpress.org/Class_Reference/WP_Screen
466
-     * @abstract
467
-     * @return void
468
-     */
469
-    abstract protected function _add_screen_options();
470
-
471
-
472
-    /**
473
-     * _add_feature_pointers
474
-     * Child classes should use this method for implementing any "feature pointers" (using built-in WP styling js).
475
-     * Note child classes can also define _add_feature_pointers_($this->_current_view) to limit screen options to a
476
-     * particular view. Note: this is just a placeholder for now.  Implementation will come down the road See:
477
-     * WP_Internal_Pointers class in wp-admin/includes/template.php for example (its a final class so can't be
478
-     * extended) also see:
479
-     *
480
-     * @link   http://eamann.com/tech/wordpress-portland/
481
-     * @abstract
482
-     * @return void
483
-     */
484
-    abstract protected function _add_feature_pointers();
485
-
486
-
487
-    /**
488
-     * load_scripts_styles
489
-     * child classes put their wp_enqueue_script and wp_enqueue_style hooks in here for anything they need loaded for
490
-     * their pages/subpages.  Note this is for all pages/subpages of the system.  You can also load only specific
491
-     * scripts/styles per view by putting them in a dynamic function in this format
492
-     * (load_scripts_styles_{$this->_current_view}) which matches your page route (action request arg)
493
-     *
494
-     * @abstract
495
-     * @return void
496
-     */
497
-    abstract public function load_scripts_styles();
498
-
499
-
500
-    /**
501
-     * admin_init
502
-     * Anything that should be set/executed at 'admin_init' WP hook runtime should be put in here.  This will apply to
503
-     * all pages/views loaded by child class.
504
-     *
505
-     * @abstract
506
-     * @return void
507
-     */
508
-    abstract public function admin_init();
509
-
510
-
511
-    /**
512
-     * admin_notices
513
-     * Anything triggered by the 'admin_notices' WP hook should be put in here.  This particular method will apply to
514
-     * all pages/views loaded by child class.
515
-     *
516
-     * @abstract
517
-     * @return void
518
-     */
519
-    abstract public function admin_notices();
520
-
521
-
522
-    /**
523
-     * admin_footer_scripts
524
-     * Anything triggered by the 'admin_print_footer_scripts' WP hook should be put in here. This particular method
525
-     * will apply to all pages/views loaded by child class.
526
-     *
527
-     * @return void
528
-     */
529
-    abstract public function admin_footer_scripts();
530
-
531
-
532
-    /**
533
-     * admin_footer
534
-     * anything triggered by the 'admin_footer' WP action hook should be added to here. This particular method will
535
-     * apply to all pages/views loaded by child class.
536
-     *
537
-     * @return void
538
-     */
539
-    public function admin_footer()
540
-    {
541
-    }
542
-
543
-
544
-    /**
545
-     * _global_ajax_hooks
546
-     * all global add_action('wp_ajax_{name_of_hook}') hooks in here.
547
-     * Note: within the ajax callback methods.
548
-     *
549
-     * @abstract
550
-     * @return void
551
-     */
552
-    protected function _global_ajax_hooks()
553
-    {
554
-        // for lazy loading of metabox content
555
-        add_action('wp_ajax_espresso-ajax-content', [$this, 'ajax_metabox_content'], 10);
556
-
557
-        add_action(
558
-            'wp_ajax_espresso_hide_status_change_notice',
559
-            [$this, 'hideStatusChangeNotice']
560
-        );
561
-        add_action(
562
-            'wp_ajax_nopriv_espresso_hide_status_change_notice',
563
-            [$this, 'hideStatusChangeNotice']
564
-        );
565
-    }
566
-
567
-
568
-    public function ajax_metabox_content()
569
-    {
570
-        $content_id  = $this->request->getRequestParam('contentid', '');
571
-        $content_url = $this->request->getRequestParam('contenturl', '', 'url');
572
-        EE_Admin_Page::cached_rss_display($content_id, $content_url);
573
-        wp_die();
574
-    }
575
-
576
-
577
-    public function hideStatusChangeNotice()
578
-    {
579
-        $response = [];
580
-        try {
581
-            /** @var StatusChangeNotice $status_change_notice */
582
-            $status_change_notice = $this->loader->getShared(
583
-                'EventEspresso\core\domain\services\admin\notices\status_change\StatusChangeNotice'
584
-            );
585
-            $response['success'] = $status_change_notice->dismiss() > -1;
586
-        } catch (Exception $exception) {
587
-            $response['errors'] = $exception->getMessage();
588
-        }
589
-        echo wp_json_encode($response);
590
-        exit();
591
-    }
592
-
593
-
594
-    /**
595
-     * allows extending classes do something specific before the parent constructor runs _page_setup().
596
-     *
597
-     * @return void
598
-     */
599
-    protected function _before_page_setup()
600
-    {
601
-        // default is to do nothing
602
-    }
603
-
604
-
605
-    /**
606
-     * Makes sure any things that need to be loaded early get handled.
607
-     * We also escape early here if the page requested doesn't match the object.
608
-     *
609
-     * @final
610
-     * @return void
611
-     * @throws EE_Error
612
-     * @throws InvalidArgumentException
613
-     * @throws ReflectionException
614
-     * @throws InvalidDataTypeException
615
-     * @throws InvalidInterfaceException
616
-     */
617
-    final protected function _page_setup()
618
-    {
619
-        // requires?
620
-        // admin_init stuff - global - we're setting this REALLY early
621
-        // so if EE_Admin pages have to hook into other WP pages they can.
622
-        // But keep in mind, not everything is available from the EE_Admin Page object at this point.
623
-        add_action('admin_init', [$this, 'admin_init_global'], 5);
624
-        // next verify if we need to load anything...
625
-        $this->_current_page = $this->request->getRequestParam('page', '', 'key');
626
-        $this->_current_page = $this->request->getRequestParam('current_page', $this->_current_page, 'key');
627
-        $this->page_folder   = strtolower(
628
-            str_replace(['_Admin_Page', 'Extend_'], '', $this->class_name)
629
-        );
630
-        global $ee_menu_slugs;
631
-        $ee_menu_slugs = (array) $ee_menu_slugs;
632
-        if (
633
-            ! $this->request->isAjax()
634
-            && (! $this->_current_page || ! isset($ee_menu_slugs[ $this->_current_page ]))
635
-        ) {
636
-            return;
637
-        }
638
-        // because WP List tables have two duplicate select inputs for choosing bulk actions,
639
-        // we need to copy the action from the second to the first
640
-        $action     = $this->request->getRequestParam('action', '-1', 'key');
641
-        $action2    = $this->request->getRequestParam('action2', '-1', 'key');
642
-        $action     = $action !== '-1' ? $action : $action2;
643
-        $req_action = $action !== '-1' ? $action : 'default';
644
-
645
-        // if a specific 'route' has been set, and the action is 'default' OR we are doing_ajax
646
-        // then let's use the route as the action.
647
-        // This covers cases where we're coming in from a list table that isn't on the default route.
648
-        $route = $this->request->getRequestParam('route');
649
-        $this->_req_action = $route && ($req_action === 'default' || $this->request->isAjax())
650
-            ? $route
651
-            : $req_action;
652
-
653
-        $this->_current_view = $this->_req_action;
654
-        $this->_req_nonce    = $this->_req_action . '_nonce';
655
-        $this->_define_page_props();
656
-        $this->_current_page_view_url = add_query_arg(
657
-            ['page' => $this->_current_page, 'action' => $this->_current_view],
658
-            $this->_admin_base_url
659
-        );
660
-        // set page configs
661
-        $this->_set_page_routes();
662
-        $this->_set_page_config();
663
-        // let's include any referrer data in our default_query_args for this route for "stickiness".
664
-        if ($this->request->requestParamIsSet('wp_referer')) {
665
-            $wp_referer = $this->request->getRequestParam('wp_referer');
666
-            if ($wp_referer) {
667
-                $this->_default_route_query_args['wp_referer'] = $wp_referer;
668
-            }
669
-        }
670
-        // for caffeinated and other extended functionality.
671
-        //  If there is a _extend_page_config method
672
-        // then let's run that to modify the all the various page configuration arrays
673
-        if (method_exists($this, '_extend_page_config')) {
674
-            $this->_extend_page_config();
675
-        }
676
-        // for CPT and other extended functionality.
677
-        // If there is an _extend_page_config_for_cpt
678
-        // then let's run that to modify all the various page configuration arrays.
679
-        if (method_exists($this, '_extend_page_config_for_cpt')) {
680
-            $this->_extend_page_config_for_cpt();
681
-        }
682
-        // filter routes and page_config so addons can add their stuff. Filtering done per class
683
-        $this->_page_routes = apply_filters(
684
-            'FHEE__' . $this->class_name . '__page_setup__page_routes',
685
-            $this->_page_routes,
686
-            $this
687
-        );
688
-        $this->_page_config = apply_filters(
689
-            'FHEE__' . $this->class_name . '__page_setup__page_config',
690
-            $this->_page_config,
691
-            $this
692
-        );
693
-        if ($this->base_class_name !== '') {
694
-            $this->_page_routes = apply_filters(
695
-                'FHEE__' . $this->base_class_name . '__page_setup__page_routes',
696
-                $this->_page_routes,
697
-                $this
698
-            );
699
-            $this->_page_config = apply_filters(
700
-                'FHEE__' . $this->base_class_name . '__page_setup__page_config',
701
-                $this->_page_config,
702
-                $this
703
-            );
704
-        }
705
-        // if AHEE__EE_Admin_Page__route_admin_request_$this->_current_view method is present
706
-        // then we call it hooked into the AHEE__EE_Admin_Page__route_admin_request action
707
-        if (method_exists($this, 'AHEE__EE_Admin_Page__route_admin_request_' . $this->_current_view)) {
708
-            add_action(
709
-                'AHEE__EE_Admin_Page__route_admin_request',
710
-                [$this, 'AHEE__EE_Admin_Page__route_admin_request_' . $this->_current_view],
711
-                10,
712
-                2
713
-            );
714
-        }
715
-        // next route only if routing enabled
716
-        if ($this->_routing && ! $this->request->isAjax()) {
717
-            $this->_verify_routes();
718
-            // next let's just check user_access and kill if no access
719
-            $this->check_user_access();
720
-            if ($this->_is_UI_request) {
721
-                // admin_init stuff - global, all views for this page class, specific view
722
-                add_action('admin_init', [$this, 'admin_init'], 10);
723
-                if (method_exists($this, 'admin_init_' . $this->_current_view)) {
724
-                    add_action('admin_init', [$this, 'admin_init_' . $this->_current_view], 15);
725
-                }
726
-            } else {
727
-                // hijack regular WP loading and route admin request immediately
728
-                @ini_set('memory_limit', apply_filters('admin_memory_limit', WP_MAX_MEMORY_LIMIT));
729
-                $this->route_admin_request();
730
-            }
731
-        }
732
-    }
733
-
734
-
735
-    /**
736
-     * Provides a way for related child admin pages to load stuff on the loaded admin page.
737
-     *
738
-     * @return void
739
-     * @throws EE_Error
740
-     */
741
-    private function _do_other_page_hooks()
742
-    {
743
-        $registered_pages = apply_filters('FHEE_do_other_page_hooks_' . $this->page_slug, []);
744
-        foreach ($registered_pages as $page) {
745
-            // now let's setup the file name and class that should be present
746
-            $classname = str_replace('.class.php', '', $page);
747
-            // autoloaders should take care of loading file
748
-            if (! class_exists($classname)) {
749
-                $error_msg[] = sprintf(
750
-                    esc_html__(
751
-                        'Something went wrong with loading the %s admin hooks page.',
752
-                        'event_espresso'
753
-                    ),
754
-                    $page
755
-                );
756
-                $error_msg[] = $error_msg[0]
757
-                               . "\r\n"
758
-                               . sprintf(
759
-                                   esc_html__(
760
-                                       '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',
761
-                                       'event_espresso'
762
-                                   ),
763
-                                   $page,
764
-                                   '<br />',
765
-                                   '<strong>' . $classname . '</strong>'
766
-                               );
767
-                throw new EE_Error(implode('||', $error_msg));
768
-            }
769
-            // notice we are passing the instance of this class to the hook object.
770
-            $this->loader->getShared($classname, [$this]);
771
-        }
772
-    }
773
-
774
-
775
-    /**
776
-     * @throws ReflectionException
777
-     * @throws EE_Error
778
-     */
779
-    public function load_page_dependencies()
780
-    {
781
-        try {
782
-            $this->_load_page_dependencies();
783
-        } catch (EE_Error $e) {
784
-            $e->get_error();
785
-        }
786
-    }
787
-
788
-
789
-    /**
790
-     * load_page_dependencies
791
-     * loads things specific to this page class when its loaded.  Really helps with efficiency.
792
-     *
793
-     * @return void
794
-     * @throws DomainException
795
-     * @throws EE_Error
796
-     * @throws InvalidArgumentException
797
-     * @throws InvalidDataTypeException
798
-     * @throws InvalidInterfaceException
799
-     */
800
-    protected function _load_page_dependencies()
801
-    {
802
-        // let's set the current_screen and screen options to override what WP set
803
-        $this->_current_screen = get_current_screen();
804
-        // load admin_notices - global, page class, and view specific
805
-        add_action('admin_notices', [$this, 'admin_notices_global'], 5);
806
-        add_action('admin_notices', [$this, 'admin_notices'], 10);
807
-        if (method_exists($this, 'admin_notices_' . $this->_current_view)) {
808
-            add_action('admin_notices', [$this, 'admin_notices_' . $this->_current_view], 15);
809
-        }
810
-        // load network admin_notices - global, page class, and view specific
811
-        add_action('network_admin_notices', [$this, 'network_admin_notices_global'], 5);
812
-        if (method_exists($this, 'network_admin_notices_' . $this->_current_view)) {
813
-            add_action('network_admin_notices', [$this, 'network_admin_notices_' . $this->_current_view]);
814
-        }
815
-        // this will save any per_page screen options if they are present
816
-        $this->_set_per_page_screen_options();
817
-        // setup list table properties
818
-        $this->_set_list_table();
819
-        // child classes can "register" a metabox to be automatically handled via the _page_config array property.
820
-        // However in some cases the metaboxes will need to be added within a route handling callback.
821
-        $this->_add_registered_meta_boxes();
822
-        $this->_add_screen_columns();
823
-        // add screen options - global, page child class, and view specific
824
-        $this->_add_global_screen_options();
825
-        $this->_add_screen_options();
826
-        $add_screen_options = "_add_screen_options_{$this->_current_view}";
827
-        if (method_exists($this, $add_screen_options)) {
828
-            $this->{$add_screen_options}();
829
-        }
830
-        // add help tab(s) - set via page_config and qtips.
831
-        $this->_add_help_tabs();
832
-        $this->_add_qtips();
833
-        // add feature_pointers - global, page child class, and view specific
834
-        $this->_add_feature_pointers();
835
-        $this->_add_global_feature_pointers();
836
-        $add_feature_pointer = "_add_feature_pointer_{$this->_current_view}";
837
-        if (method_exists($this, $add_feature_pointer)) {
838
-            $this->{$add_feature_pointer}();
839
-        }
840
-        // enqueue scripts/styles - global, page class, and view specific
841
-        add_action('admin_enqueue_scripts', [$this, 'load_global_scripts_styles'], 5);
842
-        add_action('admin_enqueue_scripts', [$this, 'load_scripts_styles'], 10);
843
-        if (method_exists($this, "load_scripts_styles_{$this->_current_view}")) {
844
-            add_action('admin_enqueue_scripts', [$this, "load_scripts_styles_{$this->_current_view}"], 15);
845
-        }
846
-        add_action('admin_enqueue_scripts', [$this, 'admin_footer_scripts_eei18n_js_strings'], 100);
847
-        // admin_print_footer_scripts - global, page child class, and view specific.
848
-        // NOTE, despite the name, whenever possible, scripts should NOT be loaded using this.
849
-        // In most cases that's doing_it_wrong().  But adding hidden container elements etc.
850
-        // is a good use case. Notice the late priority we're giving these
851
-        add_action('admin_print_footer_scripts', [$this, 'admin_footer_scripts_global'], 99);
852
-        add_action('admin_print_footer_scripts', [$this, 'admin_footer_scripts'], 100);
853
-        if (method_exists($this, "admin_footer_scripts_{$this->_current_view}")) {
854
-            add_action('admin_print_footer_scripts', [$this, "admin_footer_scripts_{$this->_current_view}"], 101);
855
-        }
856
-        // admin footer scripts
857
-        add_action('admin_footer', [$this, 'admin_footer_global'], 99);
858
-        add_action('admin_footer', [$this, 'admin_footer'], 100);
859
-        if (method_exists($this, "admin_footer_{$this->_current_view}")) {
860
-            add_action('admin_footer', [$this, "admin_footer_{$this->_current_view}"], 101);
861
-        }
862
-        do_action('FHEE__EE_Admin_Page___load_page_dependencies__after_load', $this->page_slug);
863
-        // targeted hook
864
-        do_action(
865
-            "FHEE__EE_Admin_Page___load_page_dependencies__after_load__{$this->page_slug}__{$this->_req_action}"
866
-        );
867
-    }
868
-
869
-
870
-    /**
871
-     * _set_defaults
872
-     * This sets some global defaults for class properties.
873
-     */
874
-    private function _set_defaults()
875
-    {
876
-        $this->_current_screen       = $this->_admin_page_title = $this->_req_action = $this->_req_nonce = null;
877
-        $this->_event                = $this->_template_path = $this->_column_template_path = null;
878
-        $this->_nav_tabs             = $this->_views = $this->_page_routes = [];
879
-        $this->_page_config          = $this->_default_route_query_args = [];
880
-        $this->_default_nav_tab_name = 'overview';
881
-        // init template args
882
-        $this->_template_args = [
883
-            'admin_page_header'  => '',
884
-            'admin_page_content' => '',
885
-            'post_body_content'  => '',
886
-            'before_list_table'  => '',
887
-            'after_list_table'   => '',
888
-        ];
889
-    }
890
-
891
-
892
-    /**
893
-     * route_admin_request
894
-     *
895
-     * @return void
896
-     * @throws InvalidArgumentException
897
-     * @throws InvalidInterfaceException
898
-     * @throws InvalidDataTypeException
899
-     * @throws EE_Error
900
-     * @throws ReflectionException
901
-     * @see    _route_admin_request()
902
-     */
903
-    public function route_admin_request()
904
-    {
905
-        try {
906
-            $this->_route_admin_request();
907
-        } catch (EE_Error $e) {
908
-            $e->get_error();
909
-        }
910
-    }
911
-
912
-
913
-    public function set_wp_page_slug($wp_page_slug)
914
-    {
915
-        $this->_wp_page_slug = $wp_page_slug;
916
-        // if in network admin then we need to append "-network" to the page slug. Why? Because that's how WP rolls...
917
-        if (is_network_admin()) {
918
-            $this->_wp_page_slug .= '-network';
919
-        }
920
-    }
921
-
922
-
923
-    /**
924
-     * _verify_routes
925
-     * All this method does is verify the incoming request and make sure that routes exist for it.  We do this early so
926
-     * we know if we need to drop out.
927
-     *
928
-     * @return bool
929
-     * @throws EE_Error
930
-     */
931
-    protected function _verify_routes()
932
-    {
933
-        do_action('AHEE_log', __FILE__, __FUNCTION__, '');
934
-        if (! $this->_current_page && ! $this->request->isAjax()) {
935
-            return false;
936
-        }
937
-        $this->_route = false;
938
-        // check that the page_routes array is not empty
939
-        if (empty($this->_page_routes)) {
940
-            // user error msg
941
-            $error_msg = sprintf(
942
-                esc_html__('No page routes have been set for the %s admin page.', 'event_espresso'),
943
-                $this->_admin_page_title
944
-            );
945
-            // developer error msg
946
-            $error_msg .= '||' . $error_msg
947
-                          . esc_html__(
948
-                              ' Make sure the "set_page_routes()" method exists, and is setting the "_page_routes" array properly.',
949
-                              'event_espresso'
950
-                          );
951
-            throw new EE_Error($error_msg);
952
-        }
953
-        // and that the requested page route exists
954
-        if (array_key_exists($this->_req_action, $this->_page_routes)) {
955
-            $this->_route        = $this->_page_routes[ $this->_req_action ];
956
-            $this->_route_config = $this->_page_config[ $this->_req_action ] ?? [];
957
-        } else {
958
-            // user error msg
959
-            $error_msg = sprintf(
960
-                esc_html__(
961
-                    'The requested page route does not exist for the %s admin page.',
962
-                    'event_espresso'
963
-                ),
964
-                $this->_admin_page_title
965
-            );
966
-            // developer error msg
967
-            $error_msg .= '||' . $error_msg
968
-                          . sprintf(
969
-                              esc_html__(
970
-                                  ' Create a key in the "_page_routes" array named "%s" and set its value to the appropriate method.',
971
-                                  'event_espresso'
972
-                              ),
973
-                              $this->_req_action
974
-                          );
975
-            throw new EE_Error($error_msg);
976
-        }
977
-        // and that a default route exists
978
-        if (! array_key_exists('default', $this->_page_routes)) {
979
-            // user error msg
980
-            $error_msg = sprintf(
981
-                esc_html__(
982
-                    'A default page route has not been set for the % admin page.',
983
-                    'event_espresso'
984
-                ),
985
-                $this->_admin_page_title
986
-            );
987
-            // developer error msg
988
-            $error_msg .= '||' . $error_msg
989
-                          . esc_html__(
990
-                              ' Create a key in the "_page_routes" array named "default" and set its value to your default page method.',
991
-                              'event_espresso'
992
-                          );
993
-            throw new EE_Error($error_msg);
994
-        }
995
-
996
-        // first lets' catch if the UI request has EVER been set.
997
-        if ($this->_is_UI_request === null) {
998
-            // lets set if this is a UI request or not.
999
-            $this->_is_UI_request = ! $this->request->getRequestParam('noheader', false, 'bool');
1000
-            // wait a minute... we might have a noheader in the route array
1001
-            $this->_is_UI_request = ! (
1002
-                is_array($this->_route) && isset($this->_route['noheader']) && $this->_route['noheader']
1003
-            )
1004
-                ? $this->_is_UI_request
1005
-                : false;
1006
-        }
1007
-        $this->_set_current_labels();
1008
-        return true;
1009
-    }
1010
-
1011
-
1012
-    /**
1013
-     * this method simply verifies a given route and makes sure its an actual route available for the loaded page
1014
-     *
1015
-     * @param string $route the route name we're verifying
1016
-     * @return bool we'll throw an exception if this isn't a valid route.
1017
-     * @throws EE_Error
1018
-     */
1019
-    protected function _verify_route($route)
1020
-    {
1021
-        if (array_key_exists($this->_req_action, $this->_page_routes)) {
1022
-            return true;
1023
-        }
1024
-        // user error msg
1025
-        $error_msg = sprintf(
1026
-            esc_html__('The given page route does not exist for the %s admin page.', 'event_espresso'),
1027
-            $this->_admin_page_title
1028
-        );
1029
-        // developer error msg
1030
-        $error_msg .= '||' . $error_msg
1031
-                      . sprintf(
1032
-                          esc_html__(
1033
-                              ' Check the route you are using in your method (%s) and make sure it matches a route set in your "_page_routes" array property',
1034
-                              'event_espresso'
1035
-                          ),
1036
-                          $route
1037
-                      );
1038
-        throw new EE_Error($error_msg);
1039
-    }
1040
-
1041
-
1042
-    /**
1043
-     * perform nonce verification
1044
-     * This method has be encapsulated here so that any ajax requests that bypass normal routes can verify their nonces
1045
-     * using this method (and save retyping!)
1046
-     *
1047
-     * @param string $nonce     The nonce sent
1048
-     * @param string $nonce_ref The nonce reference string (name0)
1049
-     * @return void
1050
-     * @throws EE_Error
1051
-     * @throws InvalidArgumentException
1052
-     * @throws InvalidDataTypeException
1053
-     * @throws InvalidInterfaceException
1054
-     */
1055
-    protected function _verify_nonce($nonce, $nonce_ref)
1056
-    {
1057
-        // verify nonce against expected value
1058
-        if (! wp_verify_nonce($nonce, $nonce_ref)) {
1059
-            // these are not the droids you are looking for !!!
1060
-            $msg = sprintf(
1061
-                esc_html__('%sNonce Fail.%s', 'event_espresso'),
1062
-                '<a href="https://www.youtube.com/watch?v=56_S0WeTkzs">',
1063
-                '</a>'
1064
-            );
1065
-            if (WP_DEBUG) {
1066
-                $msg .= "\n  ";
1067
-                $msg .= sprintf(
1068
-                    esc_html__(
1069
-                        'In order to dynamically generate nonces for your actions, use the %s::add_query_args_and_nonce() method. May the Nonce be with you!',
1070
-                        'event_espresso'
1071
-                    ),
1072
-                    __CLASS__
1073
-                );
1074
-            }
1075
-            if (! $this->request->isAjax()) {
1076
-                wp_die($msg);
1077
-            }
1078
-            EE_Error::add_error($msg, __FILE__, __FUNCTION__, __LINE__);
1079
-            $this->_return_json();
1080
-        }
1081
-    }
1082
-
1083
-
1084
-    /**
1085
-     * _route_admin_request()
1086
-     * Meat and potatoes of the class.  Basically, this dude checks out what's being requested and sees if there are
1087
-     * some doodads to work the magic and handle the flingjangy. Translation:  Checks if the requested action is listed
1088
-     * in the page routes and then will try to load the corresponding method.
1089
-     *
1090
-     * @return void
1091
-     * @throws EE_Error
1092
-     * @throws InvalidArgumentException
1093
-     * @throws InvalidDataTypeException
1094
-     * @throws InvalidInterfaceException
1095
-     * @throws ReflectionException
1096
-     */
1097
-    protected function _route_admin_request()
1098
-    {
1099
-        if (! $this->_is_UI_request) {
1100
-            $this->_verify_routes();
1101
-        }
1102
-        $nonce_check = ! isset($this->_route_config['require_nonce']) || $this->_route_config['require_nonce'];
1103
-        if ($this->_req_action !== 'default' && $nonce_check) {
1104
-            // set nonce from post data
1105
-            $nonce = $this->request->getRequestParam($this->_req_nonce, '');
1106
-            $this->_verify_nonce($nonce, $this->_req_nonce);
1107
-        }
1108
-        // set the nav_tabs array but ONLY if this is  UI_request
1109
-        if ($this->_is_UI_request) {
1110
-            $this->_set_nav_tabs();
1111
-        }
1112
-        // grab callback function
1113
-        $func = is_array($this->_route) && isset($this->_route['func']) ? $this->_route['func'] : $this->_route;
1114
-        // check if callback has args
1115
-        $args      = is_array($this->_route) && isset($this->_route['args']) ? $this->_route['args'] : [];
1116
-        $error_msg = '';
1117
-        // action right before calling route
1118
-        // (hook is something like 'AHEE__Registrations_Admin_Page__route_admin_request')
1119
-        if (! did_action('AHEE__EE_Admin_Page__route_admin_request')) {
1120
-            do_action('AHEE__EE_Admin_Page__route_admin_request', $this->_current_view, $this);
1121
-        }
1122
-        // strip _wp_http_referer from the server REQUEST_URI
1123
-        // else it grows in length on every submission due to recursion,
1124
-        // ultimately causing a "Request-URI Too Large" error
1125
-        $this->request->unSetRequestParam('_wp_http_referer');
1126
-        $this->request->unSetServerParam('_wp_http_referer');
1127
-        $cleaner_request_uri = remove_query_arg(
1128
-            '_wp_http_referer',
1129
-            wp_unslash($this->request->getServerParam('REQUEST_URI'))
1130
-        );
1131
-        $this->request->setRequestParam('_wp_http_referer', $cleaner_request_uri, true);
1132
-        $this->request->setServerParam('REQUEST_URI', $cleaner_request_uri, true);
1133
-        if (! empty($func)) {
1134
-            if (is_array($func)) {
1135
-                [$class, $method] = $func;
1136
-            } elseif (strpos($func, '::') !== false) {
1137
-                [$class, $method] = explode('::', $func);
1138
-            } else {
1139
-                $class  = $this;
1140
-                $method = $func;
1141
-            }
1142
-            // is it neither a class method NOR a standalone function?
1143
-            if (! method_exists($class, $method) && ! function_exists($method)) {
1144
-                // user error msg
1145
-                $error_msg = esc_html__(
1146
-                    'An error occurred. The  requested page route could not be found.',
1147
-                    'event_espresso'
1148
-                );
1149
-                // developer error msg
1150
-                $error_msg .= '||';
1151
-                $error_msg .= sprintf(
1152
-                    esc_html__(
1153
-                        'Page route "%s" could not be called. Check that the spelling for method names and actions in the "_page_routes" array are all correct.',
1154
-                        'event_espresso'
1155
-                    ),
1156
-                    $method
1157
-                );
1158
-                throw new EE_Error($error_msg);
1159
-            }
1160
-            if ($class !== $this && ! in_array($this, $args)) {
1161
-                $args = array_merge(['admin_page' => $this], $args);
1162
-            }
1163
-            try {
1164
-                $success = method_exists($class, $method) && call_user_func_array([$class, $method], $args);
1165
-                if (! $success && function_exists($method)) {
1166
-                    call_user_func_array($method, $args);
1167
-                }
1168
-            } catch (Throwable $throwable) {
1169
-                $class_name = is_object($class) ? get_class($class) : $class;
1170
-                new ExceptionStackTraceDisplay(
1171
-                    new RuntimeException(
1172
-                        sprintf(
1173
-                            esc_html__(
1174
-                                'The following error occurred while trying to route the %1$s admin request: %2$s',
1175
-                                'event_espresso'
1176
-                            ),
1177
-                            "$class_name::$method()",
1178
-                            $throwable->getMessage()
1179
-                        )
1180
-                    )
1181
-                );
1182
-            }
1183
-        }
1184
-        // if we've routed and this route has a no headers route AND a sent_headers_route,
1185
-        // then we need to reset the routing properties to the new route.
1186
-        // 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.
1187
-        if (
1188
-            $this->_is_UI_request === false
1189
-            && is_array($this->_route)
1190
-            && ! empty($this->_route['headers_sent_route'])
1191
-        ) {
1192
-            $this->_reset_routing_properties($this->_route['headers_sent_route']);
1193
-        }
1194
-    }
1195
-
1196
-
1197
-    /**
1198
-     * This method just allows the resetting of page properties in the case where a no headers
1199
-     * route redirects to a headers route in its route config.
1200
-     *
1201
-     * @param string $new_route New (non header) route to redirect to.
1202
-     * @return   void
1203
-     * @throws ReflectionException
1204
-     * @throws InvalidArgumentException
1205
-     * @throws InvalidInterfaceException
1206
-     * @throws InvalidDataTypeException
1207
-     * @throws EE_Error
1208
-     * @since   4.3.0
1209
-     */
1210
-    protected function _reset_routing_properties($new_route)
1211
-    {
1212
-        $this->_is_UI_request = true;
1213
-        // now we set the current route to whatever the headers_sent_route is set at
1214
-        $this->request->setRequestParam('action', $new_route);
1215
-        // rerun page setup
1216
-        $this->_page_setup();
1217
-    }
1218
-
1219
-
1220
-    /**
1221
-     * _add_query_arg
1222
-     * adds nonce to array of arguments then calls WP add_query_arg function
1223
-     *(internally just uses EEH_URL's function with the same name)
1224
-     *
1225
-     * @param array  $args
1226
-     * @param string $url
1227
-     * @param bool   $sticky                  if true, then the existing Request params will be appended to the
1228
-     *                                        generated url in an associative array indexed by the key 'wp_referer';
1229
-     *                                        Example usage: If the current page is:
1230
-     *                                        http://mydomain.com/wp-admin/admin.php?page=espresso_registrations
1231
-     *                                        &action=default&event_id=20&month_range=March%202015
1232
-     *                                        &_wpnonce=5467821
1233
-     *                                        and you call:
1234
-     *                                        EE_Admin_Page::add_query_args_and_nonce(
1235
-     *                                        array(
1236
-     *                                        'action' => 'resend_something',
1237
-     *                                        'page=>espresso_registrations'
1238
-     *                                        ),
1239
-     *                                        $some_url,
1240
-     *                                        true
1241
-     *                                        );
1242
-     *                                        It will produce a url in this structure:
1243
-     *                                        http://{$some_url}/?page=espresso_registrations&action=resend_something
1244
-     *                                        &wp_referer[action]=default&wp_referer[event_id]=20&wpreferer[
1245
-     *                                        month_range]=March%202015
1246
-     * @param bool   $exclude_nonce           If true, the the nonce will be excluded from the generated nonce.
1247
-     * @return string
1248
-     */
1249
-    public static function add_query_args_and_nonce(
1250
-        $args = [],
1251
-        $url = '',
1252
-        $sticky = false,
1253
-        $exclude_nonce = false
1254
-    ) {
1255
-        // if there is a _wp_http_referer include the values from the request but only if sticky = true
1256
-        if ($sticky) {
1257
-            /** @var RequestInterface $request */
1258
-            $request = LoaderFactory::getLoader()->getShared(RequestInterface::class);
1259
-            $request->unSetRequestParams(['_wp_http_referer', 'wp_referer'], true);
1260
-            $request->unSetServerParam('_wp_http_referer', true);
1261
-            foreach ($request->requestParams() as $key => $value) {
1262
-                // do not add nonces
1263
-                if (strpos($key, 'nonce') !== false) {
1264
-                    continue;
1265
-                }
1266
-                $args[ 'wp_referer[' . $key . ']' ] = is_string($value) ? htmlspecialchars($value) : $value;
1267
-            }
1268
-        }
1269
-        return EEH_URL::add_query_args_and_nonce($args, $url, $exclude_nonce);
1270
-    }
1271
-
1272
-
1273
-    /**
1274
-     * This returns a generated link that will load the related help tab.
1275
-     *
1276
-     * @param string $help_tab_id the id for the connected help tab
1277
-     * @param string $icon_style  (optional) include css class for the style you want to use for the help icon.
1278
-     * @param string $help_text   (optional) send help text you want to use for the link if default not to be used
1279
-     * @return string              generated link
1280
-     * @uses EEH_Template::get_help_tab_link()
1281
-     */
1282
-    protected function _get_help_tab_link($help_tab_id, $icon_style = '', $help_text = '')
1283
-    {
1284
-        return EEH_Template::get_help_tab_link(
1285
-            $help_tab_id,
1286
-            $this->page_slug,
1287
-            $this->_req_action,
1288
-            $icon_style,
1289
-            $help_text
1290
-        );
1291
-    }
1292
-
1293
-
1294
-    /**
1295
-     * _add_help_tabs
1296
-     * Note child classes define their help tabs within the page_config array.
1297
-     *
1298
-     * @link   http://codex.wordpress.org/Function_Reference/add_help_tab
1299
-     * @return void
1300
-     * @throws DomainException
1301
-     * @throws EE_Error
1302
-     * @throws ReflectionException
1303
-     */
1304
-    protected function _add_help_tabs()
1305
-    {
1306
-        if (isset($this->_page_config[ $this->_req_action ])) {
1307
-            $config = $this->_page_config[ $this->_req_action ];
1308
-            // let's see if there is a help_sidebar set for the current route and we'll set that up for usage as well.
1309
-            if (is_array($config) && isset($config['help_sidebar'])) {
1310
-                // check that the callback given is valid
1311
-                if (! method_exists($this, $config['help_sidebar'])) {
1312
-                    throw new EE_Error(
1313
-                        sprintf(
1314
-                            esc_html__(
1315
-                                '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',
1316
-                                'event_espresso'
1317
-                            ),
1318
-                            $config['help_sidebar'],
1319
-                            $this->class_name
1320
-                        )
1321
-                    );
1322
-                }
1323
-                $content = apply_filters(
1324
-                    'FHEE__' . $this->class_name . '__add_help_tabs__help_sidebar',
1325
-                    $this->{$config['help_sidebar']}()
1326
-                );
1327
-                $this->_current_screen->set_help_sidebar($content);
1328
-            }
1329
-            if (! isset($config['help_tabs'])) {
1330
-                return;
1331
-            } //no help tabs for this route
1332
-            foreach ((array) $config['help_tabs'] as $tab_id => $cfg) {
1333
-                // we're here so there ARE help tabs!
1334
-                // make sure we've got what we need
1335
-                if (! isset($cfg['title'])) {
1336
-                    throw new EE_Error(
1337
-                        esc_html__(
1338
-                            'The _page_config array is not set up properly for help tabs.  It is missing a title',
1339
-                            'event_espresso'
1340
-                        )
1341
-                    );
1342
-                }
1343
-                if (! isset($cfg['filename']) && ! isset($cfg['callback']) && ! isset($cfg['content'])) {
1344
-                    throw new EE_Error(
1345
-                        esc_html__(
1346
-                            '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',
1347
-                            'event_espresso'
1348
-                        )
1349
-                    );
1350
-                }
1351
-                // first priority goes to content.
1352
-                if (! empty($cfg['content'])) {
1353
-                    $content = ! empty($cfg['content']) ? $cfg['content'] : null;
1354
-                    // second priority goes to filename
1355
-                } elseif (! empty($cfg['filename'])) {
1356
-                    $file_path = $this->_get_dir() . '/help_tabs/' . $cfg['filename'] . '.help_tab.php';
1357
-                    // 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)
1358
-                    $file_path = ! is_readable($file_path) ? EE_ADMIN_PAGES
1359
-                                                             . basename($this->_get_dir())
1360
-                                                             . '/help_tabs/'
1361
-                                                             . $cfg['filename']
1362
-                                                             . '.help_tab.php' : $file_path;
1363
-                    // if file is STILL not readable then let's do a EE_Error so its more graceful than a fatal error.
1364
-                    if (! isset($cfg['callback']) && ! is_readable($file_path)) {
1365
-                        EE_Error::add_error(
1366
-                            sprintf(
1367
-                                esc_html__(
1368
-                                    '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',
1369
-                                    'event_espresso'
1370
-                                ),
1371
-                                $tab_id,
1372
-                                key($config),
1373
-                                $file_path
1374
-                            ),
1375
-                            __FILE__,
1376
-                            __FUNCTION__,
1377
-                            __LINE__
1378
-                        );
1379
-                        return;
1380
-                    }
1381
-                    $template_args['admin_page_obj'] = $this;
1382
-                    $content                         = EEH_Template::display_template(
1383
-                        $file_path,
1384
-                        $template_args,
1385
-                        true
1386
-                    );
1387
-                } else {
1388
-                    $content = '';
1389
-                }
1390
-                // check if callback is valid
1391
-                if (
1392
-                    empty($content)
1393
-                    && (
1394
-                        ! isset($cfg['callback']) || ! method_exists($this, $cfg['callback'])
1395
-                    )
1396
-                ) {
1397
-                    EE_Error::add_error(
1398
-                        sprintf(
1399
-                            esc_html__(
1400
-                                '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.',
1401
-                                'event_espresso'
1402
-                            ),
1403
-                            $cfg['title']
1404
-                        ),
1405
-                        __FILE__,
1406
-                        __FUNCTION__,
1407
-                        __LINE__
1408
-                    );
1409
-                    return;
1410
-                }
1411
-                // setup config array for help tab method
1412
-                $id  = $this->page_slug . '-' . $this->_req_action . '-' . $tab_id;
1413
-                $_ht = [
1414
-                    'id'       => $id,
1415
-                    'title'    => $cfg['title'],
1416
-                    'callback' => isset($cfg['callback']) && empty($content) ? [$this, $cfg['callback']] : null,
1417
-                    'content'  => $content,
1418
-                ];
1419
-                $this->_current_screen->add_help_tab($_ht);
1420
-            }
1421
-        }
1422
-    }
1423
-
1424
-
1425
-    /**
1426
-     * This simply sets up any qtips that have been defined in the page config
1427
-     *
1428
-     * @return void
1429
-     * @throws ReflectionException
1430
-     * @throws EE_Error
1431
-     */
1432
-    protected function _add_qtips()
1433
-    {
1434
-        if (isset($this->_route_config['qtips'])) {
1435
-            $qtips = (array) $this->_route_config['qtips'];
1436
-            // load qtip loader
1437
-            $path = [
1438
-                $this->_get_dir() . '/qtips/',
1439
-                EE_ADMIN_PAGES . basename($this->_get_dir()) . '/qtips/',
1440
-            ];
1441
-            EEH_Qtip_Loader::instance()->register($qtips, $path);
1442
-        }
1443
-    }
1444
-
1445
-
1446
-    /**
1447
-     * _set_nav_tabs
1448
-     * This sets up the nav tabs from the page_routes array.  This method can be overwritten by child classes if you
1449
-     * wish to add additional tabs or modify accordingly.
1450
-     *
1451
-     * @return void
1452
-     * @throws InvalidArgumentException
1453
-     * @throws InvalidInterfaceException
1454
-     * @throws InvalidDataTypeException
1455
-     */
1456
-    protected function _set_nav_tabs()
1457
-    {
1458
-        $i = 0;
1459
-        $only_tab = count($this->_page_config) < 2;
1460
-        foreach ($this->_page_config as $slug => $config) {
1461
-            if (! is_array($config) || empty($config['nav'])) {
1462
-                continue;
1463
-            }
1464
-            // no nav tab for this config
1465
-            // check for persistent flag
1466
-            if ($slug !== $this->_req_action && isset($config['nav']['persistent']) && ! $config['nav']['persistent']) {
1467
-                // nav tab is only to appear when route requested.
1468
-                continue;
1469
-            }
1470
-            if (! $this->check_user_access($slug, true)) {
1471
-                // no nav tab because current user does not have access.
1472
-                continue;
1473
-            }
1474
-            $css_class = isset($config['css_class']) ? $config['css_class'] . ' ' : '';
1475
-            $css_class .= $only_tab ? ' ee-only-tab' : '';
1476
-
1477
-            $this->_nav_tabs[ $slug ] = [
1478
-                'url'       => $config['nav']['url'] ?? EE_Admin_Page::add_query_args_and_nonce(
1479
-                    ['action' => $slug],
1480
-                    $this->_admin_base_url
1481
-                ),
1482
-                'link_text' => $this->navTabLabel($config['nav'], $slug),
1483
-                'css_class' => $this->_req_action === $slug ? $css_class . ' nav-tab-active' : $css_class,
1484
-                'order'     => $config['nav']['order'] ?? $i,
1485
-            ];
1486
-            $i++;
1487
-        }
1488
-        // if $this->_nav_tabs is empty then lets set the default
1489
-        if (empty($this->_nav_tabs)) {
1490
-            $this->_nav_tabs[ $this->_default_nav_tab_name ] = [
1491
-                'url'       => $this->_admin_base_url,
1492
-                'link_text' => ucwords(str_replace('_', ' ', $this->_default_nav_tab_name)),
1493
-                'css_class' => 'nav-tab-active',
1494
-                'order'     => 10,
1495
-            ];
1496
-        }
1497
-        // now let's sort the tabs according to order
1498
-        usort($this->_nav_tabs, [$this, '_sort_nav_tabs']);
1499
-    }
1500
-
1501
-
1502
-    private function navTabLabel(array $nav_tab, string $slug): string
1503
-    {
1504
-        $label = $nav_tab['label'] ?? ucwords(str_replace('_', ' ', $slug));
1505
-        $icon = $nav_tab['icon'] ?? null;
1506
-        $icon = $icon ? '<span class="dashicons ' . $icon . '"></span>' : '';
1507
-        return '
143
+	/**
144
+	 * unprocessed value for the 'page' request param (default '')
145
+	 *
146
+	 * @var string
147
+	 */
148
+	protected $raw_req_page = '';
149
+
150
+	/**
151
+	 * sanitized request action (and nonce)
152
+	 *
153
+	 * @var string
154
+	 */
155
+	protected $_req_action = '';
156
+
157
+	/**
158
+	 * sanitized request action nonce
159
+	 *
160
+	 * @var string
161
+	 */
162
+	protected $_req_nonce = '';
163
+
164
+	/**
165
+	 * @var string
166
+	 */
167
+	protected $_search_btn_label = '';
168
+
169
+	/**
170
+	 * @var string
171
+	 */
172
+	protected $_search_box_callback = '';
173
+
174
+	/**
175
+	 * @var WP_Screen
176
+	 */
177
+	protected $_current_screen;
178
+
179
+	// for holding EE_Admin_Hooks object when needed (set via set_hook_object())
180
+	protected $_hook_obj;
181
+
182
+	// for holding incoming request data
183
+	protected $_req_data = [];
184
+
185
+	// yes / no array for admin form fields
186
+	protected $_yes_no_values = [];
187
+
188
+	// some default things shared by all child classes
189
+	protected $_default_espresso_metaboxes = [
190
+		'_espresso_news_post_box',
191
+		'_espresso_links_post_box',
192
+		'_espresso_ratings_request',
193
+		'_espresso_sponsors_post_box',
194
+	];
195
+
196
+	/**
197
+	 * @var EE_Registry
198
+	 */
199
+	protected $EE;
200
+
201
+
202
+	/**
203
+	 * This is just a property that flags whether the given route is a caffeinated route or not.
204
+	 *
205
+	 * @var boolean
206
+	 */
207
+	protected $_is_caf = false;
208
+
209
+	/**
210
+	 * whether or not initializePage() has run
211
+	 *
212
+	 * @var boolean
213
+	 */
214
+	protected $initialized = false;
215
+
216
+	/**
217
+	 * @var FeatureFlags
218
+	 */
219
+	protected $feature;
220
+
221
+
222
+	/**
223
+	 * @var string
224
+	 */
225
+	protected $class_name;
226
+
227
+	/**
228
+	 * if the current class is an admin page extension, like: Extend_Events_Admin_Page,
229
+	 * then this would be the parent classname: Events_Admin_Page
230
+	 *
231
+	 * @var string
232
+	 */
233
+	protected $base_class_name;
234
+
235
+
236
+	/**
237
+	 * @Constructor
238
+	 * @param bool $routing indicate whether we want to just load the object and handle routing or just load the object.
239
+	 * @throws InvalidArgumentException
240
+	 * @throws InvalidDataTypeException
241
+	 * @throws InvalidInterfaceException
242
+	 * @throws ReflectionException
243
+	 */
244
+	public function __construct($routing = true)
245
+	{
246
+		$this->loader = LoaderFactory::getLoader();
247
+		$this->admin_config = $this->loader->getShared('EE_Admin_Config');
248
+		$this->feature = $this->loader->getShared(FeatureFlags::class);
249
+		$this->request = $this->loader->getShared(RequestInterface::class);
250
+		// routing enabled?
251
+		$this->_routing = $routing;
252
+
253
+		$this->class_name = get_class($this);
254
+		$this->base_class_name = strpos($this->class_name, 'Extend_') === 0
255
+			? str_replace('Extend_', '', $this->class_name)
256
+			: '';
257
+
258
+		if (strpos($this->_get_dir(), 'caffeinated') !== false) {
259
+			$this->_is_caf = true;
260
+		}
261
+		$this->_yes_no_values = [
262
+			['id' => true, 'text' => esc_html__('Yes', 'event_espresso')],
263
+			['id' => false, 'text' => esc_html__('No', 'event_espresso')],
264
+		];
265
+		// set the _req_data property.
266
+		$this->_req_data = $this->request->requestParams();
267
+	}
268
+
269
+
270
+	/**
271
+	 * @return EE_Admin_Config
272
+	 */
273
+	public function adminConfig(): EE_Admin_Config
274
+	{
275
+		return $this->admin_config;
276
+	}
277
+
278
+
279
+	/**
280
+	 * @return FeatureFlags
281
+	 */
282
+	public function feature(): FeatureFlags
283
+	{
284
+		return $this->feature;
285
+	}
286
+
287
+
288
+	/**
289
+	 * This logic used to be in the constructor, but that caused a chicken <--> egg scenario
290
+	 * for child classes that needed to set properties prior to these methods getting called,
291
+	 * but also needed the parent class to have its construction completed as well.
292
+	 * Bottom line is that constructors should ONLY be used for setting initial properties
293
+	 * and any complex initialization logic should only run after instantiation is complete.
294
+	 *
295
+	 * This method gets called immediately after construction from within
296
+	 *      EE_Admin_Page_Init::_initialize_admin_page()
297
+	 *
298
+	 * @throws EE_Error
299
+	 * @throws InvalidArgumentException
300
+	 * @throws InvalidDataTypeException
301
+	 * @throws InvalidInterfaceException
302
+	 * @throws ReflectionException
303
+	 * @since $VID:$
304
+	 */
305
+	public function initializePage()
306
+	{
307
+		if ($this->initialized) {
308
+			return;
309
+		}
310
+		// set initial page props (child method)
311
+		$this->_init_page_props();
312
+		// set global defaults
313
+		$this->_set_defaults();
314
+		// set early because incoming requests could be ajax related and we need to register those hooks.
315
+		$this->_global_ajax_hooks();
316
+		$this->_ajax_hooks();
317
+		// other_page_hooks have to be early too.
318
+		$this->_do_other_page_hooks();
319
+		// set up page dependencies
320
+		$this->_before_page_setup();
321
+		$this->_page_setup();
322
+		$this->initialized = true;
323
+	}
324
+
325
+
326
+	/**
327
+	 * _init_page_props
328
+	 * Child classes use to set at least the following properties:
329
+	 * $page_slug.
330
+	 * $page_label.
331
+	 *
332
+	 * @abstract
333
+	 * @return void
334
+	 */
335
+	abstract protected function _init_page_props();
336
+
337
+
338
+	/**
339
+	 * _ajax_hooks
340
+	 * child classes put all their add_action('wp_ajax_{name_of_hook}') hooks in here.
341
+	 * Note: within the ajax callback methods.
342
+	 *
343
+	 * @abstract
344
+	 * @return void
345
+	 */
346
+	abstract protected function _ajax_hooks();
347
+
348
+
349
+	/**
350
+	 * _define_page_props
351
+	 * child classes define page properties in here.  Must include at least:
352
+	 * $_admin_base_url = base_url for all admin pages
353
+	 * $_admin_page_title = default admin_page_title for admin pages
354
+	 * $_labels = array of default labels for various automatically generated elements:
355
+	 *    array(
356
+	 *        'buttons' => array(
357
+	 *            'add' => esc_html__('label for add new button'),
358
+	 *            'edit' => esc_html__('label for edit button'),
359
+	 *            'delete' => esc_html__('label for delete button')
360
+	 *            )
361
+	 *        )
362
+	 *
363
+	 * @abstract
364
+	 * @return void
365
+	 */
366
+	abstract protected function _define_page_props();
367
+
368
+
369
+	/**
370
+	 * _set_page_routes
371
+	 * child classes use this to define the page routes for all subpages handled by the class.  Page routes are
372
+	 * assigned to a action => method pairs in an array and to the $_page_routes property.  Each page route must also
373
+	 * have a 'default' route. Here's the format
374
+	 * $this->_page_routes = array(
375
+	 *        'default' => array(
376
+	 *            'func' => '_default_method_handling_route',
377
+	 *            'args' => array('array','of','args'),
378
+	 *            'noheader' => true, //add this in if this page route is processed before any headers are loaded (i.e.
379
+	 *            ajax request, backend processing)
380
+	 *            'headers_sent_route'=>'headers_route_reference', //add this if noheader=>true, and you want to load a
381
+	 *            headers route after.  The string you enter here should match the defined route reference for a
382
+	 *            headers sent route.
383
+	 *            'capability' => 'route_capability', //indicate a string for minimum capability required to access
384
+	 *            this route.
385
+	 *            'obj_id' => 10 // if this route has an object id, then this can include it (used for capability
386
+	 *            checks).
387
+	 *        ),
388
+	 *        'insert_item' => '_method_for_handling_insert_item' //this can be used if all we need to have is a
389
+	 *        handling method.
390
+	 *        )
391
+	 * )
392
+	 *
393
+	 * @abstract
394
+	 * @return void
395
+	 */
396
+	abstract protected function _set_page_routes();
397
+
398
+
399
+	/**
400
+	 * _set_page_config
401
+	 * child classes use this to define the _page_config array for all subpages handled by the class. Each key in the
402
+	 * array corresponds to the page_route for the loaded page. Format:
403
+	 * $this->_page_config = array(
404
+	 *        'default' => array(
405
+	 *            'labels' => array(
406
+	 *                'buttons' => array(
407
+	 *                    'add' => esc_html__('label for adding item'),
408
+	 *                    'edit' => esc_html__('label for editing item'),
409
+	 *                    'delete' => esc_html__('label for deleting item')
410
+	 *                ),
411
+	 *                'publishbox' => esc_html__('Localized Title for Publish metabox', 'event_espresso')
412
+	 *            ), //optional an array of custom labels for various automatically generated elements to use on the
413
+	 *            page. If this isn't present then the defaults will be used as set for the $this->_labels in
414
+	 *            _define_page_props() method
415
+	 *            'nav' => array(
416
+	 *                'label' => esc_html__('Label for Tab', 'event_espresso').
417
+	 *                'url' => 'http://someurl', //automatically generated UNLESS you define
418
+	 *                'css_class' => 'css-class', //automatically generated UNLESS you define
419
+	 *                'order' => 10, //required to indicate tab position.
420
+	 *                'persistent' => false //if you want the nav tab to ONLY display when the specific route is
421
+	 *                displayed then add this parameter.
422
+	 *            'list_table' => 'name_of_list_table' //string for list table class to be loaded for this admin_page.
423
+	 *            'metaboxes' => array('metabox1', 'metabox2'), //if present this key indicates we want to load
424
+	 *            metaboxes set for eventespresso admin pages.
425
+	 *            'has_metaboxes' => true, //this boolean flag can simply be used to indicate if the route will have
426
+	 *            metaboxes.  Typically this is used if the 'metaboxes' index is not used because metaboxes are added
427
+	 *            later.  We just use this flag to make sure the necessary js gets enqueued on page load.
428
+	 *            'has_help_popups' => false //defaults(true) //this boolean flag can simply be used to indicate if the
429
+	 *            given route has help popups setup and if it does then we need to make sure thickbox is enqueued.
430
+	 *            'columns' => array(4, 2), //this key triggers the setup of a page that uses columns (metaboxes).  The
431
+	 *            array indicates the max number of columns (4) and the default number of columns on page load (2).
432
+	 *            There is an option in the "screen_options" dropdown that is setup so users can pick what columns they
433
+	 *            want to display.
434
+	 *            'help_tabs' => array( //this is used for adding help tabs to a page
435
+	 *                'tab_id' => array(
436
+	 *                    'title' => 'tab_title',
437
+	 *                    'filename' => 'name_of_file_containing_content', //this is the primary method for setting
438
+	 *                    help tab content.  The fallback if it isn't present is to try a the callback.  Filename
439
+	 *                    should match a file in the admin folder's "help_tabs" dir (ie..
440
+	 *                    events/help_tabs/name_of_file_containing_content.help_tab.php)
441
+	 *                    'callback' => 'callback_method_for_content', //if 'filename' isn't present then system will
442
+	 *                    attempt to use the callback which should match the name of a method in the class
443
+	 *                    ),
444
+	 *                'tab2_id' => array(
445
+	 *                    'title' => 'tab2 title',
446
+	 *                    'filename' => 'file_name_2'
447
+	 *                    'callback' => 'callback_method_for_content',
448
+	 *                 ),
449
+	 *            'help_sidebar' => 'callback_for_sidebar_content', //this is used for setting up the sidebar in the
450
+	 *            help tab area on an admin page. @return void
451
+	 *
452
+	 * @abstract
453
+	 */
454
+	abstract protected function _set_page_config();
455
+
456
+
457
+	/**
458
+	 * _add_screen_options
459
+	 * Child classes can add any extra wp_screen_options within this method using built-in WP functions/methods for
460
+	 * doing so. Note child classes can also define _add_screen_options_($this->_current_view) to limit screen options
461
+	 * to a particular view.
462
+	 *
463
+	 * @link   http://chrismarslender.com/wp-tutorials/wordpress-screen-options-tutorial/
464
+	 *         see also WP_Screen object documents...
465
+	 * @link   http://codex.wordpress.org/Class_Reference/WP_Screen
466
+	 * @abstract
467
+	 * @return void
468
+	 */
469
+	abstract protected function _add_screen_options();
470
+
471
+
472
+	/**
473
+	 * _add_feature_pointers
474
+	 * Child classes should use this method for implementing any "feature pointers" (using built-in WP styling js).
475
+	 * Note child classes can also define _add_feature_pointers_($this->_current_view) to limit screen options to a
476
+	 * particular view. Note: this is just a placeholder for now.  Implementation will come down the road See:
477
+	 * WP_Internal_Pointers class in wp-admin/includes/template.php for example (its a final class so can't be
478
+	 * extended) also see:
479
+	 *
480
+	 * @link   http://eamann.com/tech/wordpress-portland/
481
+	 * @abstract
482
+	 * @return void
483
+	 */
484
+	abstract protected function _add_feature_pointers();
485
+
486
+
487
+	/**
488
+	 * load_scripts_styles
489
+	 * child classes put their wp_enqueue_script and wp_enqueue_style hooks in here for anything they need loaded for
490
+	 * their pages/subpages.  Note this is for all pages/subpages of the system.  You can also load only specific
491
+	 * scripts/styles per view by putting them in a dynamic function in this format
492
+	 * (load_scripts_styles_{$this->_current_view}) which matches your page route (action request arg)
493
+	 *
494
+	 * @abstract
495
+	 * @return void
496
+	 */
497
+	abstract public function load_scripts_styles();
498
+
499
+
500
+	/**
501
+	 * admin_init
502
+	 * Anything that should be set/executed at 'admin_init' WP hook runtime should be put in here.  This will apply to
503
+	 * all pages/views loaded by child class.
504
+	 *
505
+	 * @abstract
506
+	 * @return void
507
+	 */
508
+	abstract public function admin_init();
509
+
510
+
511
+	/**
512
+	 * admin_notices
513
+	 * Anything triggered by the 'admin_notices' WP hook should be put in here.  This particular method will apply to
514
+	 * all pages/views loaded by child class.
515
+	 *
516
+	 * @abstract
517
+	 * @return void
518
+	 */
519
+	abstract public function admin_notices();
520
+
521
+
522
+	/**
523
+	 * admin_footer_scripts
524
+	 * Anything triggered by the 'admin_print_footer_scripts' WP hook should be put in here. This particular method
525
+	 * will apply to all pages/views loaded by child class.
526
+	 *
527
+	 * @return void
528
+	 */
529
+	abstract public function admin_footer_scripts();
530
+
531
+
532
+	/**
533
+	 * admin_footer
534
+	 * anything triggered by the 'admin_footer' WP action hook should be added to here. This particular method will
535
+	 * apply to all pages/views loaded by child class.
536
+	 *
537
+	 * @return void
538
+	 */
539
+	public function admin_footer()
540
+	{
541
+	}
542
+
543
+
544
+	/**
545
+	 * _global_ajax_hooks
546
+	 * all global add_action('wp_ajax_{name_of_hook}') hooks in here.
547
+	 * Note: within the ajax callback methods.
548
+	 *
549
+	 * @abstract
550
+	 * @return void
551
+	 */
552
+	protected function _global_ajax_hooks()
553
+	{
554
+		// for lazy loading of metabox content
555
+		add_action('wp_ajax_espresso-ajax-content', [$this, 'ajax_metabox_content'], 10);
556
+
557
+		add_action(
558
+			'wp_ajax_espresso_hide_status_change_notice',
559
+			[$this, 'hideStatusChangeNotice']
560
+		);
561
+		add_action(
562
+			'wp_ajax_nopriv_espresso_hide_status_change_notice',
563
+			[$this, 'hideStatusChangeNotice']
564
+		);
565
+	}
566
+
567
+
568
+	public function ajax_metabox_content()
569
+	{
570
+		$content_id  = $this->request->getRequestParam('contentid', '');
571
+		$content_url = $this->request->getRequestParam('contenturl', '', 'url');
572
+		EE_Admin_Page::cached_rss_display($content_id, $content_url);
573
+		wp_die();
574
+	}
575
+
576
+
577
+	public function hideStatusChangeNotice()
578
+	{
579
+		$response = [];
580
+		try {
581
+			/** @var StatusChangeNotice $status_change_notice */
582
+			$status_change_notice = $this->loader->getShared(
583
+				'EventEspresso\core\domain\services\admin\notices\status_change\StatusChangeNotice'
584
+			);
585
+			$response['success'] = $status_change_notice->dismiss() > -1;
586
+		} catch (Exception $exception) {
587
+			$response['errors'] = $exception->getMessage();
588
+		}
589
+		echo wp_json_encode($response);
590
+		exit();
591
+	}
592
+
593
+
594
+	/**
595
+	 * allows extending classes do something specific before the parent constructor runs _page_setup().
596
+	 *
597
+	 * @return void
598
+	 */
599
+	protected function _before_page_setup()
600
+	{
601
+		// default is to do nothing
602
+	}
603
+
604
+
605
+	/**
606
+	 * Makes sure any things that need to be loaded early get handled.
607
+	 * We also escape early here if the page requested doesn't match the object.
608
+	 *
609
+	 * @final
610
+	 * @return void
611
+	 * @throws EE_Error
612
+	 * @throws InvalidArgumentException
613
+	 * @throws ReflectionException
614
+	 * @throws InvalidDataTypeException
615
+	 * @throws InvalidInterfaceException
616
+	 */
617
+	final protected function _page_setup()
618
+	{
619
+		// requires?
620
+		// admin_init stuff - global - we're setting this REALLY early
621
+		// so if EE_Admin pages have to hook into other WP pages they can.
622
+		// But keep in mind, not everything is available from the EE_Admin Page object at this point.
623
+		add_action('admin_init', [$this, 'admin_init_global'], 5);
624
+		// next verify if we need to load anything...
625
+		$this->_current_page = $this->request->getRequestParam('page', '', 'key');
626
+		$this->_current_page = $this->request->getRequestParam('current_page', $this->_current_page, 'key');
627
+		$this->page_folder   = strtolower(
628
+			str_replace(['_Admin_Page', 'Extend_'], '', $this->class_name)
629
+		);
630
+		global $ee_menu_slugs;
631
+		$ee_menu_slugs = (array) $ee_menu_slugs;
632
+		if (
633
+			! $this->request->isAjax()
634
+			&& (! $this->_current_page || ! isset($ee_menu_slugs[ $this->_current_page ]))
635
+		) {
636
+			return;
637
+		}
638
+		// because WP List tables have two duplicate select inputs for choosing bulk actions,
639
+		// we need to copy the action from the second to the first
640
+		$action     = $this->request->getRequestParam('action', '-1', 'key');
641
+		$action2    = $this->request->getRequestParam('action2', '-1', 'key');
642
+		$action     = $action !== '-1' ? $action : $action2;
643
+		$req_action = $action !== '-1' ? $action : 'default';
644
+
645
+		// if a specific 'route' has been set, and the action is 'default' OR we are doing_ajax
646
+		// then let's use the route as the action.
647
+		// This covers cases where we're coming in from a list table that isn't on the default route.
648
+		$route = $this->request->getRequestParam('route');
649
+		$this->_req_action = $route && ($req_action === 'default' || $this->request->isAjax())
650
+			? $route
651
+			: $req_action;
652
+
653
+		$this->_current_view = $this->_req_action;
654
+		$this->_req_nonce    = $this->_req_action . '_nonce';
655
+		$this->_define_page_props();
656
+		$this->_current_page_view_url = add_query_arg(
657
+			['page' => $this->_current_page, 'action' => $this->_current_view],
658
+			$this->_admin_base_url
659
+		);
660
+		// set page configs
661
+		$this->_set_page_routes();
662
+		$this->_set_page_config();
663
+		// let's include any referrer data in our default_query_args for this route for "stickiness".
664
+		if ($this->request->requestParamIsSet('wp_referer')) {
665
+			$wp_referer = $this->request->getRequestParam('wp_referer');
666
+			if ($wp_referer) {
667
+				$this->_default_route_query_args['wp_referer'] = $wp_referer;
668
+			}
669
+		}
670
+		// for caffeinated and other extended functionality.
671
+		//  If there is a _extend_page_config method
672
+		// then let's run that to modify the all the various page configuration arrays
673
+		if (method_exists($this, '_extend_page_config')) {
674
+			$this->_extend_page_config();
675
+		}
676
+		// for CPT and other extended functionality.
677
+		// If there is an _extend_page_config_for_cpt
678
+		// then let's run that to modify all the various page configuration arrays.
679
+		if (method_exists($this, '_extend_page_config_for_cpt')) {
680
+			$this->_extend_page_config_for_cpt();
681
+		}
682
+		// filter routes and page_config so addons can add their stuff. Filtering done per class
683
+		$this->_page_routes = apply_filters(
684
+			'FHEE__' . $this->class_name . '__page_setup__page_routes',
685
+			$this->_page_routes,
686
+			$this
687
+		);
688
+		$this->_page_config = apply_filters(
689
+			'FHEE__' . $this->class_name . '__page_setup__page_config',
690
+			$this->_page_config,
691
+			$this
692
+		);
693
+		if ($this->base_class_name !== '') {
694
+			$this->_page_routes = apply_filters(
695
+				'FHEE__' . $this->base_class_name . '__page_setup__page_routes',
696
+				$this->_page_routes,
697
+				$this
698
+			);
699
+			$this->_page_config = apply_filters(
700
+				'FHEE__' . $this->base_class_name . '__page_setup__page_config',
701
+				$this->_page_config,
702
+				$this
703
+			);
704
+		}
705
+		// if AHEE__EE_Admin_Page__route_admin_request_$this->_current_view method is present
706
+		// then we call it hooked into the AHEE__EE_Admin_Page__route_admin_request action
707
+		if (method_exists($this, 'AHEE__EE_Admin_Page__route_admin_request_' . $this->_current_view)) {
708
+			add_action(
709
+				'AHEE__EE_Admin_Page__route_admin_request',
710
+				[$this, 'AHEE__EE_Admin_Page__route_admin_request_' . $this->_current_view],
711
+				10,
712
+				2
713
+			);
714
+		}
715
+		// next route only if routing enabled
716
+		if ($this->_routing && ! $this->request->isAjax()) {
717
+			$this->_verify_routes();
718
+			// next let's just check user_access and kill if no access
719
+			$this->check_user_access();
720
+			if ($this->_is_UI_request) {
721
+				// admin_init stuff - global, all views for this page class, specific view
722
+				add_action('admin_init', [$this, 'admin_init'], 10);
723
+				if (method_exists($this, 'admin_init_' . $this->_current_view)) {
724
+					add_action('admin_init', [$this, 'admin_init_' . $this->_current_view], 15);
725
+				}
726
+			} else {
727
+				// hijack regular WP loading and route admin request immediately
728
+				@ini_set('memory_limit', apply_filters('admin_memory_limit', WP_MAX_MEMORY_LIMIT));
729
+				$this->route_admin_request();
730
+			}
731
+		}
732
+	}
733
+
734
+
735
+	/**
736
+	 * Provides a way for related child admin pages to load stuff on the loaded admin page.
737
+	 *
738
+	 * @return void
739
+	 * @throws EE_Error
740
+	 */
741
+	private function _do_other_page_hooks()
742
+	{
743
+		$registered_pages = apply_filters('FHEE_do_other_page_hooks_' . $this->page_slug, []);
744
+		foreach ($registered_pages as $page) {
745
+			// now let's setup the file name and class that should be present
746
+			$classname = str_replace('.class.php', '', $page);
747
+			// autoloaders should take care of loading file
748
+			if (! class_exists($classname)) {
749
+				$error_msg[] = sprintf(
750
+					esc_html__(
751
+						'Something went wrong with loading the %s admin hooks page.',
752
+						'event_espresso'
753
+					),
754
+					$page
755
+				);
756
+				$error_msg[] = $error_msg[0]
757
+							   . "\r\n"
758
+							   . sprintf(
759
+								   esc_html__(
760
+									   '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',
761
+									   'event_espresso'
762
+								   ),
763
+								   $page,
764
+								   '<br />',
765
+								   '<strong>' . $classname . '</strong>'
766
+							   );
767
+				throw new EE_Error(implode('||', $error_msg));
768
+			}
769
+			// notice we are passing the instance of this class to the hook object.
770
+			$this->loader->getShared($classname, [$this]);
771
+		}
772
+	}
773
+
774
+
775
+	/**
776
+	 * @throws ReflectionException
777
+	 * @throws EE_Error
778
+	 */
779
+	public function load_page_dependencies()
780
+	{
781
+		try {
782
+			$this->_load_page_dependencies();
783
+		} catch (EE_Error $e) {
784
+			$e->get_error();
785
+		}
786
+	}
787
+
788
+
789
+	/**
790
+	 * load_page_dependencies
791
+	 * loads things specific to this page class when its loaded.  Really helps with efficiency.
792
+	 *
793
+	 * @return void
794
+	 * @throws DomainException
795
+	 * @throws EE_Error
796
+	 * @throws InvalidArgumentException
797
+	 * @throws InvalidDataTypeException
798
+	 * @throws InvalidInterfaceException
799
+	 */
800
+	protected function _load_page_dependencies()
801
+	{
802
+		// let's set the current_screen and screen options to override what WP set
803
+		$this->_current_screen = get_current_screen();
804
+		// load admin_notices - global, page class, and view specific
805
+		add_action('admin_notices', [$this, 'admin_notices_global'], 5);
806
+		add_action('admin_notices', [$this, 'admin_notices'], 10);
807
+		if (method_exists($this, 'admin_notices_' . $this->_current_view)) {
808
+			add_action('admin_notices', [$this, 'admin_notices_' . $this->_current_view], 15);
809
+		}
810
+		// load network admin_notices - global, page class, and view specific
811
+		add_action('network_admin_notices', [$this, 'network_admin_notices_global'], 5);
812
+		if (method_exists($this, 'network_admin_notices_' . $this->_current_view)) {
813
+			add_action('network_admin_notices', [$this, 'network_admin_notices_' . $this->_current_view]);
814
+		}
815
+		// this will save any per_page screen options if they are present
816
+		$this->_set_per_page_screen_options();
817
+		// setup list table properties
818
+		$this->_set_list_table();
819
+		// child classes can "register" a metabox to be automatically handled via the _page_config array property.
820
+		// However in some cases the metaboxes will need to be added within a route handling callback.
821
+		$this->_add_registered_meta_boxes();
822
+		$this->_add_screen_columns();
823
+		// add screen options - global, page child class, and view specific
824
+		$this->_add_global_screen_options();
825
+		$this->_add_screen_options();
826
+		$add_screen_options = "_add_screen_options_{$this->_current_view}";
827
+		if (method_exists($this, $add_screen_options)) {
828
+			$this->{$add_screen_options}();
829
+		}
830
+		// add help tab(s) - set via page_config and qtips.
831
+		$this->_add_help_tabs();
832
+		$this->_add_qtips();
833
+		// add feature_pointers - global, page child class, and view specific
834
+		$this->_add_feature_pointers();
835
+		$this->_add_global_feature_pointers();
836
+		$add_feature_pointer = "_add_feature_pointer_{$this->_current_view}";
837
+		if (method_exists($this, $add_feature_pointer)) {
838
+			$this->{$add_feature_pointer}();
839
+		}
840
+		// enqueue scripts/styles - global, page class, and view specific
841
+		add_action('admin_enqueue_scripts', [$this, 'load_global_scripts_styles'], 5);
842
+		add_action('admin_enqueue_scripts', [$this, 'load_scripts_styles'], 10);
843
+		if (method_exists($this, "load_scripts_styles_{$this->_current_view}")) {
844
+			add_action('admin_enqueue_scripts', [$this, "load_scripts_styles_{$this->_current_view}"], 15);
845
+		}
846
+		add_action('admin_enqueue_scripts', [$this, 'admin_footer_scripts_eei18n_js_strings'], 100);
847
+		// admin_print_footer_scripts - global, page child class, and view specific.
848
+		// NOTE, despite the name, whenever possible, scripts should NOT be loaded using this.
849
+		// In most cases that's doing_it_wrong().  But adding hidden container elements etc.
850
+		// is a good use case. Notice the late priority we're giving these
851
+		add_action('admin_print_footer_scripts', [$this, 'admin_footer_scripts_global'], 99);
852
+		add_action('admin_print_footer_scripts', [$this, 'admin_footer_scripts'], 100);
853
+		if (method_exists($this, "admin_footer_scripts_{$this->_current_view}")) {
854
+			add_action('admin_print_footer_scripts', [$this, "admin_footer_scripts_{$this->_current_view}"], 101);
855
+		}
856
+		// admin footer scripts
857
+		add_action('admin_footer', [$this, 'admin_footer_global'], 99);
858
+		add_action('admin_footer', [$this, 'admin_footer'], 100);
859
+		if (method_exists($this, "admin_footer_{$this->_current_view}")) {
860
+			add_action('admin_footer', [$this, "admin_footer_{$this->_current_view}"], 101);
861
+		}
862
+		do_action('FHEE__EE_Admin_Page___load_page_dependencies__after_load', $this->page_slug);
863
+		// targeted hook
864
+		do_action(
865
+			"FHEE__EE_Admin_Page___load_page_dependencies__after_load__{$this->page_slug}__{$this->_req_action}"
866
+		);
867
+	}
868
+
869
+
870
+	/**
871
+	 * _set_defaults
872
+	 * This sets some global defaults for class properties.
873
+	 */
874
+	private function _set_defaults()
875
+	{
876
+		$this->_current_screen       = $this->_admin_page_title = $this->_req_action = $this->_req_nonce = null;
877
+		$this->_event                = $this->_template_path = $this->_column_template_path = null;
878
+		$this->_nav_tabs             = $this->_views = $this->_page_routes = [];
879
+		$this->_page_config          = $this->_default_route_query_args = [];
880
+		$this->_default_nav_tab_name = 'overview';
881
+		// init template args
882
+		$this->_template_args = [
883
+			'admin_page_header'  => '',
884
+			'admin_page_content' => '',
885
+			'post_body_content'  => '',
886
+			'before_list_table'  => '',
887
+			'after_list_table'   => '',
888
+		];
889
+	}
890
+
891
+
892
+	/**
893
+	 * route_admin_request
894
+	 *
895
+	 * @return void
896
+	 * @throws InvalidArgumentException
897
+	 * @throws InvalidInterfaceException
898
+	 * @throws InvalidDataTypeException
899
+	 * @throws EE_Error
900
+	 * @throws ReflectionException
901
+	 * @see    _route_admin_request()
902
+	 */
903
+	public function route_admin_request()
904
+	{
905
+		try {
906
+			$this->_route_admin_request();
907
+		} catch (EE_Error $e) {
908
+			$e->get_error();
909
+		}
910
+	}
911
+
912
+
913
+	public function set_wp_page_slug($wp_page_slug)
914
+	{
915
+		$this->_wp_page_slug = $wp_page_slug;
916
+		// if in network admin then we need to append "-network" to the page slug. Why? Because that's how WP rolls...
917
+		if (is_network_admin()) {
918
+			$this->_wp_page_slug .= '-network';
919
+		}
920
+	}
921
+
922
+
923
+	/**
924
+	 * _verify_routes
925
+	 * All this method does is verify the incoming request and make sure that routes exist for it.  We do this early so
926
+	 * we know if we need to drop out.
927
+	 *
928
+	 * @return bool
929
+	 * @throws EE_Error
930
+	 */
931
+	protected function _verify_routes()
932
+	{
933
+		do_action('AHEE_log', __FILE__, __FUNCTION__, '');
934
+		if (! $this->_current_page && ! $this->request->isAjax()) {
935
+			return false;
936
+		}
937
+		$this->_route = false;
938
+		// check that the page_routes array is not empty
939
+		if (empty($this->_page_routes)) {
940
+			// user error msg
941
+			$error_msg = sprintf(
942
+				esc_html__('No page routes have been set for the %s admin page.', 'event_espresso'),
943
+				$this->_admin_page_title
944
+			);
945
+			// developer error msg
946
+			$error_msg .= '||' . $error_msg
947
+						  . esc_html__(
948
+							  ' Make sure the "set_page_routes()" method exists, and is setting the "_page_routes" array properly.',
949
+							  'event_espresso'
950
+						  );
951
+			throw new EE_Error($error_msg);
952
+		}
953
+		// and that the requested page route exists
954
+		if (array_key_exists($this->_req_action, $this->_page_routes)) {
955
+			$this->_route        = $this->_page_routes[ $this->_req_action ];
956
+			$this->_route_config = $this->_page_config[ $this->_req_action ] ?? [];
957
+		} else {
958
+			// user error msg
959
+			$error_msg = sprintf(
960
+				esc_html__(
961
+					'The requested page route does not exist for the %s admin page.',
962
+					'event_espresso'
963
+				),
964
+				$this->_admin_page_title
965
+			);
966
+			// developer error msg
967
+			$error_msg .= '||' . $error_msg
968
+						  . sprintf(
969
+							  esc_html__(
970
+								  ' Create a key in the "_page_routes" array named "%s" and set its value to the appropriate method.',
971
+								  'event_espresso'
972
+							  ),
973
+							  $this->_req_action
974
+						  );
975
+			throw new EE_Error($error_msg);
976
+		}
977
+		// and that a default route exists
978
+		if (! array_key_exists('default', $this->_page_routes)) {
979
+			// user error msg
980
+			$error_msg = sprintf(
981
+				esc_html__(
982
+					'A default page route has not been set for the % admin page.',
983
+					'event_espresso'
984
+				),
985
+				$this->_admin_page_title
986
+			);
987
+			// developer error msg
988
+			$error_msg .= '||' . $error_msg
989
+						  . esc_html__(
990
+							  ' Create a key in the "_page_routes" array named "default" and set its value to your default page method.',
991
+							  'event_espresso'
992
+						  );
993
+			throw new EE_Error($error_msg);
994
+		}
995
+
996
+		// first lets' catch if the UI request has EVER been set.
997
+		if ($this->_is_UI_request === null) {
998
+			// lets set if this is a UI request or not.
999
+			$this->_is_UI_request = ! $this->request->getRequestParam('noheader', false, 'bool');
1000
+			// wait a minute... we might have a noheader in the route array
1001
+			$this->_is_UI_request = ! (
1002
+				is_array($this->_route) && isset($this->_route['noheader']) && $this->_route['noheader']
1003
+			)
1004
+				? $this->_is_UI_request
1005
+				: false;
1006
+		}
1007
+		$this->_set_current_labels();
1008
+		return true;
1009
+	}
1010
+
1011
+
1012
+	/**
1013
+	 * this method simply verifies a given route and makes sure its an actual route available for the loaded page
1014
+	 *
1015
+	 * @param string $route the route name we're verifying
1016
+	 * @return bool we'll throw an exception if this isn't a valid route.
1017
+	 * @throws EE_Error
1018
+	 */
1019
+	protected function _verify_route($route)
1020
+	{
1021
+		if (array_key_exists($this->_req_action, $this->_page_routes)) {
1022
+			return true;
1023
+		}
1024
+		// user error msg
1025
+		$error_msg = sprintf(
1026
+			esc_html__('The given page route does not exist for the %s admin page.', 'event_espresso'),
1027
+			$this->_admin_page_title
1028
+		);
1029
+		// developer error msg
1030
+		$error_msg .= '||' . $error_msg
1031
+					  . sprintf(
1032
+						  esc_html__(
1033
+							  ' Check the route you are using in your method (%s) and make sure it matches a route set in your "_page_routes" array property',
1034
+							  'event_espresso'
1035
+						  ),
1036
+						  $route
1037
+					  );
1038
+		throw new EE_Error($error_msg);
1039
+	}
1040
+
1041
+
1042
+	/**
1043
+	 * perform nonce verification
1044
+	 * This method has be encapsulated here so that any ajax requests that bypass normal routes can verify their nonces
1045
+	 * using this method (and save retyping!)
1046
+	 *
1047
+	 * @param string $nonce     The nonce sent
1048
+	 * @param string $nonce_ref The nonce reference string (name0)
1049
+	 * @return void
1050
+	 * @throws EE_Error
1051
+	 * @throws InvalidArgumentException
1052
+	 * @throws InvalidDataTypeException
1053
+	 * @throws InvalidInterfaceException
1054
+	 */
1055
+	protected function _verify_nonce($nonce, $nonce_ref)
1056
+	{
1057
+		// verify nonce against expected value
1058
+		if (! wp_verify_nonce($nonce, $nonce_ref)) {
1059
+			// these are not the droids you are looking for !!!
1060
+			$msg = sprintf(
1061
+				esc_html__('%sNonce Fail.%s', 'event_espresso'),
1062
+				'<a href="https://www.youtube.com/watch?v=56_S0WeTkzs">',
1063
+				'</a>'
1064
+			);
1065
+			if (WP_DEBUG) {
1066
+				$msg .= "\n  ";
1067
+				$msg .= sprintf(
1068
+					esc_html__(
1069
+						'In order to dynamically generate nonces for your actions, use the %s::add_query_args_and_nonce() method. May the Nonce be with you!',
1070
+						'event_espresso'
1071
+					),
1072
+					__CLASS__
1073
+				);
1074
+			}
1075
+			if (! $this->request->isAjax()) {
1076
+				wp_die($msg);
1077
+			}
1078
+			EE_Error::add_error($msg, __FILE__, __FUNCTION__, __LINE__);
1079
+			$this->_return_json();
1080
+		}
1081
+	}
1082
+
1083
+
1084
+	/**
1085
+	 * _route_admin_request()
1086
+	 * Meat and potatoes of the class.  Basically, this dude checks out what's being requested and sees if there are
1087
+	 * some doodads to work the magic and handle the flingjangy. Translation:  Checks if the requested action is listed
1088
+	 * in the page routes and then will try to load the corresponding method.
1089
+	 *
1090
+	 * @return void
1091
+	 * @throws EE_Error
1092
+	 * @throws InvalidArgumentException
1093
+	 * @throws InvalidDataTypeException
1094
+	 * @throws InvalidInterfaceException
1095
+	 * @throws ReflectionException
1096
+	 */
1097
+	protected function _route_admin_request()
1098
+	{
1099
+		if (! $this->_is_UI_request) {
1100
+			$this->_verify_routes();
1101
+		}
1102
+		$nonce_check = ! isset($this->_route_config['require_nonce']) || $this->_route_config['require_nonce'];
1103
+		if ($this->_req_action !== 'default' && $nonce_check) {
1104
+			// set nonce from post data
1105
+			$nonce = $this->request->getRequestParam($this->_req_nonce, '');
1106
+			$this->_verify_nonce($nonce, $this->_req_nonce);
1107
+		}
1108
+		// set the nav_tabs array but ONLY if this is  UI_request
1109
+		if ($this->_is_UI_request) {
1110
+			$this->_set_nav_tabs();
1111
+		}
1112
+		// grab callback function
1113
+		$func = is_array($this->_route) && isset($this->_route['func']) ? $this->_route['func'] : $this->_route;
1114
+		// check if callback has args
1115
+		$args      = is_array($this->_route) && isset($this->_route['args']) ? $this->_route['args'] : [];
1116
+		$error_msg = '';
1117
+		// action right before calling route
1118
+		// (hook is something like 'AHEE__Registrations_Admin_Page__route_admin_request')
1119
+		if (! did_action('AHEE__EE_Admin_Page__route_admin_request')) {
1120
+			do_action('AHEE__EE_Admin_Page__route_admin_request', $this->_current_view, $this);
1121
+		}
1122
+		// strip _wp_http_referer from the server REQUEST_URI
1123
+		// else it grows in length on every submission due to recursion,
1124
+		// ultimately causing a "Request-URI Too Large" error
1125
+		$this->request->unSetRequestParam('_wp_http_referer');
1126
+		$this->request->unSetServerParam('_wp_http_referer');
1127
+		$cleaner_request_uri = remove_query_arg(
1128
+			'_wp_http_referer',
1129
+			wp_unslash($this->request->getServerParam('REQUEST_URI'))
1130
+		);
1131
+		$this->request->setRequestParam('_wp_http_referer', $cleaner_request_uri, true);
1132
+		$this->request->setServerParam('REQUEST_URI', $cleaner_request_uri, true);
1133
+		if (! empty($func)) {
1134
+			if (is_array($func)) {
1135
+				[$class, $method] = $func;
1136
+			} elseif (strpos($func, '::') !== false) {
1137
+				[$class, $method] = explode('::', $func);
1138
+			} else {
1139
+				$class  = $this;
1140
+				$method = $func;
1141
+			}
1142
+			// is it neither a class method NOR a standalone function?
1143
+			if (! method_exists($class, $method) && ! function_exists($method)) {
1144
+				// user error msg
1145
+				$error_msg = esc_html__(
1146
+					'An error occurred. The  requested page route could not be found.',
1147
+					'event_espresso'
1148
+				);
1149
+				// developer error msg
1150
+				$error_msg .= '||';
1151
+				$error_msg .= sprintf(
1152
+					esc_html__(
1153
+						'Page route "%s" could not be called. Check that the spelling for method names and actions in the "_page_routes" array are all correct.',
1154
+						'event_espresso'
1155
+					),
1156
+					$method
1157
+				);
1158
+				throw new EE_Error($error_msg);
1159
+			}
1160
+			if ($class !== $this && ! in_array($this, $args)) {
1161
+				$args = array_merge(['admin_page' => $this], $args);
1162
+			}
1163
+			try {
1164
+				$success = method_exists($class, $method) && call_user_func_array([$class, $method], $args);
1165
+				if (! $success && function_exists($method)) {
1166
+					call_user_func_array($method, $args);
1167
+				}
1168
+			} catch (Throwable $throwable) {
1169
+				$class_name = is_object($class) ? get_class($class) : $class;
1170
+				new ExceptionStackTraceDisplay(
1171
+					new RuntimeException(
1172
+						sprintf(
1173
+							esc_html__(
1174
+								'The following error occurred while trying to route the %1$s admin request: %2$s',
1175
+								'event_espresso'
1176
+							),
1177
+							"$class_name::$method()",
1178
+							$throwable->getMessage()
1179
+						)
1180
+					)
1181
+				);
1182
+			}
1183
+		}
1184
+		// if we've routed and this route has a no headers route AND a sent_headers_route,
1185
+		// then we need to reset the routing properties to the new route.
1186
+		// 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.
1187
+		if (
1188
+			$this->_is_UI_request === false
1189
+			&& is_array($this->_route)
1190
+			&& ! empty($this->_route['headers_sent_route'])
1191
+		) {
1192
+			$this->_reset_routing_properties($this->_route['headers_sent_route']);
1193
+		}
1194
+	}
1195
+
1196
+
1197
+	/**
1198
+	 * This method just allows the resetting of page properties in the case where a no headers
1199
+	 * route redirects to a headers route in its route config.
1200
+	 *
1201
+	 * @param string $new_route New (non header) route to redirect to.
1202
+	 * @return   void
1203
+	 * @throws ReflectionException
1204
+	 * @throws InvalidArgumentException
1205
+	 * @throws InvalidInterfaceException
1206
+	 * @throws InvalidDataTypeException
1207
+	 * @throws EE_Error
1208
+	 * @since   4.3.0
1209
+	 */
1210
+	protected function _reset_routing_properties($new_route)
1211
+	{
1212
+		$this->_is_UI_request = true;
1213
+		// now we set the current route to whatever the headers_sent_route is set at
1214
+		$this->request->setRequestParam('action', $new_route);
1215
+		// rerun page setup
1216
+		$this->_page_setup();
1217
+	}
1218
+
1219
+
1220
+	/**
1221
+	 * _add_query_arg
1222
+	 * adds nonce to array of arguments then calls WP add_query_arg function
1223
+	 *(internally just uses EEH_URL's function with the same name)
1224
+	 *
1225
+	 * @param array  $args
1226
+	 * @param string $url
1227
+	 * @param bool   $sticky                  if true, then the existing Request params will be appended to the
1228
+	 *                                        generated url in an associative array indexed by the key 'wp_referer';
1229
+	 *                                        Example usage: If the current page is:
1230
+	 *                                        http://mydomain.com/wp-admin/admin.php?page=espresso_registrations
1231
+	 *                                        &action=default&event_id=20&month_range=March%202015
1232
+	 *                                        &_wpnonce=5467821
1233
+	 *                                        and you call:
1234
+	 *                                        EE_Admin_Page::add_query_args_and_nonce(
1235
+	 *                                        array(
1236
+	 *                                        'action' => 'resend_something',
1237
+	 *                                        'page=>espresso_registrations'
1238
+	 *                                        ),
1239
+	 *                                        $some_url,
1240
+	 *                                        true
1241
+	 *                                        );
1242
+	 *                                        It will produce a url in this structure:
1243
+	 *                                        http://{$some_url}/?page=espresso_registrations&action=resend_something
1244
+	 *                                        &wp_referer[action]=default&wp_referer[event_id]=20&wpreferer[
1245
+	 *                                        month_range]=March%202015
1246
+	 * @param bool   $exclude_nonce           If true, the the nonce will be excluded from the generated nonce.
1247
+	 * @return string
1248
+	 */
1249
+	public static function add_query_args_and_nonce(
1250
+		$args = [],
1251
+		$url = '',
1252
+		$sticky = false,
1253
+		$exclude_nonce = false
1254
+	) {
1255
+		// if there is a _wp_http_referer include the values from the request but only if sticky = true
1256
+		if ($sticky) {
1257
+			/** @var RequestInterface $request */
1258
+			$request = LoaderFactory::getLoader()->getShared(RequestInterface::class);
1259
+			$request->unSetRequestParams(['_wp_http_referer', 'wp_referer'], true);
1260
+			$request->unSetServerParam('_wp_http_referer', true);
1261
+			foreach ($request->requestParams() as $key => $value) {
1262
+				// do not add nonces
1263
+				if (strpos($key, 'nonce') !== false) {
1264
+					continue;
1265
+				}
1266
+				$args[ 'wp_referer[' . $key . ']' ] = is_string($value) ? htmlspecialchars($value) : $value;
1267
+			}
1268
+		}
1269
+		return EEH_URL::add_query_args_and_nonce($args, $url, $exclude_nonce);
1270
+	}
1271
+
1272
+
1273
+	/**
1274
+	 * This returns a generated link that will load the related help tab.
1275
+	 *
1276
+	 * @param string $help_tab_id the id for the connected help tab
1277
+	 * @param string $icon_style  (optional) include css class for the style you want to use for the help icon.
1278
+	 * @param string $help_text   (optional) send help text you want to use for the link if default not to be used
1279
+	 * @return string              generated link
1280
+	 * @uses EEH_Template::get_help_tab_link()
1281
+	 */
1282
+	protected function _get_help_tab_link($help_tab_id, $icon_style = '', $help_text = '')
1283
+	{
1284
+		return EEH_Template::get_help_tab_link(
1285
+			$help_tab_id,
1286
+			$this->page_slug,
1287
+			$this->_req_action,
1288
+			$icon_style,
1289
+			$help_text
1290
+		);
1291
+	}
1292
+
1293
+
1294
+	/**
1295
+	 * _add_help_tabs
1296
+	 * Note child classes define their help tabs within the page_config array.
1297
+	 *
1298
+	 * @link   http://codex.wordpress.org/Function_Reference/add_help_tab
1299
+	 * @return void
1300
+	 * @throws DomainException
1301
+	 * @throws EE_Error
1302
+	 * @throws ReflectionException
1303
+	 */
1304
+	protected function _add_help_tabs()
1305
+	{
1306
+		if (isset($this->_page_config[ $this->_req_action ])) {
1307
+			$config = $this->_page_config[ $this->_req_action ];
1308
+			// let's see if there is a help_sidebar set for the current route and we'll set that up for usage as well.
1309
+			if (is_array($config) && isset($config['help_sidebar'])) {
1310
+				// check that the callback given is valid
1311
+				if (! method_exists($this, $config['help_sidebar'])) {
1312
+					throw new EE_Error(
1313
+						sprintf(
1314
+							esc_html__(
1315
+								'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',
1316
+								'event_espresso'
1317
+							),
1318
+							$config['help_sidebar'],
1319
+							$this->class_name
1320
+						)
1321
+					);
1322
+				}
1323
+				$content = apply_filters(
1324
+					'FHEE__' . $this->class_name . '__add_help_tabs__help_sidebar',
1325
+					$this->{$config['help_sidebar']}()
1326
+				);
1327
+				$this->_current_screen->set_help_sidebar($content);
1328
+			}
1329
+			if (! isset($config['help_tabs'])) {
1330
+				return;
1331
+			} //no help tabs for this route
1332
+			foreach ((array) $config['help_tabs'] as $tab_id => $cfg) {
1333
+				// we're here so there ARE help tabs!
1334
+				// make sure we've got what we need
1335
+				if (! isset($cfg['title'])) {
1336
+					throw new EE_Error(
1337
+						esc_html__(
1338
+							'The _page_config array is not set up properly for help tabs.  It is missing a title',
1339
+							'event_espresso'
1340
+						)
1341
+					);
1342
+				}
1343
+				if (! isset($cfg['filename']) && ! isset($cfg['callback']) && ! isset($cfg['content'])) {
1344
+					throw new EE_Error(
1345
+						esc_html__(
1346
+							'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',
1347
+							'event_espresso'
1348
+						)
1349
+					);
1350
+				}
1351
+				// first priority goes to content.
1352
+				if (! empty($cfg['content'])) {
1353
+					$content = ! empty($cfg['content']) ? $cfg['content'] : null;
1354
+					// second priority goes to filename
1355
+				} elseif (! empty($cfg['filename'])) {
1356
+					$file_path = $this->_get_dir() . '/help_tabs/' . $cfg['filename'] . '.help_tab.php';
1357
+					// 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)
1358
+					$file_path = ! is_readable($file_path) ? EE_ADMIN_PAGES
1359
+															 . basename($this->_get_dir())
1360
+															 . '/help_tabs/'
1361
+															 . $cfg['filename']
1362
+															 . '.help_tab.php' : $file_path;
1363
+					// if file is STILL not readable then let's do a EE_Error so its more graceful than a fatal error.
1364
+					if (! isset($cfg['callback']) && ! is_readable($file_path)) {
1365
+						EE_Error::add_error(
1366
+							sprintf(
1367
+								esc_html__(
1368
+									'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',
1369
+									'event_espresso'
1370
+								),
1371
+								$tab_id,
1372
+								key($config),
1373
+								$file_path
1374
+							),
1375
+							__FILE__,
1376
+							__FUNCTION__,
1377
+							__LINE__
1378
+						);
1379
+						return;
1380
+					}
1381
+					$template_args['admin_page_obj'] = $this;
1382
+					$content                         = EEH_Template::display_template(
1383
+						$file_path,
1384
+						$template_args,
1385
+						true
1386
+					);
1387
+				} else {
1388
+					$content = '';
1389
+				}
1390
+				// check if callback is valid
1391
+				if (
1392
+					empty($content)
1393
+					&& (
1394
+						! isset($cfg['callback']) || ! method_exists($this, $cfg['callback'])
1395
+					)
1396
+				) {
1397
+					EE_Error::add_error(
1398
+						sprintf(
1399
+							esc_html__(
1400
+								'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.',
1401
+								'event_espresso'
1402
+							),
1403
+							$cfg['title']
1404
+						),
1405
+						__FILE__,
1406
+						__FUNCTION__,
1407
+						__LINE__
1408
+					);
1409
+					return;
1410
+				}
1411
+				// setup config array for help tab method
1412
+				$id  = $this->page_slug . '-' . $this->_req_action . '-' . $tab_id;
1413
+				$_ht = [
1414
+					'id'       => $id,
1415
+					'title'    => $cfg['title'],
1416
+					'callback' => isset($cfg['callback']) && empty($content) ? [$this, $cfg['callback']] : null,
1417
+					'content'  => $content,
1418
+				];
1419
+				$this->_current_screen->add_help_tab($_ht);
1420
+			}
1421
+		}
1422
+	}
1423
+
1424
+
1425
+	/**
1426
+	 * This simply sets up any qtips that have been defined in the page config
1427
+	 *
1428
+	 * @return void
1429
+	 * @throws ReflectionException
1430
+	 * @throws EE_Error
1431
+	 */
1432
+	protected function _add_qtips()
1433
+	{
1434
+		if (isset($this->_route_config['qtips'])) {
1435
+			$qtips = (array) $this->_route_config['qtips'];
1436
+			// load qtip loader
1437
+			$path = [
1438
+				$this->_get_dir() . '/qtips/',
1439
+				EE_ADMIN_PAGES . basename($this->_get_dir()) . '/qtips/',
1440
+			];
1441
+			EEH_Qtip_Loader::instance()->register($qtips, $path);
1442
+		}
1443
+	}
1444
+
1445
+
1446
+	/**
1447
+	 * _set_nav_tabs
1448
+	 * This sets up the nav tabs from the page_routes array.  This method can be overwritten by child classes if you
1449
+	 * wish to add additional tabs or modify accordingly.
1450
+	 *
1451
+	 * @return void
1452
+	 * @throws InvalidArgumentException
1453
+	 * @throws InvalidInterfaceException
1454
+	 * @throws InvalidDataTypeException
1455
+	 */
1456
+	protected function _set_nav_tabs()
1457
+	{
1458
+		$i = 0;
1459
+		$only_tab = count($this->_page_config) < 2;
1460
+		foreach ($this->_page_config as $slug => $config) {
1461
+			if (! is_array($config) || empty($config['nav'])) {
1462
+				continue;
1463
+			}
1464
+			// no nav tab for this config
1465
+			// check for persistent flag
1466
+			if ($slug !== $this->_req_action && isset($config['nav']['persistent']) && ! $config['nav']['persistent']) {
1467
+				// nav tab is only to appear when route requested.
1468
+				continue;
1469
+			}
1470
+			if (! $this->check_user_access($slug, true)) {
1471
+				// no nav tab because current user does not have access.
1472
+				continue;
1473
+			}
1474
+			$css_class = isset($config['css_class']) ? $config['css_class'] . ' ' : '';
1475
+			$css_class .= $only_tab ? ' ee-only-tab' : '';
1476
+
1477
+			$this->_nav_tabs[ $slug ] = [
1478
+				'url'       => $config['nav']['url'] ?? EE_Admin_Page::add_query_args_and_nonce(
1479
+					['action' => $slug],
1480
+					$this->_admin_base_url
1481
+				),
1482
+				'link_text' => $this->navTabLabel($config['nav'], $slug),
1483
+				'css_class' => $this->_req_action === $slug ? $css_class . ' nav-tab-active' : $css_class,
1484
+				'order'     => $config['nav']['order'] ?? $i,
1485
+			];
1486
+			$i++;
1487
+		}
1488
+		// if $this->_nav_tabs is empty then lets set the default
1489
+		if (empty($this->_nav_tabs)) {
1490
+			$this->_nav_tabs[ $this->_default_nav_tab_name ] = [
1491
+				'url'       => $this->_admin_base_url,
1492
+				'link_text' => ucwords(str_replace('_', ' ', $this->_default_nav_tab_name)),
1493
+				'css_class' => 'nav-tab-active',
1494
+				'order'     => 10,
1495
+			];
1496
+		}
1497
+		// now let's sort the tabs according to order
1498
+		usort($this->_nav_tabs, [$this, '_sort_nav_tabs']);
1499
+	}
1500
+
1501
+
1502
+	private function navTabLabel(array $nav_tab, string $slug): string
1503
+	{
1504
+		$label = $nav_tab['label'] ?? ucwords(str_replace('_', ' ', $slug));
1505
+		$icon = $nav_tab['icon'] ?? null;
1506
+		$icon = $icon ? '<span class="dashicons ' . $icon . '"></span>' : '';
1507
+		return '
1508 1508
             <span class="ee-admin-screen-tab__label">
1509 1509
                 ' . $icon . '
1510 1510
                 <span class="ee-nav-label__text">' . $label . '</span>
1511 1511
             </span>';
1512
-    }
1513
-
1514
-    /**
1515
-     * _set_current_labels
1516
-     * This method modifies the _labels property with any optional specific labels indicated in the _page_routes
1517
-     * property array
1518
-     *
1519
-     * @return void
1520
-     */
1521
-    private function _set_current_labels()
1522
-    {
1523
-        if (is_array($this->_route_config) && isset($this->_route_config['labels'])) {
1524
-            foreach ($this->_route_config['labels'] as $label => $text) {
1525
-                if (is_array($text)) {
1526
-                    foreach ($text as $sublabel => $subtext) {
1527
-                        $this->_labels[ $label ][ $sublabel ] = $subtext;
1528
-                    }
1529
-                } else {
1530
-                    $this->_labels[ $label ] = $text;
1531
-                }
1532
-            }
1533
-        }
1534
-    }
1535
-
1536
-
1537
-    /**
1538
-     *        verifies user access for this admin page
1539
-     *
1540
-     * @param string $route_to_check if present then the capability for the route matching this string is checked.
1541
-     * @param bool   $verify_only    Default is FALSE which means if user check fails then wp_die().  Otherwise just
1542
-     *                               return false if verify fail.
1543
-     * @return bool
1544
-     * @throws InvalidArgumentException
1545
-     * @throws InvalidDataTypeException
1546
-     * @throws InvalidInterfaceException
1547
-     */
1548
-    public function check_user_access($route_to_check = '', $verify_only = false)
1549
-    {
1550
-        do_action('AHEE_log', __FILE__, __FUNCTION__, '');
1551
-        $route_to_check = empty($route_to_check) ? $this->_req_action : $route_to_check;
1552
-        $capability = ! empty($route_to_check) && isset($this->_page_routes[ $route_to_check ])
1553
-                                      && is_array($this->_page_routes[ $route_to_check ])
1554
-                        && ! empty($this->_page_routes[ $route_to_check ]['capability'])
1555
-            ? $this->_page_routes[ $route_to_check ]['capability']
1556
-            : null;
1557
-
1558
-        if (empty($capability) && empty($route_to_check)) {
1559
-            $capability = is_array($this->_route) && empty($this->_route['capability']) ? 'manage_options'
1560
-                : $this->_route['capability'];
1561
-        } else {
1562
-            $capability = empty($capability) ? 'manage_options' : $capability;
1563
-        }
1564
-        $id = is_array($this->_route) && ! empty($this->_route['obj_id']) ? $this->_route['obj_id'] : 0;
1565
-        if (
1566
-            ! $this->request->isAjax()
1567
-            && (
1568
-                ! function_exists('is_admin')
1569
-                || ! EE_Registry::instance()->CAP->current_user_can(
1570
-                    $capability,
1571
-                    $this->page_slug
1572
-                    . '_'
1573
-                    . $route_to_check,
1574
-                    $id
1575
-                )
1576
-            )
1577
-        ) {
1578
-            if ($verify_only) {
1579
-                return false;
1580
-            }
1581
-            if (is_user_logged_in()) {
1582
-                wp_die(esc_html__('You do not have access to this route.', 'event_espresso'));
1583
-            } else {
1584
-                return false;
1585
-            }
1586
-        }
1587
-        return true;
1588
-    }
1589
-
1590
-
1591
-    /**
1592
-     * @param string                 $box_id
1593
-     * @param string                 $title
1594
-     * @param callable|string|null   $callback
1595
-     * @param string|array|WP_Screen $screen
1596
-     * @param string                 $context
1597
-     * @param string                 $priority
1598
-     * @param array|null             $callback_args
1599
-     */
1600
-    protected function addMetaBox(
1601
-        string $box_id,
1602
-        string $title,
1603
-        $callback,
1604
-        $screen,
1605
-        string $context = 'normal',
1606
-        string $priority = 'default',
1607
-        ?array $callback_args = null
1608
-    ) {
1609
-        if (! is_callable($callback)) {
1610
-            return;
1611
-        }
1612
-
1613
-        add_meta_box($box_id, $title, $callback, $screen, $context, $priority, $callback_args);
1614
-        add_filter(
1615
-            "postbox_classes_{$this->_wp_page_slug}_{$box_id}",
1616
-            function ($classes) {
1617
-                array_push($classes, 'ee-admin-container');
1618
-                return $classes;
1619
-            }
1620
-        );
1621
-    }
1622
-
1623
-
1624
-    /**
1625
-     * admin_init_global
1626
-     * This runs all the code that we want executed within the WP admin_init hook.
1627
-     * This method executes for ALL EE Admin pages.
1628
-     *
1629
-     * @return void
1630
-     */
1631
-    public function admin_init_global()
1632
-    {
1633
-    }
1634
-
1635
-
1636
-    /**
1637
-     * wp_loaded_global
1638
-     * This runs all the code that we want executed within the WP wp_loaded hook.  This method is optional for an
1639
-     * EE_Admin page and will execute on every EE Admin Page load
1640
-     *
1641
-     * @return void
1642
-     */
1643
-    public function wp_loaded()
1644
-    {
1645
-    }
1646
-
1647
-
1648
-    /**
1649
-     * admin_notices
1650
-     * Anything triggered by the 'admin_notices' WP hook should be put in here.  This particular method will apply on
1651
-     * ALL EE_Admin pages.
1652
-     *
1653
-     * @return void
1654
-     */
1655
-    public function admin_notices_global()
1656
-    {
1657
-        $this->_display_no_javascript_warning();
1658
-        $this->_display_espresso_notices();
1659
-    }
1660
-
1661
-
1662
-    public function network_admin_notices_global()
1663
-    {
1664
-        $this->_display_no_javascript_warning();
1665
-        $this->_display_espresso_notices();
1666
-    }
1667
-
1668
-
1669
-    /**
1670
-     * admin_footer_scripts_global
1671
-     * Anything triggered by the 'admin_print_footer_scripts' WP hook should be put in here. This particular method
1672
-     * will apply on ALL EE_Admin pages.
1673
-     *
1674
-     * @return void
1675
-     */
1676
-    public function admin_footer_scripts_global()
1677
-    {
1678
-        $this->_add_admin_page_ajax_loading_img();
1679
-        $this->_add_admin_page_overlay();
1680
-        // if metaboxes are present we need to add the nonce field
1681
-        if (
1682
-            isset($this->_route_config['metaboxes'])
1683
-            || isset($this->_route_config['list_table'])
1684
-            || (isset($this->_route_config['has_metaboxes']) && $this->_route_config['has_metaboxes'])
1685
-        ) {
1686
-            wp_nonce_field('closedpostboxes', 'closedpostboxesnonce', false);
1687
-            wp_nonce_field('meta-box-order', 'meta-box-order-nonce', false);
1688
-        }
1689
-    }
1690
-
1691
-
1692
-    /**
1693
-     * admin_footer_global
1694
-     * Anything triggered by the wp 'admin_footer' wp hook should be put in here.
1695
-     * This particular method will apply on ALL EE_Admin Pages.
1696
-     *
1697
-     * @return void
1698
-     */
1699
-    public function admin_footer_global()
1700
-    {
1701
-        // dialog container for dialog helper
1702
-        echo '
1512
+	}
1513
+
1514
+	/**
1515
+	 * _set_current_labels
1516
+	 * This method modifies the _labels property with any optional specific labels indicated in the _page_routes
1517
+	 * property array
1518
+	 *
1519
+	 * @return void
1520
+	 */
1521
+	private function _set_current_labels()
1522
+	{
1523
+		if (is_array($this->_route_config) && isset($this->_route_config['labels'])) {
1524
+			foreach ($this->_route_config['labels'] as $label => $text) {
1525
+				if (is_array($text)) {
1526
+					foreach ($text as $sublabel => $subtext) {
1527
+						$this->_labels[ $label ][ $sublabel ] = $subtext;
1528
+					}
1529
+				} else {
1530
+					$this->_labels[ $label ] = $text;
1531
+				}
1532
+			}
1533
+		}
1534
+	}
1535
+
1536
+
1537
+	/**
1538
+	 *        verifies user access for this admin page
1539
+	 *
1540
+	 * @param string $route_to_check if present then the capability for the route matching this string is checked.
1541
+	 * @param bool   $verify_only    Default is FALSE which means if user check fails then wp_die().  Otherwise just
1542
+	 *                               return false if verify fail.
1543
+	 * @return bool
1544
+	 * @throws InvalidArgumentException
1545
+	 * @throws InvalidDataTypeException
1546
+	 * @throws InvalidInterfaceException
1547
+	 */
1548
+	public function check_user_access($route_to_check = '', $verify_only = false)
1549
+	{
1550
+		do_action('AHEE_log', __FILE__, __FUNCTION__, '');
1551
+		$route_to_check = empty($route_to_check) ? $this->_req_action : $route_to_check;
1552
+		$capability = ! empty($route_to_check) && isset($this->_page_routes[ $route_to_check ])
1553
+									  && is_array($this->_page_routes[ $route_to_check ])
1554
+						&& ! empty($this->_page_routes[ $route_to_check ]['capability'])
1555
+			? $this->_page_routes[ $route_to_check ]['capability']
1556
+			: null;
1557
+
1558
+		if (empty($capability) && empty($route_to_check)) {
1559
+			$capability = is_array($this->_route) && empty($this->_route['capability']) ? 'manage_options'
1560
+				: $this->_route['capability'];
1561
+		} else {
1562
+			$capability = empty($capability) ? 'manage_options' : $capability;
1563
+		}
1564
+		$id = is_array($this->_route) && ! empty($this->_route['obj_id']) ? $this->_route['obj_id'] : 0;
1565
+		if (
1566
+			! $this->request->isAjax()
1567
+			&& (
1568
+				! function_exists('is_admin')
1569
+				|| ! EE_Registry::instance()->CAP->current_user_can(
1570
+					$capability,
1571
+					$this->page_slug
1572
+					. '_'
1573
+					. $route_to_check,
1574
+					$id
1575
+				)
1576
+			)
1577
+		) {
1578
+			if ($verify_only) {
1579
+				return false;
1580
+			}
1581
+			if (is_user_logged_in()) {
1582
+				wp_die(esc_html__('You do not have access to this route.', 'event_espresso'));
1583
+			} else {
1584
+				return false;
1585
+			}
1586
+		}
1587
+		return true;
1588
+	}
1589
+
1590
+
1591
+	/**
1592
+	 * @param string                 $box_id
1593
+	 * @param string                 $title
1594
+	 * @param callable|string|null   $callback
1595
+	 * @param string|array|WP_Screen $screen
1596
+	 * @param string                 $context
1597
+	 * @param string                 $priority
1598
+	 * @param array|null             $callback_args
1599
+	 */
1600
+	protected function addMetaBox(
1601
+		string $box_id,
1602
+		string $title,
1603
+		$callback,
1604
+		$screen,
1605
+		string $context = 'normal',
1606
+		string $priority = 'default',
1607
+		?array $callback_args = null
1608
+	) {
1609
+		if (! is_callable($callback)) {
1610
+			return;
1611
+		}
1612
+
1613
+		add_meta_box($box_id, $title, $callback, $screen, $context, $priority, $callback_args);
1614
+		add_filter(
1615
+			"postbox_classes_{$this->_wp_page_slug}_{$box_id}",
1616
+			function ($classes) {
1617
+				array_push($classes, 'ee-admin-container');
1618
+				return $classes;
1619
+			}
1620
+		);
1621
+	}
1622
+
1623
+
1624
+	/**
1625
+	 * admin_init_global
1626
+	 * This runs all the code that we want executed within the WP admin_init hook.
1627
+	 * This method executes for ALL EE Admin pages.
1628
+	 *
1629
+	 * @return void
1630
+	 */
1631
+	public function admin_init_global()
1632
+	{
1633
+	}
1634
+
1635
+
1636
+	/**
1637
+	 * wp_loaded_global
1638
+	 * This runs all the code that we want executed within the WP wp_loaded hook.  This method is optional for an
1639
+	 * EE_Admin page and will execute on every EE Admin Page load
1640
+	 *
1641
+	 * @return void
1642
+	 */
1643
+	public function wp_loaded()
1644
+	{
1645
+	}
1646
+
1647
+
1648
+	/**
1649
+	 * admin_notices
1650
+	 * Anything triggered by the 'admin_notices' WP hook should be put in here.  This particular method will apply on
1651
+	 * ALL EE_Admin pages.
1652
+	 *
1653
+	 * @return void
1654
+	 */
1655
+	public function admin_notices_global()
1656
+	{
1657
+		$this->_display_no_javascript_warning();
1658
+		$this->_display_espresso_notices();
1659
+	}
1660
+
1661
+
1662
+	public function network_admin_notices_global()
1663
+	{
1664
+		$this->_display_no_javascript_warning();
1665
+		$this->_display_espresso_notices();
1666
+	}
1667
+
1668
+
1669
+	/**
1670
+	 * admin_footer_scripts_global
1671
+	 * Anything triggered by the 'admin_print_footer_scripts' WP hook should be put in here. This particular method
1672
+	 * will apply on ALL EE_Admin pages.
1673
+	 *
1674
+	 * @return void
1675
+	 */
1676
+	public function admin_footer_scripts_global()
1677
+	{
1678
+		$this->_add_admin_page_ajax_loading_img();
1679
+		$this->_add_admin_page_overlay();
1680
+		// if metaboxes are present we need to add the nonce field
1681
+		if (
1682
+			isset($this->_route_config['metaboxes'])
1683
+			|| isset($this->_route_config['list_table'])
1684
+			|| (isset($this->_route_config['has_metaboxes']) && $this->_route_config['has_metaboxes'])
1685
+		) {
1686
+			wp_nonce_field('closedpostboxes', 'closedpostboxesnonce', false);
1687
+			wp_nonce_field('meta-box-order', 'meta-box-order-nonce', false);
1688
+		}
1689
+	}
1690
+
1691
+
1692
+	/**
1693
+	 * admin_footer_global
1694
+	 * Anything triggered by the wp 'admin_footer' wp hook should be put in here.
1695
+	 * This particular method will apply on ALL EE_Admin Pages.
1696
+	 *
1697
+	 * @return void
1698
+	 */
1699
+	public function admin_footer_global()
1700
+	{
1701
+		// dialog container for dialog helper
1702
+		echo '
1703 1703
         <div class="ee-admin-dialog-container auto-hide hidden">
1704 1704
             <div class="ee-notices"></div>
1705 1705
             <div class="ee-admin-dialog-container-inner-content"></div>
1706 1706
         </div>
1707 1707
         ';
1708 1708
 
1709
-        // current set timezone for timezone js
1710
-        echo '<span id="current_timezone" class="hidden">' . esc_html(EEH_DTT_Helper::get_timezone()) . '</span>';
1711
-    }
1712
-
1713
-
1714
-    /**
1715
-     * This function sees if there is a method for help popup content existing for the given route.  If there is then
1716
-     * we'll use the retrieved array to output the content using the template. For child classes: If you want to have
1717
-     * help popups then in your templates or your content you set "triggers" for the content using the
1718
-     * "_set_help_trigger('help_trigger_id')" where "help_trigger_id" is what you will use later in your custom method
1719
-     * for the help popup content on that page. Then in your Child_Admin_Page class you need to define a help popup
1720
-     * method for the content in the format "_help_popup_content_{route_name}()"  So if you are setting help content
1721
-     * for the
1722
-     * 'edit_event' route you should have a method named "_help_popup_content_edit_route". In your defined
1723
-     * "help_popup_content_..." method.  You must prepare and return an array in the following format array(
1724
-     *    'help_trigger_id' => array(
1725
-     *        'title' => esc_html__('localized title for popup', 'event_espresso'),
1726
-     *        'content' => esc_html__('localized content for popup', 'event_espresso')
1727
-     *    )
1728
-     * );
1729
-     * Then the EE_Admin_Parent will take care of making sure that is setup properly on the correct route.
1730
-     *
1731
-     * @param array $help_array
1732
-     * @param bool  $display
1733
-     * @return string content
1734
-     * @throws DomainException
1735
-     * @throws EE_Error
1736
-     */
1737
-    protected function _set_help_popup_content($help_array = [], $display = false)
1738
-    {
1739
-        $content    = '';
1740
-        $help_array = empty($help_array) ? $this->_get_help_content() : $help_array;
1741
-        // loop through the array and setup content
1742
-        foreach ($help_array as $trigger => $help) {
1743
-            // make sure the array is setup properly
1744
-            if (! isset($help['title'], $help['content'])) {
1745
-                throw new EE_Error(
1746
-                    esc_html__(
1747
-                        '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',
1748
-                        'event_espresso'
1749
-                    )
1750
-                );
1751
-            }
1752
-            // we're good so let's setup the template vars and then assign parsed template content to our content.
1753
-            $template_args = [
1754
-                'help_popup_id'      => $trigger,
1755
-                'help_popup_title'   => $help['title'],
1756
-                'help_popup_content' => $help['content'],
1757
-            ];
1758
-            $content       .= EEH_Template::display_template(
1759
-                EE_ADMIN_TEMPLATE . 'admin_help_popup.template.php',
1760
-                $template_args,
1761
-                true
1762
-            );
1763
-        }
1764
-        if ($display) {
1765
-            echo wp_kses($content, AllowedTags::getWithFormTags());
1766
-            return '';
1767
-        }
1768
-        return $content;
1769
-    }
1770
-
1771
-
1772
-    /**
1773
-     * All this does is retrieve the help content array if set by the EE_Admin_Page child
1774
-     *
1775
-     * @return array properly formatted array for help popup content
1776
-     * @throws EE_Error
1777
-     */
1778
-    private function _get_help_content()
1779
-    {
1780
-        // what is the method we're looking for?
1781
-        $method_name = '_help_popup_content_' . $this->_req_action;
1782
-        // if method doesn't exist let's get out.
1783
-        if (! method_exists($this, $method_name)) {
1784
-            return [];
1785
-        }
1786
-        // k we're good to go let's retrieve the help array
1787
-        $help_array = $this->{$method_name}();
1788
-        // make sure we've got an array!
1789
-        if (! is_array($help_array)) {
1790
-            throw new EE_Error(
1791
-                esc_html__(
1792
-                    'Something went wrong with help popup content generation. Expecting an array and well, this ain\'t no array bub.',
1793
-                    'event_espresso'
1794
-                )
1795
-            );
1796
-        }
1797
-        return $help_array;
1798
-    }
1799
-
1800
-
1801
-    /**
1802
-     * EE Admin Pages can use this to set a properly formatted trigger for a help popup.
1803
-     * By default the trigger html is printed.  Otherwise it can be returned if the $display flag is set "false"
1804
-     * See comments made on the _set_help_content method for understanding other parts to the help popup tool.
1805
-     *
1806
-     * @param string  $trigger_id reference for retrieving the trigger content for the popup
1807
-     * @param boolean $display    if false then we return the trigger string
1808
-     * @param array   $dimensions an array of dimensions for the box (array(h,w))
1809
-     * @return string
1810
-     * @throws DomainException
1811
-     * @throws EE_Error
1812
-     */
1813
-    protected function _set_help_trigger($trigger_id, $display = true, $dimensions = ['400', '640'])
1814
-    {
1815
-        if ($this->request->isAjax()) {
1816
-            return '';
1817
-        }
1818
-        // 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
1819
-        $help_array   = $this->_get_help_content();
1820
-        $help_content = '';
1821
-        if (empty($help_array) || ! isset($help_array[ $trigger_id ])) {
1822
-            $help_array[ $trigger_id ] = [
1823
-                'title'   => esc_html__('Missing Content', 'event_espresso'),
1824
-                'content' => esc_html__(
1825
-                    '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.)',
1826
-                    'event_espresso'
1827
-                ),
1828
-            ];
1829
-            $help_content = $this->_set_help_popup_content($help_array);
1830
-        }
1831
-        // let's setup the trigger
1832
-        $content = '<a class="ee-dialog" href="?height='
1833
-                   . esc_attr($dimensions[0])
1834
-                   . '&width='
1835
-                   . esc_attr($dimensions[1])
1836
-                   . '&inlineId='
1837
-                   . esc_attr($trigger_id)
1838
-                   . '" target="_blank"><span class="question ee-help-popup-question"></span></a>';
1839
-        $content .= $help_content;
1840
-        if ($display) {
1841
-            echo wp_kses($content, AllowedTags::getWithFormTags());
1842
-            return '';
1843
-        }
1844
-        return $content;
1845
-    }
1846
-
1847
-
1848
-    /**
1849
-     * _add_global_screen_options
1850
-     * Add any extra wp_screen_options within this method using built-in WP functions/methods for doing so.
1851
-     * This particular method will add_screen_options on ALL EE_Admin Pages
1852
-     *
1853
-     * @link   http://chrismarslender.com/wp-tutorials/wordpress-screen-options-tutorial/
1854
-     *         see also WP_Screen object documents...
1855
-     * @link   http://codex.wordpress.org/Class_Reference/WP_Screen
1856
-     * @abstract
1857
-     * @return void
1858
-     */
1859
-    private function _add_global_screen_options()
1860
-    {
1861
-    }
1862
-
1863
-
1864
-    /**
1865
-     * _add_global_feature_pointers
1866
-     * This method is used for implementing any "feature pointers" (using built-in WP styling js).
1867
-     * This particular method will implement feature pointers for ALL EE_Admin pages.
1868
-     * Note: this is just a placeholder for now.  Implementation will come down the road
1869
-     *
1870
-     * @see    WP_Internal_Pointers class in wp-admin/includes/template.php for example (its a final class so can't be
1871
-     *         extended) also see:
1872
-     * @link   http://eamann.com/tech/wordpress-portland/
1873
-     * @abstract
1874
-     * @return void
1875
-     */
1876
-    private function _add_global_feature_pointers()
1877
-    {
1878
-    }
1879
-
1880
-
1881
-    /**
1882
-     * load_global_scripts_styles
1883
-     * The scripts and styles enqueued in here will be loaded on every EE Admin page
1884
-     *
1885
-     * @return void
1886
-     */
1887
-    public function load_global_scripts_styles()
1888
-    {
1889
-        // add debugging styles
1890
-        if (WP_DEBUG) {
1891
-            add_action('admin_head', [$this, 'add_xdebug_style']);
1892
-        }
1893
-        // taking care of metaboxes
1894
-        if (
1895
-            empty($this->_cpt_route)
1896
-            && (isset($this->_route_config['metaboxes']) || isset($this->_route_config['has_metaboxes']))
1897
-        ) {
1898
-            wp_enqueue_script('dashboard');
1899
-        }
1900
-
1901
-        wp_enqueue_script(JqueryAssetManager::JS_HANDLE_JQUERY_UI_TOUCH_PUNCH);
1902
-        wp_enqueue_script(EspressoLegacyAdminAssetManager::JS_HANDLE_EE_ADMIN);
1903
-        // LOCALIZED DATA
1904
-        // localize script for ajax lazy loading
1905
-        wp_localize_script(
1906
-            EspressoLegacyAdminAssetManager::JS_HANDLE_EE_ADMIN,
1907
-            'eeLazyLoadingContainers',
1908
-            apply_filters(
1909
-                'FHEE__EE_Admin_Page_Core__load_global_scripts_styles__loader_containers',
1910
-                ['espresso_news_post_box_content']
1911
-            )
1912
-        );
1913
-        StatusChangeNotice::loadAssets();
1914
-
1915
-        add_filter(
1916
-            'admin_body_class',
1917
-            function ($classes) {
1918
-                if (strpos($classes, 'espresso-admin') === false) {
1919
-                    $classes .= ' espresso-admin';
1920
-                }
1921
-                return $classes;
1922
-            }
1923
-        );
1924
-    }
1925
-
1926
-
1927
-    /**
1928
-     *        admin_footer_scripts_eei18n_js_strings
1929
-     *
1930
-     * @return        void
1931
-     */
1932
-    public function admin_footer_scripts_eei18n_js_strings()
1933
-    {
1934
-        EE_Registry::$i18n_js_strings['ajax_url']       = WP_AJAX_URL;
1935
-        EE_Registry::$i18n_js_strings['confirm_delete'] = wp_strip_all_tags(
1936
-            __(
1937
-                '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!!!',
1938
-                'event_espresso'
1939
-            )
1940
-        );
1941
-        EE_Registry::$i18n_js_strings['January']        = wp_strip_all_tags(__('January', 'event_espresso'));
1942
-        EE_Registry::$i18n_js_strings['February']       = wp_strip_all_tags(__('February', 'event_espresso'));
1943
-        EE_Registry::$i18n_js_strings['March']          = wp_strip_all_tags(__('March', 'event_espresso'));
1944
-        EE_Registry::$i18n_js_strings['April']          = wp_strip_all_tags(__('April', 'event_espresso'));
1945
-        EE_Registry::$i18n_js_strings['May']            = wp_strip_all_tags(__('May', 'event_espresso'));
1946
-        EE_Registry::$i18n_js_strings['June']           = wp_strip_all_tags(__('June', 'event_espresso'));
1947
-        EE_Registry::$i18n_js_strings['July']           = wp_strip_all_tags(__('July', 'event_espresso'));
1948
-        EE_Registry::$i18n_js_strings['August']         = wp_strip_all_tags(__('August', 'event_espresso'));
1949
-        EE_Registry::$i18n_js_strings['September']      = wp_strip_all_tags(__('September', 'event_espresso'));
1950
-        EE_Registry::$i18n_js_strings['October']        = wp_strip_all_tags(__('October', 'event_espresso'));
1951
-        EE_Registry::$i18n_js_strings['November']       = wp_strip_all_tags(__('November', 'event_espresso'));
1952
-        EE_Registry::$i18n_js_strings['December']       = wp_strip_all_tags(__('December', 'event_espresso'));
1953
-        EE_Registry::$i18n_js_strings['Jan']            = wp_strip_all_tags(__('Jan', 'event_espresso'));
1954
-        EE_Registry::$i18n_js_strings['Feb']            = wp_strip_all_tags(__('Feb', 'event_espresso'));
1955
-        EE_Registry::$i18n_js_strings['Mar']            = wp_strip_all_tags(__('Mar', 'event_espresso'));
1956
-        EE_Registry::$i18n_js_strings['Apr']            = wp_strip_all_tags(__('Apr', 'event_espresso'));
1957
-        EE_Registry::$i18n_js_strings['May']            = wp_strip_all_tags(__('May', 'event_espresso'));
1958
-        EE_Registry::$i18n_js_strings['Jun']            = wp_strip_all_tags(__('Jun', 'event_espresso'));
1959
-        EE_Registry::$i18n_js_strings['Jul']            = wp_strip_all_tags(__('Jul', 'event_espresso'));
1960
-        EE_Registry::$i18n_js_strings['Aug']            = wp_strip_all_tags(__('Aug', 'event_espresso'));
1961
-        EE_Registry::$i18n_js_strings['Sep']            = wp_strip_all_tags(__('Sep', 'event_espresso'));
1962
-        EE_Registry::$i18n_js_strings['Oct']            = wp_strip_all_tags(__('Oct', 'event_espresso'));
1963
-        EE_Registry::$i18n_js_strings['Nov']            = wp_strip_all_tags(__('Nov', 'event_espresso'));
1964
-        EE_Registry::$i18n_js_strings['Dec']            = wp_strip_all_tags(__('Dec', 'event_espresso'));
1965
-        EE_Registry::$i18n_js_strings['Sunday']         = wp_strip_all_tags(__('Sunday', 'event_espresso'));
1966
-        EE_Registry::$i18n_js_strings['Monday']         = wp_strip_all_tags(__('Monday', 'event_espresso'));
1967
-        EE_Registry::$i18n_js_strings['Tuesday']        = wp_strip_all_tags(__('Tuesday', 'event_espresso'));
1968
-        EE_Registry::$i18n_js_strings['Wednesday']      = wp_strip_all_tags(__('Wednesday', 'event_espresso'));
1969
-        EE_Registry::$i18n_js_strings['Thursday']       = wp_strip_all_tags(__('Thursday', 'event_espresso'));
1970
-        EE_Registry::$i18n_js_strings['Friday']         = wp_strip_all_tags(__('Friday', 'event_espresso'));
1971
-        EE_Registry::$i18n_js_strings['Saturday']       = wp_strip_all_tags(__('Saturday', 'event_espresso'));
1972
-        EE_Registry::$i18n_js_strings['Sun']            = wp_strip_all_tags(__('Sun', 'event_espresso'));
1973
-        EE_Registry::$i18n_js_strings['Mon']            = wp_strip_all_tags(__('Mon', 'event_espresso'));
1974
-        EE_Registry::$i18n_js_strings['Tue']            = wp_strip_all_tags(__('Tue', 'event_espresso'));
1975
-        EE_Registry::$i18n_js_strings['Wed']            = wp_strip_all_tags(__('Wed', 'event_espresso'));
1976
-        EE_Registry::$i18n_js_strings['Thu']            = wp_strip_all_tags(__('Thu', 'event_espresso'));
1977
-        EE_Registry::$i18n_js_strings['Fri']            = wp_strip_all_tags(__('Fri', 'event_espresso'));
1978
-        EE_Registry::$i18n_js_strings['Sat']            = wp_strip_all_tags(__('Sat', 'event_espresso'));
1979
-    }
1980
-
1981
-
1982
-    /**
1983
-     *        load enhanced xdebug styles for ppl with failing eyesight
1984
-     *
1985
-     * @return        void
1986
-     */
1987
-    public function add_xdebug_style()
1988
-    {
1989
-        echo '<style>.xdebug-error { font-size:1.5em; }</style>';
1990
-    }
1991
-
1992
-
1993
-    /************************/
1994
-    /** LIST TABLE METHODS **/
1995
-    /************************/
1996
-    /**
1997
-     * this sets up the list table if the current view requires it.
1998
-     *
1999
-     * @return void
2000
-     * @throws EE_Error
2001
-     * @throws InvalidArgumentException
2002
-     * @throws InvalidDataTypeException
2003
-     * @throws InvalidInterfaceException
2004
-     */
2005
-    protected function _set_list_table()
2006
-    {
2007
-        // first is this a list_table view?
2008
-        if (! isset($this->_route_config['list_table'])) {
2009
-            return;
2010
-        } //not a list_table view so get out.
2011
-        // list table functions are per view specific (because some admin pages might have more than one list table!)
2012
-        $list_table_view = '_set_list_table_views_' . $this->_req_action;
2013
-        if (! method_exists($this, $list_table_view) || $this->{$list_table_view}() === false) {
2014
-            // user error msg
2015
-            $error_msg = esc_html__(
2016
-                'An error occurred. The requested list table views could not be found.',
2017
-                'event_espresso'
2018
-            );
2019
-            // developer error msg
2020
-            $error_msg .= '||'
2021
-                          . sprintf(
2022
-                              esc_html__(
2023
-                                  '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.',
2024
-                                  'event_espresso'
2025
-                              ),
2026
-                              $this->_req_action,
2027
-                              $list_table_view
2028
-                          );
2029
-            throw new EE_Error($error_msg);
2030
-        }
2031
-        // let's provide the ability to filter the views per PAGE AND ROUTE, per PAGE, and globally
2032
-        $this->_views = apply_filters(
2033
-            'FHEE_list_table_views_' . $this->page_slug . '_' . $this->_req_action,
2034
-            $this->_views
2035
-        );
2036
-        $this->_views = apply_filters('FHEE_list_table_views_' . $this->page_slug, $this->_views);
2037
-        $this->_views = apply_filters('FHEE_list_table_views', $this->_views);
2038
-        $this->_set_list_table_view();
2039
-        $this->_set_list_table_object();
2040
-    }
2041
-
2042
-
2043
-    /**
2044
-     * set current view for List Table
2045
-     *
2046
-     * @return void
2047
-     */
2048
-    protected function _set_list_table_view()
2049
-    {
2050
-        $this->_view = isset($this->_views['in_use']) ? 'in_use' : 'all';
2051
-        $status = $this->request->getRequestParam('status', null, 'key');
2052
-        $this->_view = $status && array_key_exists($status, $this->_views)
2053
-            ? $status
2054
-            : $this->_view;
2055
-    }
2056
-
2057
-
2058
-    /**
2059
-     * _set_list_table_object
2060
-     * WP_List_Table objects need to be loaded fairly early so automatic stuff WP does is taken care of.
2061
-     *
2062
-     * @throws InvalidInterfaceException
2063
-     * @throws InvalidArgumentException
2064
-     * @throws InvalidDataTypeException
2065
-     * @throws EE_Error
2066
-     * @throws InvalidInterfaceException
2067
-     */
2068
-    protected function _set_list_table_object()
2069
-    {
2070
-        if (isset($this->_route_config['list_table'])) {
2071
-            if (! class_exists($this->_route_config['list_table'])) {
2072
-                throw new EE_Error(
2073
-                    sprintf(
2074
-                        esc_html__(
2075
-                            '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.',
2076
-                            'event_espresso'
2077
-                        ),
2078
-                        $this->_route_config['list_table'],
2079
-                        $this->class_name
2080
-                    )
2081
-                );
2082
-            }
2083
-            $this->_list_table_object = $this->loader->getShared(
2084
-                $this->_route_config['list_table'],
2085
-                [$this]
2086
-            );
2087
-        }
2088
-    }
2089
-
2090
-
2091
-    /**
2092
-     * get_list_table_view_RLs - get it? View RL ?? VU-RL???  URL ??
2093
-     *
2094
-     * @param array $extra_query_args                     Optional. An array of extra query args to add to the generated
2095
-     *                                                    urls.  The array should be indexed by the view it is being
2096
-     *                                                    added to.
2097
-     * @return array
2098
-     */
2099
-    public function get_list_table_view_RLs($extra_query_args = [])
2100
-    {
2101
-        do_action('AHEE_log', __FILE__, __FUNCTION__, '');
2102
-        if (empty($this->_views)) {
2103
-            $this->_views = [];
2104
-        }
2105
-        // cycle thru views
2106
-        foreach ($this->_views as $key => $view) {
2107
-            $query_args = [];
2108
-            // check for current view
2109
-            $this->_views[ $key ]['class']               = $this->_view === $view['slug'] ? 'current' : '';
2110
-            $query_args['action']                        = $this->_req_action;
2111
-            $query_args[ $this->_req_action . '_nonce' ] = wp_create_nonce($query_args['action'] . '_nonce');
2112
-            $query_args['status']                        = $view['slug'];
2113
-            // merge any other arguments sent in.
2114
-            if (isset($extra_query_args[ $view['slug'] ])) {
2115
-                foreach ($extra_query_args[ $view['slug'] ] as $extra_query_arg) {
2116
-                    $query_args[] = $extra_query_arg;
2117
-                }
2118
-            }
2119
-            $this->_views[ $key ]['url'] = EE_Admin_Page::add_query_args_and_nonce($query_args, $this->_admin_base_url);
2120
-        }
2121
-        return $this->_views;
2122
-    }
2123
-
2124
-
2125
-    /**
2126
-     * _entries_per_page_dropdown
2127
-     * generates a dropdown box for selecting the number of visible rows in an admin page list table
2128
-     *
2129
-     * @param int $max_entries total number of rows in the table
2130
-     * @return string
2131
-     * @todo   : Note: ideally this should be added to the screen options dropdown as that would be consistent with how
2132
-     *         WP does it.
2133
-     */
2134
-    protected function _entries_per_page_dropdown($max_entries = 0)
2135
-    {
2136
-        do_action('AHEE_log', __FILE__, __FUNCTION__, '');
2137
-        $values   = [10, 25, 50, 100];
2138
-        $per_page = $this->request->getRequestParam('per_page', 10, 'int');
2139
-        if ($max_entries) {
2140
-            $values[] = $max_entries;
2141
-            sort($values);
2142
-        }
2143
-        $entries_per_page_dropdown = '
1709
+		// current set timezone for timezone js
1710
+		echo '<span id="current_timezone" class="hidden">' . esc_html(EEH_DTT_Helper::get_timezone()) . '</span>';
1711
+	}
1712
+
1713
+
1714
+	/**
1715
+	 * This function sees if there is a method for help popup content existing for the given route.  If there is then
1716
+	 * we'll use the retrieved array to output the content using the template. For child classes: If you want to have
1717
+	 * help popups then in your templates or your content you set "triggers" for the content using the
1718
+	 * "_set_help_trigger('help_trigger_id')" where "help_trigger_id" is what you will use later in your custom method
1719
+	 * for the help popup content on that page. Then in your Child_Admin_Page class you need to define a help popup
1720
+	 * method for the content in the format "_help_popup_content_{route_name}()"  So if you are setting help content
1721
+	 * for the
1722
+	 * 'edit_event' route you should have a method named "_help_popup_content_edit_route". In your defined
1723
+	 * "help_popup_content_..." method.  You must prepare and return an array in the following format array(
1724
+	 *    'help_trigger_id' => array(
1725
+	 *        'title' => esc_html__('localized title for popup', 'event_espresso'),
1726
+	 *        'content' => esc_html__('localized content for popup', 'event_espresso')
1727
+	 *    )
1728
+	 * );
1729
+	 * Then the EE_Admin_Parent will take care of making sure that is setup properly on the correct route.
1730
+	 *
1731
+	 * @param array $help_array
1732
+	 * @param bool  $display
1733
+	 * @return string content
1734
+	 * @throws DomainException
1735
+	 * @throws EE_Error
1736
+	 */
1737
+	protected function _set_help_popup_content($help_array = [], $display = false)
1738
+	{
1739
+		$content    = '';
1740
+		$help_array = empty($help_array) ? $this->_get_help_content() : $help_array;
1741
+		// loop through the array and setup content
1742
+		foreach ($help_array as $trigger => $help) {
1743
+			// make sure the array is setup properly
1744
+			if (! isset($help['title'], $help['content'])) {
1745
+				throw new EE_Error(
1746
+					esc_html__(
1747
+						'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',
1748
+						'event_espresso'
1749
+					)
1750
+				);
1751
+			}
1752
+			// we're good so let's setup the template vars and then assign parsed template content to our content.
1753
+			$template_args = [
1754
+				'help_popup_id'      => $trigger,
1755
+				'help_popup_title'   => $help['title'],
1756
+				'help_popup_content' => $help['content'],
1757
+			];
1758
+			$content       .= EEH_Template::display_template(
1759
+				EE_ADMIN_TEMPLATE . 'admin_help_popup.template.php',
1760
+				$template_args,
1761
+				true
1762
+			);
1763
+		}
1764
+		if ($display) {
1765
+			echo wp_kses($content, AllowedTags::getWithFormTags());
1766
+			return '';
1767
+		}
1768
+		return $content;
1769
+	}
1770
+
1771
+
1772
+	/**
1773
+	 * All this does is retrieve the help content array if set by the EE_Admin_Page child
1774
+	 *
1775
+	 * @return array properly formatted array for help popup content
1776
+	 * @throws EE_Error
1777
+	 */
1778
+	private function _get_help_content()
1779
+	{
1780
+		// what is the method we're looking for?
1781
+		$method_name = '_help_popup_content_' . $this->_req_action;
1782
+		// if method doesn't exist let's get out.
1783
+		if (! method_exists($this, $method_name)) {
1784
+			return [];
1785
+		}
1786
+		// k we're good to go let's retrieve the help array
1787
+		$help_array = $this->{$method_name}();
1788
+		// make sure we've got an array!
1789
+		if (! is_array($help_array)) {
1790
+			throw new EE_Error(
1791
+				esc_html__(
1792
+					'Something went wrong with help popup content generation. Expecting an array and well, this ain\'t no array bub.',
1793
+					'event_espresso'
1794
+				)
1795
+			);
1796
+		}
1797
+		return $help_array;
1798
+	}
1799
+
1800
+
1801
+	/**
1802
+	 * EE Admin Pages can use this to set a properly formatted trigger for a help popup.
1803
+	 * By default the trigger html is printed.  Otherwise it can be returned if the $display flag is set "false"
1804
+	 * See comments made on the _set_help_content method for understanding other parts to the help popup tool.
1805
+	 *
1806
+	 * @param string  $trigger_id reference for retrieving the trigger content for the popup
1807
+	 * @param boolean $display    if false then we return the trigger string
1808
+	 * @param array   $dimensions an array of dimensions for the box (array(h,w))
1809
+	 * @return string
1810
+	 * @throws DomainException
1811
+	 * @throws EE_Error
1812
+	 */
1813
+	protected function _set_help_trigger($trigger_id, $display = true, $dimensions = ['400', '640'])
1814
+	{
1815
+		if ($this->request->isAjax()) {
1816
+			return '';
1817
+		}
1818
+		// 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
1819
+		$help_array   = $this->_get_help_content();
1820
+		$help_content = '';
1821
+		if (empty($help_array) || ! isset($help_array[ $trigger_id ])) {
1822
+			$help_array[ $trigger_id ] = [
1823
+				'title'   => esc_html__('Missing Content', 'event_espresso'),
1824
+				'content' => esc_html__(
1825
+					'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.)',
1826
+					'event_espresso'
1827
+				),
1828
+			];
1829
+			$help_content = $this->_set_help_popup_content($help_array);
1830
+		}
1831
+		// let's setup the trigger
1832
+		$content = '<a class="ee-dialog" href="?height='
1833
+				   . esc_attr($dimensions[0])
1834
+				   . '&width='
1835
+				   . esc_attr($dimensions[1])
1836
+				   . '&inlineId='
1837
+				   . esc_attr($trigger_id)
1838
+				   . '" target="_blank"><span class="question ee-help-popup-question"></span></a>';
1839
+		$content .= $help_content;
1840
+		if ($display) {
1841
+			echo wp_kses($content, AllowedTags::getWithFormTags());
1842
+			return '';
1843
+		}
1844
+		return $content;
1845
+	}
1846
+
1847
+
1848
+	/**
1849
+	 * _add_global_screen_options
1850
+	 * Add any extra wp_screen_options within this method using built-in WP functions/methods for doing so.
1851
+	 * This particular method will add_screen_options on ALL EE_Admin Pages
1852
+	 *
1853
+	 * @link   http://chrismarslender.com/wp-tutorials/wordpress-screen-options-tutorial/
1854
+	 *         see also WP_Screen object documents...
1855
+	 * @link   http://codex.wordpress.org/Class_Reference/WP_Screen
1856
+	 * @abstract
1857
+	 * @return void
1858
+	 */
1859
+	private function _add_global_screen_options()
1860
+	{
1861
+	}
1862
+
1863
+
1864
+	/**
1865
+	 * _add_global_feature_pointers
1866
+	 * This method is used for implementing any "feature pointers" (using built-in WP styling js).
1867
+	 * This particular method will implement feature pointers for ALL EE_Admin pages.
1868
+	 * Note: this is just a placeholder for now.  Implementation will come down the road
1869
+	 *
1870
+	 * @see    WP_Internal_Pointers class in wp-admin/includes/template.php for example (its a final class so can't be
1871
+	 *         extended) also see:
1872
+	 * @link   http://eamann.com/tech/wordpress-portland/
1873
+	 * @abstract
1874
+	 * @return void
1875
+	 */
1876
+	private function _add_global_feature_pointers()
1877
+	{
1878
+	}
1879
+
1880
+
1881
+	/**
1882
+	 * load_global_scripts_styles
1883
+	 * The scripts and styles enqueued in here will be loaded on every EE Admin page
1884
+	 *
1885
+	 * @return void
1886
+	 */
1887
+	public function load_global_scripts_styles()
1888
+	{
1889
+		// add debugging styles
1890
+		if (WP_DEBUG) {
1891
+			add_action('admin_head', [$this, 'add_xdebug_style']);
1892
+		}
1893
+		// taking care of metaboxes
1894
+		if (
1895
+			empty($this->_cpt_route)
1896
+			&& (isset($this->_route_config['metaboxes']) || isset($this->_route_config['has_metaboxes']))
1897
+		) {
1898
+			wp_enqueue_script('dashboard');
1899
+		}
1900
+
1901
+		wp_enqueue_script(JqueryAssetManager::JS_HANDLE_JQUERY_UI_TOUCH_PUNCH);
1902
+		wp_enqueue_script(EspressoLegacyAdminAssetManager::JS_HANDLE_EE_ADMIN);
1903
+		// LOCALIZED DATA
1904
+		// localize script for ajax lazy loading
1905
+		wp_localize_script(
1906
+			EspressoLegacyAdminAssetManager::JS_HANDLE_EE_ADMIN,
1907
+			'eeLazyLoadingContainers',
1908
+			apply_filters(
1909
+				'FHEE__EE_Admin_Page_Core__load_global_scripts_styles__loader_containers',
1910
+				['espresso_news_post_box_content']
1911
+			)
1912
+		);
1913
+		StatusChangeNotice::loadAssets();
1914
+
1915
+		add_filter(
1916
+			'admin_body_class',
1917
+			function ($classes) {
1918
+				if (strpos($classes, 'espresso-admin') === false) {
1919
+					$classes .= ' espresso-admin';
1920
+				}
1921
+				return $classes;
1922
+			}
1923
+		);
1924
+	}
1925
+
1926
+
1927
+	/**
1928
+	 *        admin_footer_scripts_eei18n_js_strings
1929
+	 *
1930
+	 * @return        void
1931
+	 */
1932
+	public function admin_footer_scripts_eei18n_js_strings()
1933
+	{
1934
+		EE_Registry::$i18n_js_strings['ajax_url']       = WP_AJAX_URL;
1935
+		EE_Registry::$i18n_js_strings['confirm_delete'] = wp_strip_all_tags(
1936
+			__(
1937
+				'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!!!',
1938
+				'event_espresso'
1939
+			)
1940
+		);
1941
+		EE_Registry::$i18n_js_strings['January']        = wp_strip_all_tags(__('January', 'event_espresso'));
1942
+		EE_Registry::$i18n_js_strings['February']       = wp_strip_all_tags(__('February', 'event_espresso'));
1943
+		EE_Registry::$i18n_js_strings['March']          = wp_strip_all_tags(__('March', 'event_espresso'));
1944
+		EE_Registry::$i18n_js_strings['April']          = wp_strip_all_tags(__('April', 'event_espresso'));
1945
+		EE_Registry::$i18n_js_strings['May']            = wp_strip_all_tags(__('May', 'event_espresso'));
1946
+		EE_Registry::$i18n_js_strings['June']           = wp_strip_all_tags(__('June', 'event_espresso'));
1947
+		EE_Registry::$i18n_js_strings['July']           = wp_strip_all_tags(__('July', 'event_espresso'));
1948
+		EE_Registry::$i18n_js_strings['August']         = wp_strip_all_tags(__('August', 'event_espresso'));
1949
+		EE_Registry::$i18n_js_strings['September']      = wp_strip_all_tags(__('September', 'event_espresso'));
1950
+		EE_Registry::$i18n_js_strings['October']        = wp_strip_all_tags(__('October', 'event_espresso'));
1951
+		EE_Registry::$i18n_js_strings['November']       = wp_strip_all_tags(__('November', 'event_espresso'));
1952
+		EE_Registry::$i18n_js_strings['December']       = wp_strip_all_tags(__('December', 'event_espresso'));
1953
+		EE_Registry::$i18n_js_strings['Jan']            = wp_strip_all_tags(__('Jan', 'event_espresso'));
1954
+		EE_Registry::$i18n_js_strings['Feb']            = wp_strip_all_tags(__('Feb', 'event_espresso'));
1955
+		EE_Registry::$i18n_js_strings['Mar']            = wp_strip_all_tags(__('Mar', 'event_espresso'));
1956
+		EE_Registry::$i18n_js_strings['Apr']            = wp_strip_all_tags(__('Apr', 'event_espresso'));
1957
+		EE_Registry::$i18n_js_strings['May']            = wp_strip_all_tags(__('May', 'event_espresso'));
1958
+		EE_Registry::$i18n_js_strings['Jun']            = wp_strip_all_tags(__('Jun', 'event_espresso'));
1959
+		EE_Registry::$i18n_js_strings['Jul']            = wp_strip_all_tags(__('Jul', 'event_espresso'));
1960
+		EE_Registry::$i18n_js_strings['Aug']            = wp_strip_all_tags(__('Aug', 'event_espresso'));
1961
+		EE_Registry::$i18n_js_strings['Sep']            = wp_strip_all_tags(__('Sep', 'event_espresso'));
1962
+		EE_Registry::$i18n_js_strings['Oct']            = wp_strip_all_tags(__('Oct', 'event_espresso'));
1963
+		EE_Registry::$i18n_js_strings['Nov']            = wp_strip_all_tags(__('Nov', 'event_espresso'));
1964
+		EE_Registry::$i18n_js_strings['Dec']            = wp_strip_all_tags(__('Dec', 'event_espresso'));
1965
+		EE_Registry::$i18n_js_strings['Sunday']         = wp_strip_all_tags(__('Sunday', 'event_espresso'));
1966
+		EE_Registry::$i18n_js_strings['Monday']         = wp_strip_all_tags(__('Monday', 'event_espresso'));
1967
+		EE_Registry::$i18n_js_strings['Tuesday']        = wp_strip_all_tags(__('Tuesday', 'event_espresso'));
1968
+		EE_Registry::$i18n_js_strings['Wednesday']      = wp_strip_all_tags(__('Wednesday', 'event_espresso'));
1969
+		EE_Registry::$i18n_js_strings['Thursday']       = wp_strip_all_tags(__('Thursday', 'event_espresso'));
1970
+		EE_Registry::$i18n_js_strings['Friday']         = wp_strip_all_tags(__('Friday', 'event_espresso'));
1971
+		EE_Registry::$i18n_js_strings['Saturday']       = wp_strip_all_tags(__('Saturday', 'event_espresso'));
1972
+		EE_Registry::$i18n_js_strings['Sun']            = wp_strip_all_tags(__('Sun', 'event_espresso'));
1973
+		EE_Registry::$i18n_js_strings['Mon']            = wp_strip_all_tags(__('Mon', 'event_espresso'));
1974
+		EE_Registry::$i18n_js_strings['Tue']            = wp_strip_all_tags(__('Tue', 'event_espresso'));
1975
+		EE_Registry::$i18n_js_strings['Wed']            = wp_strip_all_tags(__('Wed', 'event_espresso'));
1976
+		EE_Registry::$i18n_js_strings['Thu']            = wp_strip_all_tags(__('Thu', 'event_espresso'));
1977
+		EE_Registry::$i18n_js_strings['Fri']            = wp_strip_all_tags(__('Fri', 'event_espresso'));
1978
+		EE_Registry::$i18n_js_strings['Sat']            = wp_strip_all_tags(__('Sat', 'event_espresso'));
1979
+	}
1980
+
1981
+
1982
+	/**
1983
+	 *        load enhanced xdebug styles for ppl with failing eyesight
1984
+	 *
1985
+	 * @return        void
1986
+	 */
1987
+	public function add_xdebug_style()
1988
+	{
1989
+		echo '<style>.xdebug-error { font-size:1.5em; }</style>';
1990
+	}
1991
+
1992
+
1993
+	/************************/
1994
+	/** LIST TABLE METHODS **/
1995
+	/************************/
1996
+	/**
1997
+	 * this sets up the list table if the current view requires it.
1998
+	 *
1999
+	 * @return void
2000
+	 * @throws EE_Error
2001
+	 * @throws InvalidArgumentException
2002
+	 * @throws InvalidDataTypeException
2003
+	 * @throws InvalidInterfaceException
2004
+	 */
2005
+	protected function _set_list_table()
2006
+	{
2007
+		// first is this a list_table view?
2008
+		if (! isset($this->_route_config['list_table'])) {
2009
+			return;
2010
+		} //not a list_table view so get out.
2011
+		// list table functions are per view specific (because some admin pages might have more than one list table!)
2012
+		$list_table_view = '_set_list_table_views_' . $this->_req_action;
2013
+		if (! method_exists($this, $list_table_view) || $this->{$list_table_view}() === false) {
2014
+			// user error msg
2015
+			$error_msg = esc_html__(
2016
+				'An error occurred. The requested list table views could not be found.',
2017
+				'event_espresso'
2018
+			);
2019
+			// developer error msg
2020
+			$error_msg .= '||'
2021
+						  . sprintf(
2022
+							  esc_html__(
2023
+								  '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.',
2024
+								  'event_espresso'
2025
+							  ),
2026
+							  $this->_req_action,
2027
+							  $list_table_view
2028
+						  );
2029
+			throw new EE_Error($error_msg);
2030
+		}
2031
+		// let's provide the ability to filter the views per PAGE AND ROUTE, per PAGE, and globally
2032
+		$this->_views = apply_filters(
2033
+			'FHEE_list_table_views_' . $this->page_slug . '_' . $this->_req_action,
2034
+			$this->_views
2035
+		);
2036
+		$this->_views = apply_filters('FHEE_list_table_views_' . $this->page_slug, $this->_views);
2037
+		$this->_views = apply_filters('FHEE_list_table_views', $this->_views);
2038
+		$this->_set_list_table_view();
2039
+		$this->_set_list_table_object();
2040
+	}
2041
+
2042
+
2043
+	/**
2044
+	 * set current view for List Table
2045
+	 *
2046
+	 * @return void
2047
+	 */
2048
+	protected function _set_list_table_view()
2049
+	{
2050
+		$this->_view = isset($this->_views['in_use']) ? 'in_use' : 'all';
2051
+		$status = $this->request->getRequestParam('status', null, 'key');
2052
+		$this->_view = $status && array_key_exists($status, $this->_views)
2053
+			? $status
2054
+			: $this->_view;
2055
+	}
2056
+
2057
+
2058
+	/**
2059
+	 * _set_list_table_object
2060
+	 * WP_List_Table objects need to be loaded fairly early so automatic stuff WP does is taken care of.
2061
+	 *
2062
+	 * @throws InvalidInterfaceException
2063
+	 * @throws InvalidArgumentException
2064
+	 * @throws InvalidDataTypeException
2065
+	 * @throws EE_Error
2066
+	 * @throws InvalidInterfaceException
2067
+	 */
2068
+	protected function _set_list_table_object()
2069
+	{
2070
+		if (isset($this->_route_config['list_table'])) {
2071
+			if (! class_exists($this->_route_config['list_table'])) {
2072
+				throw new EE_Error(
2073
+					sprintf(
2074
+						esc_html__(
2075
+							'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.',
2076
+							'event_espresso'
2077
+						),
2078
+						$this->_route_config['list_table'],
2079
+						$this->class_name
2080
+					)
2081
+				);
2082
+			}
2083
+			$this->_list_table_object = $this->loader->getShared(
2084
+				$this->_route_config['list_table'],
2085
+				[$this]
2086
+			);
2087
+		}
2088
+	}
2089
+
2090
+
2091
+	/**
2092
+	 * get_list_table_view_RLs - get it? View RL ?? VU-RL???  URL ??
2093
+	 *
2094
+	 * @param array $extra_query_args                     Optional. An array of extra query args to add to the generated
2095
+	 *                                                    urls.  The array should be indexed by the view it is being
2096
+	 *                                                    added to.
2097
+	 * @return array
2098
+	 */
2099
+	public function get_list_table_view_RLs($extra_query_args = [])
2100
+	{
2101
+		do_action('AHEE_log', __FILE__, __FUNCTION__, '');
2102
+		if (empty($this->_views)) {
2103
+			$this->_views = [];
2104
+		}
2105
+		// cycle thru views
2106
+		foreach ($this->_views as $key => $view) {
2107
+			$query_args = [];
2108
+			// check for current view
2109
+			$this->_views[ $key ]['class']               = $this->_view === $view['slug'] ? 'current' : '';
2110
+			$query_args['action']                        = $this->_req_action;
2111
+			$query_args[ $this->_req_action . '_nonce' ] = wp_create_nonce($query_args['action'] . '_nonce');
2112
+			$query_args['status']                        = $view['slug'];
2113
+			// merge any other arguments sent in.
2114
+			if (isset($extra_query_args[ $view['slug'] ])) {
2115
+				foreach ($extra_query_args[ $view['slug'] ] as $extra_query_arg) {
2116
+					$query_args[] = $extra_query_arg;
2117
+				}
2118
+			}
2119
+			$this->_views[ $key ]['url'] = EE_Admin_Page::add_query_args_and_nonce($query_args, $this->_admin_base_url);
2120
+		}
2121
+		return $this->_views;
2122
+	}
2123
+
2124
+
2125
+	/**
2126
+	 * _entries_per_page_dropdown
2127
+	 * generates a dropdown box for selecting the number of visible rows in an admin page list table
2128
+	 *
2129
+	 * @param int $max_entries total number of rows in the table
2130
+	 * @return string
2131
+	 * @todo   : Note: ideally this should be added to the screen options dropdown as that would be consistent with how
2132
+	 *         WP does it.
2133
+	 */
2134
+	protected function _entries_per_page_dropdown($max_entries = 0)
2135
+	{
2136
+		do_action('AHEE_log', __FILE__, __FUNCTION__, '');
2137
+		$values   = [10, 25, 50, 100];
2138
+		$per_page = $this->request->getRequestParam('per_page', 10, 'int');
2139
+		if ($max_entries) {
2140
+			$values[] = $max_entries;
2141
+			sort($values);
2142
+		}
2143
+		$entries_per_page_dropdown = '
2144 2144
 			<div id="entries-per-page-dv" class="alignleft actions">
2145 2145
 				<label class="hide-if-no-js">
2146 2146
 					Show
2147 2147
 					<select id="entries-per-page-slct" name="entries-per-page-slct">';
2148
-        foreach ($values as $value) {
2149
-            if ($value < $max_entries) {
2150
-                $selected                  = $value === $per_page ? ' selected="' . $per_page . '"' : '';
2151
-                $entries_per_page_dropdown .= '
2148
+		foreach ($values as $value) {
2149
+			if ($value < $max_entries) {
2150
+				$selected                  = $value === $per_page ? ' selected="' . $per_page . '"' : '';
2151
+				$entries_per_page_dropdown .= '
2152 2152
 						<option value="' . $value . '"' . $selected . '>' . $value . '&nbsp;&nbsp;</option>';
2153
-            }
2154
-        }
2155
-        $selected                  = $max_entries === $per_page ? ' selected="' . $per_page . '"' : '';
2156
-        $entries_per_page_dropdown .= '
2153
+			}
2154
+		}
2155
+		$selected                  = $max_entries === $per_page ? ' selected="' . $per_page . '"' : '';
2156
+		$entries_per_page_dropdown .= '
2157 2157
 						<option value="' . $max_entries . '"' . $selected . '>All&nbsp;&nbsp;</option>';
2158
-        $entries_per_page_dropdown .= '
2158
+		$entries_per_page_dropdown .= '
2159 2159
 					</select>
2160 2160
 					entries
2161 2161
 				</label>
2162 2162
 				<input id="entries-per-page-btn" class="button button--secondary" type="submit" value="Go" >
2163 2163
 			</div>
2164 2164
 		';
2165
-        return $entries_per_page_dropdown;
2166
-    }
2167
-
2168
-
2169
-    /**
2170
-     *        _set_search_attributes
2171
-     *
2172
-     * @return        void
2173
-     */
2174
-    public function _set_search_attributes()
2175
-    {
2176
-        $this->_template_args['search']['btn_label'] = sprintf(
2177
-            esc_html__('Search %s', 'event_espresso'),
2178
-            empty($this->_search_btn_label) ? $this->page_label
2179
-                : $this->_search_btn_label
2180
-        );
2181
-        $this->_template_args['search']['callback']  = 'search_' . $this->page_slug;
2182
-    }
2183
-
2184
-
2185
-
2186
-    /*** END LIST TABLE METHODS **/
2187
-
2188
-
2189
-    /**
2190
-     * _add_registered_metaboxes
2191
-     *  this loads any registered metaboxes via the 'metaboxes' index in the _page_config property array.
2192
-     *
2193
-     * @link   http://codex.wordpress.org/Function_Reference/add_meta_box
2194
-     * @return void
2195
-     * @throws EE_Error
2196
-     */
2197
-    private function _add_registered_meta_boxes()
2198
-    {
2199
-        do_action('AHEE_log', __FILE__, __FUNCTION__, '');
2200
-        // we only add meta boxes if the page_route calls for it
2201
-        if (
2202
-            is_array($this->_route_config) && isset($this->_route_config['metaboxes'])
2203
-            && is_array(
2204
-                $this->_route_config['metaboxes']
2205
-            )
2206
-        ) {
2207
-            // this simply loops through the callbacks provided
2208
-            // and checks if there is a corresponding callback registered by the child
2209
-            // if there is then we go ahead and process the metabox loader.
2210
-            foreach ($this->_route_config['metaboxes'] as $metabox_callback) {
2211
-                // first check for Closures
2212
-                if ($metabox_callback instanceof Closure) {
2213
-                    $result = $metabox_callback();
2214
-                } elseif (is_array($metabox_callback) && isset($metabox_callback[0], $metabox_callback[1])) {
2215
-                    $result = call_user_func([$metabox_callback[0], $metabox_callback[1]]);
2216
-                } else {
2217
-                    $result = $this->{$metabox_callback}();
2218
-                }
2219
-                if ($result === false) {
2220
-                    // user error msg
2221
-                    $error_msg = esc_html__(
2222
-                        'An error occurred. The  requested metabox could not be found.',
2223
-                        'event_espresso'
2224
-                    );
2225
-                    // developer error msg
2226
-                    $error_msg .= '||'
2227
-                                  . sprintf(
2228
-                                      esc_html__(
2229
-                                          '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.',
2230
-                                          'event_espresso'
2231
-                                      ),
2232
-                                      $metabox_callback
2233
-                                  );
2234
-                    throw new EE_Error($error_msg);
2235
-                }
2236
-            }
2237
-        }
2238
-    }
2239
-
2240
-
2241
-    /**
2242
-     * _add_screen_columns
2243
-     * This will check the _page_config array and if there is "columns" key index indicated, we'll set the template as
2244
-     * the dynamic column template and we'll setup the column options for the page.
2245
-     *
2246
-     * @return void
2247
-     */
2248
-    private function _add_screen_columns()
2249
-    {
2250
-        if (
2251
-            is_array($this->_route_config)
2252
-            && isset($this->_route_config['columns'])
2253
-            && is_array($this->_route_config['columns'])
2254
-            && count($this->_route_config['columns']) === 2
2255
-        ) {
2256
-            add_screen_option(
2257
-                'layout_columns',
2258
-                [
2259
-                    'max'     => (int) $this->_route_config['columns'][0],
2260
-                    'default' => (int) $this->_route_config['columns'][1],
2261
-                ]
2262
-            );
2263
-            $this->_template_args['num_columns']                 = $this->_route_config['columns'][0];
2264
-            $screen_id                                           = $this->_current_screen->id;
2265
-            $screen_columns                                      = (int) get_user_option("screen_layout_{$screen_id}");
2266
-            $total_columns                                       = ! empty($screen_columns)
2267
-                ? $screen_columns
2268
-                : $this->_route_config['columns'][1];
2269
-            $this->_template_args['current_screen_widget_class'] = 'columns-' . $total_columns;
2270
-            $this->_template_args['current_page']                = $this->_wp_page_slug;
2271
-            $this->_template_args['screen']                      = $this->_current_screen;
2272
-            $this->_column_template_path                         = EE_ADMIN_TEMPLATE
2273
-                                                                   . 'admin_details_metabox_column_wrapper.template.php';
2274
-            // finally if we don't have has_metaboxes set in the route config
2275
-            // let's make sure it IS set other wise the necessary hidden fields for this won't be loaded.
2276
-            $this->_route_config['has_metaboxes'] = true;
2277
-        }
2278
-    }
2279
-
2280
-
2281
-
2282
-    /** GLOBALLY AVAILABLE METABOXES **/
2283
-
2284
-
2285
-    /**
2286
-     * In this section we put any globally available EE metaboxes for all EE Admin pages.  They are called by simply
2287
-     * referencing the callback in the _page_config array property.  This way you can be very specific about what pages
2288
-     * these get loaded on.
2289
-     */
2290
-    private function _espresso_news_post_box()
2291
-    {
2292
-        $news_box_title = apply_filters(
2293
-            'FHEE__EE_Admin_Page___espresso_news_post_box__news_box_title',
2294
-            esc_html__('New @ Event Espresso', 'event_espresso')
2295
-        );
2296
-        $this->addMetaBox(
2297
-            'espresso_news_post_box',
2298
-            $news_box_title,
2299
-            [
2300
-                $this,
2301
-                'espresso_news_post_box',
2302
-            ],
2303
-            $this->_wp_page_slug,
2304
-            'side',
2305
-            'low'
2306
-        );
2307
-    }
2308
-
2309
-
2310
-    /**
2311
-     * Code for setting up espresso ratings request metabox.
2312
-     */
2313
-    protected function _espresso_ratings_request()
2314
-    {
2315
-        if (! apply_filters('FHEE_show_ratings_request_meta_box', true)) {
2316
-            return;
2317
-        }
2318
-        $ratings_box_title = apply_filters(
2319
-            'FHEE__EE_Admin_Page___espresso_news_post_box__news_box_title',
2320
-            esc_html__('Keep Event Espresso Decaf Free', 'event_espresso')
2321
-        );
2322
-        $this->addMetaBox(
2323
-            'espresso_ratings_request',
2324
-            $ratings_box_title,
2325
-            [
2326
-                $this,
2327
-                'espresso_ratings_request',
2328
-            ],
2329
-            $this->_wp_page_slug,
2330
-            'side'
2331
-        );
2332
-    }
2333
-
2334
-
2335
-    /**
2336
-     * Code for setting up espresso ratings request metabox content.
2337
-     *
2338
-     * @throws DomainException
2339
-     */
2340
-    public function espresso_ratings_request()
2341
-    {
2342
-        EEH_Template::display_template(EE_ADMIN_TEMPLATE . 'espresso_ratings_request_content.template.php');
2343
-    }
2344
-
2345
-
2346
-    public static function cached_rss_display($rss_id, $url)
2347
-    {
2348
-        $loading   = '<p class="widget-loading hide-if-no-js">'
2349
-                     . esc_html__('Loading&#8230;', 'event_espresso')
2350
-                     . '</p><p class="hide-if-js">'
2351
-                     . esc_html__('This widget requires JavaScript.', 'event_espresso')
2352
-                     . '</p>';
2353
-        $pre       = '<div class="espresso-rss-display">' . "\n\t";
2354
-        $pre       .= '<span id="' . esc_attr($rss_id) . '_url" class="hidden">' . esc_url_raw($url) . '</span>';
2355
-        $post      = '</div>' . "\n";
2356
-        $cache_key = 'ee_rss_' . md5($rss_id);
2357
-        $output    = get_transient($cache_key);
2358
-        if ($output !== false) {
2359
-            echo wp_kses($pre . $output . $post, AllowedTags::getWithFormTags());
2360
-            return true;
2361
-        }
2362
-        if (! (defined('DOING_AJAX') && DOING_AJAX)) {
2363
-            echo wp_kses($pre . $loading . $post, AllowedTags::getWithFormTags());
2364
-            return false;
2365
-        }
2366
-        ob_start();
2367
-        wp_widget_rss_output($url, ['show_date' => 0, 'items' => 5]);
2368
-        set_transient($cache_key, ob_get_flush(), 12 * HOUR_IN_SECONDS);
2369
-        return true;
2370
-    }
2371
-
2372
-
2373
-    public function espresso_news_post_box()
2374
-    {
2375
-        ?>
2165
+		return $entries_per_page_dropdown;
2166
+	}
2167
+
2168
+
2169
+	/**
2170
+	 *        _set_search_attributes
2171
+	 *
2172
+	 * @return        void
2173
+	 */
2174
+	public function _set_search_attributes()
2175
+	{
2176
+		$this->_template_args['search']['btn_label'] = sprintf(
2177
+			esc_html__('Search %s', 'event_espresso'),
2178
+			empty($this->_search_btn_label) ? $this->page_label
2179
+				: $this->_search_btn_label
2180
+		);
2181
+		$this->_template_args['search']['callback']  = 'search_' . $this->page_slug;
2182
+	}
2183
+
2184
+
2185
+
2186
+	/*** END LIST TABLE METHODS **/
2187
+
2188
+
2189
+	/**
2190
+	 * _add_registered_metaboxes
2191
+	 *  this loads any registered metaboxes via the 'metaboxes' index in the _page_config property array.
2192
+	 *
2193
+	 * @link   http://codex.wordpress.org/Function_Reference/add_meta_box
2194
+	 * @return void
2195
+	 * @throws EE_Error
2196
+	 */
2197
+	private function _add_registered_meta_boxes()
2198
+	{
2199
+		do_action('AHEE_log', __FILE__, __FUNCTION__, '');
2200
+		// we only add meta boxes if the page_route calls for it
2201
+		if (
2202
+			is_array($this->_route_config) && isset($this->_route_config['metaboxes'])
2203
+			&& is_array(
2204
+				$this->_route_config['metaboxes']
2205
+			)
2206
+		) {
2207
+			// this simply loops through the callbacks provided
2208
+			// and checks if there is a corresponding callback registered by the child
2209
+			// if there is then we go ahead and process the metabox loader.
2210
+			foreach ($this->_route_config['metaboxes'] as $metabox_callback) {
2211
+				// first check for Closures
2212
+				if ($metabox_callback instanceof Closure) {
2213
+					$result = $metabox_callback();
2214
+				} elseif (is_array($metabox_callback) && isset($metabox_callback[0], $metabox_callback[1])) {
2215
+					$result = call_user_func([$metabox_callback[0], $metabox_callback[1]]);
2216
+				} else {
2217
+					$result = $this->{$metabox_callback}();
2218
+				}
2219
+				if ($result === false) {
2220
+					// user error msg
2221
+					$error_msg = esc_html__(
2222
+						'An error occurred. The  requested metabox could not be found.',
2223
+						'event_espresso'
2224
+					);
2225
+					// developer error msg
2226
+					$error_msg .= '||'
2227
+								  . sprintf(
2228
+									  esc_html__(
2229
+										  '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.',
2230
+										  'event_espresso'
2231
+									  ),
2232
+									  $metabox_callback
2233
+								  );
2234
+					throw new EE_Error($error_msg);
2235
+				}
2236
+			}
2237
+		}
2238
+	}
2239
+
2240
+
2241
+	/**
2242
+	 * _add_screen_columns
2243
+	 * This will check the _page_config array and if there is "columns" key index indicated, we'll set the template as
2244
+	 * the dynamic column template and we'll setup the column options for the page.
2245
+	 *
2246
+	 * @return void
2247
+	 */
2248
+	private function _add_screen_columns()
2249
+	{
2250
+		if (
2251
+			is_array($this->_route_config)
2252
+			&& isset($this->_route_config['columns'])
2253
+			&& is_array($this->_route_config['columns'])
2254
+			&& count($this->_route_config['columns']) === 2
2255
+		) {
2256
+			add_screen_option(
2257
+				'layout_columns',
2258
+				[
2259
+					'max'     => (int) $this->_route_config['columns'][0],
2260
+					'default' => (int) $this->_route_config['columns'][1],
2261
+				]
2262
+			);
2263
+			$this->_template_args['num_columns']                 = $this->_route_config['columns'][0];
2264
+			$screen_id                                           = $this->_current_screen->id;
2265
+			$screen_columns                                      = (int) get_user_option("screen_layout_{$screen_id}");
2266
+			$total_columns                                       = ! empty($screen_columns)
2267
+				? $screen_columns
2268
+				: $this->_route_config['columns'][1];
2269
+			$this->_template_args['current_screen_widget_class'] = 'columns-' . $total_columns;
2270
+			$this->_template_args['current_page']                = $this->_wp_page_slug;
2271
+			$this->_template_args['screen']                      = $this->_current_screen;
2272
+			$this->_column_template_path                         = EE_ADMIN_TEMPLATE
2273
+																   . 'admin_details_metabox_column_wrapper.template.php';
2274
+			// finally if we don't have has_metaboxes set in the route config
2275
+			// let's make sure it IS set other wise the necessary hidden fields for this won't be loaded.
2276
+			$this->_route_config['has_metaboxes'] = true;
2277
+		}
2278
+	}
2279
+
2280
+
2281
+
2282
+	/** GLOBALLY AVAILABLE METABOXES **/
2283
+
2284
+
2285
+	/**
2286
+	 * In this section we put any globally available EE metaboxes for all EE Admin pages.  They are called by simply
2287
+	 * referencing the callback in the _page_config array property.  This way you can be very specific about what pages
2288
+	 * these get loaded on.
2289
+	 */
2290
+	private function _espresso_news_post_box()
2291
+	{
2292
+		$news_box_title = apply_filters(
2293
+			'FHEE__EE_Admin_Page___espresso_news_post_box__news_box_title',
2294
+			esc_html__('New @ Event Espresso', 'event_espresso')
2295
+		);
2296
+		$this->addMetaBox(
2297
+			'espresso_news_post_box',
2298
+			$news_box_title,
2299
+			[
2300
+				$this,
2301
+				'espresso_news_post_box',
2302
+			],
2303
+			$this->_wp_page_slug,
2304
+			'side',
2305
+			'low'
2306
+		);
2307
+	}
2308
+
2309
+
2310
+	/**
2311
+	 * Code for setting up espresso ratings request metabox.
2312
+	 */
2313
+	protected function _espresso_ratings_request()
2314
+	{
2315
+		if (! apply_filters('FHEE_show_ratings_request_meta_box', true)) {
2316
+			return;
2317
+		}
2318
+		$ratings_box_title = apply_filters(
2319
+			'FHEE__EE_Admin_Page___espresso_news_post_box__news_box_title',
2320
+			esc_html__('Keep Event Espresso Decaf Free', 'event_espresso')
2321
+		);
2322
+		$this->addMetaBox(
2323
+			'espresso_ratings_request',
2324
+			$ratings_box_title,
2325
+			[
2326
+				$this,
2327
+				'espresso_ratings_request',
2328
+			],
2329
+			$this->_wp_page_slug,
2330
+			'side'
2331
+		);
2332
+	}
2333
+
2334
+
2335
+	/**
2336
+	 * Code for setting up espresso ratings request metabox content.
2337
+	 *
2338
+	 * @throws DomainException
2339
+	 */
2340
+	public function espresso_ratings_request()
2341
+	{
2342
+		EEH_Template::display_template(EE_ADMIN_TEMPLATE . 'espresso_ratings_request_content.template.php');
2343
+	}
2344
+
2345
+
2346
+	public static function cached_rss_display($rss_id, $url)
2347
+	{
2348
+		$loading   = '<p class="widget-loading hide-if-no-js">'
2349
+					 . esc_html__('Loading&#8230;', 'event_espresso')
2350
+					 . '</p><p class="hide-if-js">'
2351
+					 . esc_html__('This widget requires JavaScript.', 'event_espresso')
2352
+					 . '</p>';
2353
+		$pre       = '<div class="espresso-rss-display">' . "\n\t";
2354
+		$pre       .= '<span id="' . esc_attr($rss_id) . '_url" class="hidden">' . esc_url_raw($url) . '</span>';
2355
+		$post      = '</div>' . "\n";
2356
+		$cache_key = 'ee_rss_' . md5($rss_id);
2357
+		$output    = get_transient($cache_key);
2358
+		if ($output !== false) {
2359
+			echo wp_kses($pre . $output . $post, AllowedTags::getWithFormTags());
2360
+			return true;
2361
+		}
2362
+		if (! (defined('DOING_AJAX') && DOING_AJAX)) {
2363
+			echo wp_kses($pre . $loading . $post, AllowedTags::getWithFormTags());
2364
+			return false;
2365
+		}
2366
+		ob_start();
2367
+		wp_widget_rss_output($url, ['show_date' => 0, 'items' => 5]);
2368
+		set_transient($cache_key, ob_get_flush(), 12 * HOUR_IN_SECONDS);
2369
+		return true;
2370
+	}
2371
+
2372
+
2373
+	public function espresso_news_post_box()
2374
+	{
2375
+		?>
2376 2376
         <div class="padding">
2377 2377
             <div id="espresso_news_post_box_content" class="infolinks">
2378 2378
                 <?php
2379
-                // Get RSS Feed(s)
2380
-                EE_Admin_Page::cached_rss_display(
2381
-                    'espresso_news_post_box_content',
2382
-                    esc_url_raw(
2383
-                        apply_filters(
2384
-                            'FHEE__EE_Admin_Page__espresso_news_post_box__feed_url',
2385
-                            'https://eventespresso.com/feed/'
2386
-                        )
2387
-                    )
2388
-                );
2389
-                ?>
2379
+				// Get RSS Feed(s)
2380
+				EE_Admin_Page::cached_rss_display(
2381
+					'espresso_news_post_box_content',
2382
+					esc_url_raw(
2383
+						apply_filters(
2384
+							'FHEE__EE_Admin_Page__espresso_news_post_box__feed_url',
2385
+							'https://eventespresso.com/feed/'
2386
+						)
2387
+					)
2388
+				);
2389
+				?>
2390 2390
             </div>
2391 2391
             <?php do_action('AHEE__EE_Admin_Page__espresso_news_post_box__after_content'); ?>
2392 2392
         </div>
2393 2393
         <?php
2394
-    }
2395
-
2396
-
2397
-    private function _espresso_links_post_box()
2398
-    {
2399
-        // Hiding until we actually have content to put in here...
2400
-        // $this->addMetaBox('espresso_links_post_box', esc_html__('Helpful Plugin Links', 'event_espresso'), array( $this, 'espresso_links_post_box'), $this->_wp_page_slug, 'side');
2401
-    }
2402
-
2403
-
2404
-    public function espresso_links_post_box()
2405
-    {
2406
-        // Hiding until we actually have content to put in here...
2407
-        // EEH_Template::display_template(
2408
-        //     EE_ADMIN_TEMPLATE . 'admin_general_metabox_contents_espresso_links.template.php'
2409
-        // );
2410
-    }
2411
-
2412
-
2413
-    protected function _espresso_sponsors_post_box()
2414
-    {
2415
-        if (apply_filters('FHEE_show_sponsors_meta_box', true)) {
2416
-            $this->addMetaBox(
2417
-                'espresso_sponsors_post_box',
2418
-                esc_html__('Event Espresso Highlights', 'event_espresso'),
2419
-                [$this, 'espresso_sponsors_post_box'],
2420
-                $this->_wp_page_slug,
2421
-                'side'
2422
-            );
2423
-        }
2424
-    }
2425
-
2426
-
2427
-    public function espresso_sponsors_post_box()
2428
-    {
2429
-        EEH_Template::display_template(
2430
-            EE_ADMIN_TEMPLATE . 'admin_general_metabox_contents_espresso_sponsors.template.php'
2431
-        );
2432
-    }
2433
-
2434
-
2435
-    private function _publish_post_box()
2436
-    {
2437
-        $meta_box_ref = 'espresso_' . $this->page_slug . '_editor_overview';
2438
-        // if there is a array('label' => array('publishbox' => 'some title') ) present in the _page_config array
2439
-        // then we'll use that for the metabox label.
2440
-        // Otherwise we'll just use publish (publishbox itself could be an array of labels indexed by routes)
2441
-        if (! empty($this->_labels['publishbox'])) {
2442
-            $box_label = is_array($this->_labels['publishbox']) ? $this->_labels['publishbox'][ $this->_req_action ]
2443
-                : $this->_labels['publishbox'];
2444
-        } else {
2445
-            $box_label = esc_html__('Publish', 'event_espresso');
2446
-        }
2447
-        $box_label = apply_filters(
2448
-            'FHEE__EE_Admin_Page___publish_post_box__box_label',
2449
-            $box_label,
2450
-            $this->_req_action,
2451
-            $this
2452
-        );
2453
-        $this->addMetaBox(
2454
-            $meta_box_ref,
2455
-            $box_label,
2456
-            [$this, 'editor_overview'],
2457
-            $this->_current_screen->id,
2458
-            'side',
2459
-            'high'
2460
-        );
2461
-    }
2462
-
2463
-
2464
-    public function editor_overview()
2465
-    {
2466
-        // if we have extra content set let's add it in if not make sure its empty
2467
-        $this->_template_args['publish_box_extra_content'] = isset($this->_template_args['publish_box_extra_content'])
2468
-            ? $this->_template_args['publish_box_extra_content']
2469
-            : '';
2470
-        echo EEH_Template::display_template(
2471
-            EE_ADMIN_TEMPLATE . 'admin_details_publish_metabox.template.php',
2472
-            $this->_template_args,
2473
-            true
2474
-        );
2475
-    }
2476
-
2477
-
2478
-    /** end of globally available metaboxes section **/
2479
-
2480
-
2481
-    /**
2482
-     * Public wrapper for the protected method.  Allows plugins/addons to externally call the
2483
-     * protected method.
2484
-     *
2485
-     * @param string $name
2486
-     * @param int    $id
2487
-     * @param bool   $delete
2488
-     * @param string $save_close_redirect_URL
2489
-     * @param bool   $both_btns
2490
-     * @throws EE_Error
2491
-     * @throws InvalidArgumentException
2492
-     * @throws InvalidDataTypeException
2493
-     * @throws InvalidInterfaceException
2494
-     * @see   $this->_set_publish_post_box_vars for param details
2495
-     * @since 4.6.0
2496
-     */
2497
-    public function set_publish_post_box_vars(
2498
-        $name = '',
2499
-        $id = 0,
2500
-        $delete = false,
2501
-        $save_close_redirect_URL = '',
2502
-        $both_btns = true
2503
-    ) {
2504
-        $this->_set_publish_post_box_vars(
2505
-            $name,
2506
-            $id,
2507
-            $delete,
2508
-            $save_close_redirect_URL,
2509
-            $both_btns
2510
-        );
2511
-    }
2512
-
2513
-
2514
-    /**
2515
-     * Sets the _template_args arguments used by the _publish_post_box shortcut
2516
-     * Note: currently there is no validation for this.  However if you want the delete button, the
2517
-     * save, and save and close buttons to work properly, then you will want to include a
2518
-     * values for the name and id arguments.
2519
-     *
2520
-     * @param string  $name                       key used for the action ID (i.e. event_id)
2521
-     * @param int     $id                         id attached to the item published
2522
-     * @param string  $delete                     page route callback for the delete action
2523
-     * @param string  $save_close_redirect_URL    custom URL to redirect to after Save & Close has been completed
2524
-     * @param boolean $both_btns                  whether to display BOTH the "Save & Close" and "Save" buttons or just
2525
-     *                                            the Save button
2526
-     * @throws EE_Error
2527
-     * @throws InvalidArgumentException
2528
-     * @throws InvalidDataTypeException
2529
-     * @throws InvalidInterfaceException
2530
-     * @todo  Add in validation for name/id arguments.
2531
-     */
2532
-    protected function _set_publish_post_box_vars(
2533
-        $name = '',
2534
-        $id = 0,
2535
-        $delete = '',
2536
-        $save_close_redirect_URL = '',
2537
-        $both_btns = true
2538
-    ) {
2539
-        // if Save & Close, use a custom redirect URL or default to the main page?
2540
-        $save_close_redirect_URL = ! empty($save_close_redirect_URL)
2541
-            ? $save_close_redirect_URL
2542
-            : $this->_admin_base_url;
2543
-        // create the Save & Close and Save buttons
2544
-        $this->_set_save_buttons($both_btns, [], [], $save_close_redirect_URL);
2545
-        // if we have extra content set let's add it in if not make sure its empty
2546
-        $this->_template_args['publish_box_extra_content'] = $this->_template_args['publish_box_extra_content'] ?? '';
2547
-        $delete_link = '';
2548
-        if ($delete && ! empty($id)) {
2549
-            // make sure we have a default if just true is sent.
2550
-            $delete           = ! empty($delete) ? $delete : 'delete';
2551
-            $delete_link      = $this->get_action_link_or_button(
2552
-                $delete,
2553
-                $delete,
2554
-                [$name => $id],
2555
-                'submitdelete deletion button button--outline button--caution'
2556
-            );
2557
-        }
2558
-        $this->_template_args['publish_delete_link'] = $delete_link;
2559
-        if (! empty($name) && ! empty($id)) {
2560
-            $hidden_field_arr[ $name ] = [
2561
-                'type'  => 'hidden',
2562
-                'value' => $id,
2563
-            ];
2564
-            $hf                        = $this->_generate_admin_form_fields($hidden_field_arr, 'array');
2565
-        } else {
2566
-            $hf = '';
2567
-        }
2568
-        // add hidden field
2569
-        $this->_template_args['publish_hidden_fields'] = is_array($hf) && ! empty($name)
2570
-            ? $hf[ $name ]['field']
2571
-            : $hf;
2572
-    }
2573
-
2574
-
2575
-    /**
2576
-     * displays an error message to ppl who have javascript disabled
2577
-     *
2578
-     * @return void
2579
-     */
2580
-    private function _display_no_javascript_warning()
2581
-    {
2582
-        ?>
2394
+	}
2395
+
2396
+
2397
+	private function _espresso_links_post_box()
2398
+	{
2399
+		// Hiding until we actually have content to put in here...
2400
+		// $this->addMetaBox('espresso_links_post_box', esc_html__('Helpful Plugin Links', 'event_espresso'), array( $this, 'espresso_links_post_box'), $this->_wp_page_slug, 'side');
2401
+	}
2402
+
2403
+
2404
+	public function espresso_links_post_box()
2405
+	{
2406
+		// Hiding until we actually have content to put in here...
2407
+		// EEH_Template::display_template(
2408
+		//     EE_ADMIN_TEMPLATE . 'admin_general_metabox_contents_espresso_links.template.php'
2409
+		// );
2410
+	}
2411
+
2412
+
2413
+	protected function _espresso_sponsors_post_box()
2414
+	{
2415
+		if (apply_filters('FHEE_show_sponsors_meta_box', true)) {
2416
+			$this->addMetaBox(
2417
+				'espresso_sponsors_post_box',
2418
+				esc_html__('Event Espresso Highlights', 'event_espresso'),
2419
+				[$this, 'espresso_sponsors_post_box'],
2420
+				$this->_wp_page_slug,
2421
+				'side'
2422
+			);
2423
+		}
2424
+	}
2425
+
2426
+
2427
+	public function espresso_sponsors_post_box()
2428
+	{
2429
+		EEH_Template::display_template(
2430
+			EE_ADMIN_TEMPLATE . 'admin_general_metabox_contents_espresso_sponsors.template.php'
2431
+		);
2432
+	}
2433
+
2434
+
2435
+	private function _publish_post_box()
2436
+	{
2437
+		$meta_box_ref = 'espresso_' . $this->page_slug . '_editor_overview';
2438
+		// if there is a array('label' => array('publishbox' => 'some title') ) present in the _page_config array
2439
+		// then we'll use that for the metabox label.
2440
+		// Otherwise we'll just use publish (publishbox itself could be an array of labels indexed by routes)
2441
+		if (! empty($this->_labels['publishbox'])) {
2442
+			$box_label = is_array($this->_labels['publishbox']) ? $this->_labels['publishbox'][ $this->_req_action ]
2443
+				: $this->_labels['publishbox'];
2444
+		} else {
2445
+			$box_label = esc_html__('Publish', 'event_espresso');
2446
+		}
2447
+		$box_label = apply_filters(
2448
+			'FHEE__EE_Admin_Page___publish_post_box__box_label',
2449
+			$box_label,
2450
+			$this->_req_action,
2451
+			$this
2452
+		);
2453
+		$this->addMetaBox(
2454
+			$meta_box_ref,
2455
+			$box_label,
2456
+			[$this, 'editor_overview'],
2457
+			$this->_current_screen->id,
2458
+			'side',
2459
+			'high'
2460
+		);
2461
+	}
2462
+
2463
+
2464
+	public function editor_overview()
2465
+	{
2466
+		// if we have extra content set let's add it in if not make sure its empty
2467
+		$this->_template_args['publish_box_extra_content'] = isset($this->_template_args['publish_box_extra_content'])
2468
+			? $this->_template_args['publish_box_extra_content']
2469
+			: '';
2470
+		echo EEH_Template::display_template(
2471
+			EE_ADMIN_TEMPLATE . 'admin_details_publish_metabox.template.php',
2472
+			$this->_template_args,
2473
+			true
2474
+		);
2475
+	}
2476
+
2477
+
2478
+	/** end of globally available metaboxes section **/
2479
+
2480
+
2481
+	/**
2482
+	 * Public wrapper for the protected method.  Allows plugins/addons to externally call the
2483
+	 * protected method.
2484
+	 *
2485
+	 * @param string $name
2486
+	 * @param int    $id
2487
+	 * @param bool   $delete
2488
+	 * @param string $save_close_redirect_URL
2489
+	 * @param bool   $both_btns
2490
+	 * @throws EE_Error
2491
+	 * @throws InvalidArgumentException
2492
+	 * @throws InvalidDataTypeException
2493
+	 * @throws InvalidInterfaceException
2494
+	 * @see   $this->_set_publish_post_box_vars for param details
2495
+	 * @since 4.6.0
2496
+	 */
2497
+	public function set_publish_post_box_vars(
2498
+		$name = '',
2499
+		$id = 0,
2500
+		$delete = false,
2501
+		$save_close_redirect_URL = '',
2502
+		$both_btns = true
2503
+	) {
2504
+		$this->_set_publish_post_box_vars(
2505
+			$name,
2506
+			$id,
2507
+			$delete,
2508
+			$save_close_redirect_URL,
2509
+			$both_btns
2510
+		);
2511
+	}
2512
+
2513
+
2514
+	/**
2515
+	 * Sets the _template_args arguments used by the _publish_post_box shortcut
2516
+	 * Note: currently there is no validation for this.  However if you want the delete button, the
2517
+	 * save, and save and close buttons to work properly, then you will want to include a
2518
+	 * values for the name and id arguments.
2519
+	 *
2520
+	 * @param string  $name                       key used for the action ID (i.e. event_id)
2521
+	 * @param int     $id                         id attached to the item published
2522
+	 * @param string  $delete                     page route callback for the delete action
2523
+	 * @param string  $save_close_redirect_URL    custom URL to redirect to after Save & Close has been completed
2524
+	 * @param boolean $both_btns                  whether to display BOTH the "Save & Close" and "Save" buttons or just
2525
+	 *                                            the Save button
2526
+	 * @throws EE_Error
2527
+	 * @throws InvalidArgumentException
2528
+	 * @throws InvalidDataTypeException
2529
+	 * @throws InvalidInterfaceException
2530
+	 * @todo  Add in validation for name/id arguments.
2531
+	 */
2532
+	protected function _set_publish_post_box_vars(
2533
+		$name = '',
2534
+		$id = 0,
2535
+		$delete = '',
2536
+		$save_close_redirect_URL = '',
2537
+		$both_btns = true
2538
+	) {
2539
+		// if Save & Close, use a custom redirect URL or default to the main page?
2540
+		$save_close_redirect_URL = ! empty($save_close_redirect_URL)
2541
+			? $save_close_redirect_URL
2542
+			: $this->_admin_base_url;
2543
+		// create the Save & Close and Save buttons
2544
+		$this->_set_save_buttons($both_btns, [], [], $save_close_redirect_URL);
2545
+		// if we have extra content set let's add it in if not make sure its empty
2546
+		$this->_template_args['publish_box_extra_content'] = $this->_template_args['publish_box_extra_content'] ?? '';
2547
+		$delete_link = '';
2548
+		if ($delete && ! empty($id)) {
2549
+			// make sure we have a default if just true is sent.
2550
+			$delete           = ! empty($delete) ? $delete : 'delete';
2551
+			$delete_link      = $this->get_action_link_or_button(
2552
+				$delete,
2553
+				$delete,
2554
+				[$name => $id],
2555
+				'submitdelete deletion button button--outline button--caution'
2556
+			);
2557
+		}
2558
+		$this->_template_args['publish_delete_link'] = $delete_link;
2559
+		if (! empty($name) && ! empty($id)) {
2560
+			$hidden_field_arr[ $name ] = [
2561
+				'type'  => 'hidden',
2562
+				'value' => $id,
2563
+			];
2564
+			$hf                        = $this->_generate_admin_form_fields($hidden_field_arr, 'array');
2565
+		} else {
2566
+			$hf = '';
2567
+		}
2568
+		// add hidden field
2569
+		$this->_template_args['publish_hidden_fields'] = is_array($hf) && ! empty($name)
2570
+			? $hf[ $name ]['field']
2571
+			: $hf;
2572
+	}
2573
+
2574
+
2575
+	/**
2576
+	 * displays an error message to ppl who have javascript disabled
2577
+	 *
2578
+	 * @return void
2579
+	 */
2580
+	private function _display_no_javascript_warning()
2581
+	{
2582
+		?>
2583 2583
         <noscript>
2584 2584
             <div id="no-js-message" class="error">
2585 2585
                 <p style="font-size:1.3em;">
2586 2586
                     <span style="color:red;"><?php esc_html_e('Warning!', 'event_espresso'); ?></span>
2587 2587
                     <?php esc_html_e(
2588
-                        '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.',
2589
-                        'event_espresso'
2590
-                    ); ?>
2588
+						'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.',
2589
+						'event_espresso'
2590
+					); ?>
2591 2591
                 </p>
2592 2592
             </div>
2593 2593
         </noscript>
2594 2594
         <?php
2595
-    }
2596
-
2597
-
2598
-    /**
2599
-     * displays espresso success and/or error notices
2600
-     *
2601
-     * @return void
2602
-     */
2603
-    protected function _display_espresso_notices()
2604
-    {
2605
-        $notices = $this->_get_transient(true);
2606
-        echo stripslashes($notices);
2607
-    }
2608
-
2609
-
2610
-    /**
2611
-     * spinny things pacify the masses
2612
-     *
2613
-     * @return void
2614
-     */
2615
-    protected function _add_admin_page_ajax_loading_img()
2616
-    {
2617
-        ?>
2595
+	}
2596
+
2597
+
2598
+	/**
2599
+	 * displays espresso success and/or error notices
2600
+	 *
2601
+	 * @return void
2602
+	 */
2603
+	protected function _display_espresso_notices()
2604
+	{
2605
+		$notices = $this->_get_transient(true);
2606
+		echo stripslashes($notices);
2607
+	}
2608
+
2609
+
2610
+	/**
2611
+	 * spinny things pacify the masses
2612
+	 *
2613
+	 * @return void
2614
+	 */
2615
+	protected function _add_admin_page_ajax_loading_img()
2616
+	{
2617
+		?>
2618 2618
         <div id="espresso-ajax-loading" class="ajax-loading-grey">
2619 2619
             <span class="ee-spinner ee-spin"></span><span class="hidden"><?php
2620
-                esc_html_e('loading...', 'event_espresso'); ?></span>
2620
+				esc_html_e('loading...', 'event_espresso'); ?></span>
2621 2621
         </div>
2622 2622
         <?php
2623
-    }
2623
+	}
2624 2624
 
2625 2625
 
2626
-    /**
2627
-     * add admin page overlay for modal boxes
2628
-     *
2629
-     * @return void
2630
-     */
2631
-    protected function _add_admin_page_overlay()
2632
-    {
2633
-        ?>
2626
+	/**
2627
+	 * add admin page overlay for modal boxes
2628
+	 *
2629
+	 * @return void
2630
+	 */
2631
+	protected function _add_admin_page_overlay()
2632
+	{
2633
+		?>
2634 2634
         <div id="espresso-admin-page-overlay-dv" class=""></div>
2635 2635
         <?php
2636
-    }
2637
-
2638
-
2639
-    /**
2640
-     * facade for $this->addMetaBox()
2641
-     *
2642
-     * @param string  $action        where the metabox gets displayed
2643
-     * @param string  $title         Title of Metabox (output in metabox header)
2644
-     * @param string  $callback      If not empty and $create_fun is set to false then we'll use a custom callback
2645
-     *                               instead of the one created in here.
2646
-     * @param array   $callback_args an array of args supplied for the metabox
2647
-     * @param string  $column        what metabox column
2648
-     * @param string  $priority      give this metabox a priority (using accepted priorities for wp meta boxes)
2649
-     * @param boolean $create_func   default is true.  Basically we can say we don't WANT to have the runtime function
2650
-     *                               created but just set our own callback for wp's add_meta_box.
2651
-     * @throws DomainException
2652
-     */
2653
-    public function _add_admin_page_meta_box(
2654
-        $action,
2655
-        $title,
2656
-        $callback,
2657
-        $callback_args,
2658
-        $column = 'normal',
2659
-        $priority = 'high',
2660
-        $create_func = true
2661
-    ) {
2662
-        do_action('AHEE_log', __FILE__, __FUNCTION__, $callback);
2663
-        // 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.
2664
-        if (empty($callback_args) && $create_func) {
2665
-            $callback_args = [
2666
-                'template_path' => $this->_template_path,
2667
-                'template_args' => $this->_template_args,
2668
-            ];
2669
-        }
2670
-        // 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)
2671
-        $call_back_func = $create_func
2672
-            ? static function ($post, $metabox) {
2673
-                do_action('AHEE_log', __FILE__, __FUNCTION__, '');
2674
-                echo EEH_Template::display_template(
2675
-                    $metabox['args']['template_path'],
2676
-                    $metabox['args']['template_args'],
2677
-                    true
2678
-                );
2679
-            }
2680
-            : $callback;
2681
-        $this->addMetaBox(
2682
-            str_replace('_', '-', $action) . '-mbox',
2683
-            $title,
2684
-            $call_back_func,
2685
-            $this->_wp_page_slug,
2686
-            $column,
2687
-            $priority,
2688
-            $callback_args
2689
-        );
2690
-    }
2691
-
2692
-
2693
-    /**
2694
-     * generates HTML wrapper for and admin details page that contains metaboxes in columns
2695
-     *
2696
-     * @throws DomainException
2697
-     * @throws EE_Error
2698
-     * @throws InvalidArgumentException
2699
-     * @throws InvalidDataTypeException
2700
-     * @throws InvalidInterfaceException
2701
-     */
2702
-    public function display_admin_page_with_metabox_columns()
2703
-    {
2704
-        $this->_template_args['post_body_content']  = $this->_template_args['admin_page_content'];
2705
-        $this->_template_args['admin_page_content'] = EEH_Template::display_template(
2706
-            $this->_column_template_path,
2707
-            $this->_template_args,
2708
-            true
2709
-        );
2710
-        // the final wrapper
2711
-        $this->admin_page_wrapper();
2712
-    }
2713
-
2714
-
2715
-    /**
2716
-     * generates  HTML wrapper for an admin details page
2717
-     *
2718
-     * @return void
2719
-     * @throws DomainException
2720
-     * @throws EE_Error
2721
-     * @throws InvalidArgumentException
2722
-     * @throws InvalidDataTypeException
2723
-     * @throws InvalidInterfaceException
2724
-     */
2725
-    public function display_admin_page_with_sidebar()
2726
-    {
2727
-        $this->_display_admin_page(true);
2728
-    }
2729
-
2730
-
2731
-    /**
2732
-     * generates  HTML wrapper for an admin details page (except no sidebar)
2733
-     *
2734
-     * @return void
2735
-     * @throws DomainException
2736
-     * @throws EE_Error
2737
-     * @throws InvalidArgumentException
2738
-     * @throws InvalidDataTypeException
2739
-     * @throws InvalidInterfaceException
2740
-     */
2741
-    public function display_admin_page_with_no_sidebar()
2742
-    {
2743
-        $this->_display_admin_page();
2744
-    }
2745
-
2746
-
2747
-    /**
2748
-     * generates HTML wrapper for an EE about admin page (no sidebar)
2749
-     *
2750
-     * @return void
2751
-     * @throws DomainException
2752
-     * @throws EE_Error
2753
-     * @throws InvalidArgumentException
2754
-     * @throws InvalidDataTypeException
2755
-     * @throws InvalidInterfaceException
2756
-     */
2757
-    public function display_about_admin_page()
2758
-    {
2759
-        $this->_display_admin_page(false, true);
2760
-    }
2761
-
2762
-
2763
-    /**
2764
-     * display_admin_page
2765
-     * contains the code for actually displaying an admin page
2766
-     *
2767
-     * @param boolean $sidebar true with sidebar, false without
2768
-     * @param boolean $about   use the about admin wrapper instead of the default.
2769
-     * @return void
2770
-     * @throws DomainException
2771
-     * @throws EE_Error
2772
-     * @throws InvalidArgumentException
2773
-     * @throws InvalidDataTypeException
2774
-     * @throws InvalidInterfaceException
2775
-     */
2776
-    private function _display_admin_page($sidebar = false, $about = false)
2777
-    {
2778
-        do_action('AHEE_log', __FILE__, __FUNCTION__, '');
2779
-        // custom remove metaboxes hook to add or remove any metaboxes to/from Admin pages.
2780
-        do_action('AHEE__EE_Admin_Page___display_admin_page__modify_metaboxes');
2781
-        // set current wp page slug - looks like: event-espresso_page_event_categories
2782
-        // keep in mind "event-espresso" COULD be something else if the top level menu label has been translated.
2783
-
2784
-        $post_body_content = $this->_template_args['before_admin_page_content'] ?? '';
2785
-
2786
-        $this->_template_args['add_page_frame'] = $this->_req_action !== 'system_status'
2787
-                                                 && $this->_req_action !== 'data_reset'
2788
-                                                 && $this->_wp_page_slug !== 'event-espresso_page_espresso_packages'
2789
-                                                 && strpos($post_body_content, 'wp-list-table') === false;
2790
-
2791
-        $this->_template_args['current_page']              = $this->_wp_page_slug;
2792
-        $this->_template_args['admin_page_wrapper_div_id'] = $this->_cpt_route
2793
-            ? 'poststuff'
2794
-            : 'espresso-default-admin';
2795
-        $this->_template_args['admin_page_wrapper_div_class'] = str_replace(
2796
-            'event-espresso_page_espresso_',
2797
-            '',
2798
-            $this->_wp_page_slug
2799
-        ) . ' ' . $this->_req_action . '-route';
2800
-
2801
-        $template_path = $sidebar
2802
-            ? EE_ADMIN_TEMPLATE . 'admin_details_wrapper.template.php'
2803
-            : EE_ADMIN_TEMPLATE . 'admin_details_wrapper_no_sidebar.template.php';
2804
-        if ($this->request->isAjax()) {
2805
-            $template_path = EE_ADMIN_TEMPLATE . 'admin_details_wrapper_no_sidebar_ajax.template.php';
2806
-        }
2807
-        $template_path = ! empty($this->_column_template_path) ? $this->_column_template_path : $template_path;
2808
-
2809
-        $this->_template_args['post_body_content']         = $this->_template_args['admin_page_content'] ?? '';
2810
-        $this->_template_args['before_admin_page_content'] = $post_body_content;
2811
-        $this->_template_args['after_admin_page_content']  = $this->_template_args['after_admin_page_content'] ?? '';
2812
-        $this->_template_args['admin_page_content']        = EEH_Template::display_template(
2813
-            $template_path,
2814
-            $this->_template_args,
2815
-            true
2816
-        );
2817
-        // the final template wrapper
2818
-        $this->admin_page_wrapper($about);
2819
-    }
2820
-
2821
-
2822
-    /**
2823
-     * This is used to display caf preview pages.
2824
-     *
2825
-     * @param string $utm_campaign_source what is the key used for google analytics link
2826
-     * @param bool   $display_sidebar     whether to use the sidebar template or the full template for the page.  TRUE
2827
-     *                                    = SHOW sidebar, FALSE = no sidebar. Default no sidebar.
2828
-     * @return void
2829
-     * @throws DomainException
2830
-     * @throws EE_Error
2831
-     * @throws InvalidArgumentException
2832
-     * @throws InvalidDataTypeException
2833
-     * @throws InvalidInterfaceException
2834
-     * @since 4.3.2
2835
-     */
2836
-    public function display_admin_caf_preview_page($utm_campaign_source = '', $display_sidebar = true)
2837
-    {
2838
-        // let's generate a default preview action button if there isn't one already present.
2839
-        $this->_labels['buttons']['buy_now']           = esc_html__(
2840
-            'Upgrade to Event Espresso 4 Right Now',
2841
-            'event_espresso'
2842
-        );
2843
-        $buy_now_url                                   = add_query_arg(
2844
-            [
2845
-                'ee_ver'       => 'ee4',
2846
-                'utm_source'   => 'ee4_plugin_admin',
2847
-                'utm_medium'   => 'link',
2848
-                'utm_campaign' => $utm_campaign_source,
2849
-                'utm_content'  => 'buy_now_button',
2850
-            ],
2851
-            'https://eventespresso.com/pricing/'
2852
-        );
2853
-        $this->_template_args['preview_action_button'] = ! isset($this->_template_args['preview_action_button'])
2854
-            ? $this->get_action_link_or_button(
2855
-                '',
2856
-                'buy_now',
2857
-                [],
2858
-                'button button--primary button--big',
2859
-                esc_url_raw($buy_now_url),
2860
-                true
2861
-            )
2862
-            : $this->_template_args['preview_action_button'];
2863
-        $this->_template_args['admin_page_content']    = EEH_Template::display_template(
2864
-            EE_ADMIN_TEMPLATE . 'admin_caf_full_page_preview.template.php',
2865
-            $this->_template_args,
2866
-            true
2867
-        );
2868
-        $this->_display_admin_page($display_sidebar);
2869
-    }
2870
-
2871
-
2872
-    /**
2873
-     * display_admin_list_table_page_with_sidebar
2874
-     * generates HTML wrapper for an admin_page with list_table
2875
-     *
2876
-     * @return void
2877
-     * @throws DomainException
2878
-     * @throws EE_Error
2879
-     * @throws InvalidArgumentException
2880
-     * @throws InvalidDataTypeException
2881
-     * @throws InvalidInterfaceException
2882
-     */
2883
-    public function display_admin_list_table_page_with_sidebar()
2884
-    {
2885
-        $this->_display_admin_list_table_page(true);
2886
-    }
2887
-
2888
-
2889
-    /**
2890
-     * display_admin_list_table_page_with_no_sidebar
2891
-     * generates HTML wrapper for an admin_page with list_table (but with no sidebar)
2892
-     *
2893
-     * @return void
2894
-     * @throws DomainException
2895
-     * @throws EE_Error
2896
-     * @throws InvalidArgumentException
2897
-     * @throws InvalidDataTypeException
2898
-     * @throws InvalidInterfaceException
2899
-     */
2900
-    public function display_admin_list_table_page_with_no_sidebar()
2901
-    {
2902
-        $this->_display_admin_list_table_page();
2903
-    }
2904
-
2905
-
2906
-    /**
2907
-     * generates html wrapper for an admin_list_table page
2908
-     *
2909
-     * @param boolean $sidebar whether to display with sidebar or not.
2910
-     * @return void
2911
-     * @throws DomainException
2912
-     * @throws EE_Error
2913
-     * @throws InvalidArgumentException
2914
-     * @throws InvalidDataTypeException
2915
-     * @throws InvalidInterfaceException
2916
-     */
2917
-    private function _display_admin_list_table_page($sidebar = false)
2918
-    {
2919
-        // setup search attributes
2920
-        $this->_set_search_attributes();
2921
-        $this->_template_args['current_page']     = $this->_wp_page_slug;
2922
-        $template_path                            = EE_ADMIN_TEMPLATE . 'admin_list_wrapper.template.php';
2923
-        $this->_template_args['table_url']        = $this->request->isAjax()
2924
-            ? add_query_arg(['noheader' => 'true', 'route' => $this->_req_action], $this->_admin_base_url)
2925
-            : add_query_arg(['route' => $this->_req_action], $this->_admin_base_url);
2926
-        $this->_template_args['list_table']       = $this->_list_table_object;
2927
-        $this->_template_args['current_route']    = $this->_req_action;
2928
-        $this->_template_args['list_table_class'] = get_class($this->_list_table_object);
2929
-        $ajax_sorting_callback                    = $this->_list_table_object->get_ajax_sorting_callback();
2930
-        if (! empty($ajax_sorting_callback)) {
2931
-            $sortable_list_table_form_fields = wp_nonce_field(
2932
-                $ajax_sorting_callback . '_nonce',
2933
-                $ajax_sorting_callback . '_nonce',
2934
-                false,
2935
-                false
2936
-            );
2937
-            $sortable_list_table_form_fields .= '<input type="hidden" id="ajax_table_sort_page" name="ajax_table_sort_page" value="'
2938
-                                                . $this->page_slug
2939
-                                                . '" />';
2940
-            $sortable_list_table_form_fields .= '<input type="hidden" id="ajax_table_sort_action" name="ajax_table_sort_action" value="'
2941
-                                                . $ajax_sorting_callback
2942
-                                                . '" />';
2943
-        } else {
2944
-            $sortable_list_table_form_fields = '';
2945
-        }
2946
-        $this->_template_args['sortable_list_table_form_fields'] = $sortable_list_table_form_fields;
2947
-
2948
-        $hidden_form_fields = $this->_template_args['list_table_hidden_fields'] ?? '';
2949
-
2950
-        $nonce_ref          = $this->_req_action . '_nonce';
2951
-        $hidden_form_fields .= '
2636
+	}
2637
+
2638
+
2639
+	/**
2640
+	 * facade for $this->addMetaBox()
2641
+	 *
2642
+	 * @param string  $action        where the metabox gets displayed
2643
+	 * @param string  $title         Title of Metabox (output in metabox header)
2644
+	 * @param string  $callback      If not empty and $create_fun is set to false then we'll use a custom callback
2645
+	 *                               instead of the one created in here.
2646
+	 * @param array   $callback_args an array of args supplied for the metabox
2647
+	 * @param string  $column        what metabox column
2648
+	 * @param string  $priority      give this metabox a priority (using accepted priorities for wp meta boxes)
2649
+	 * @param boolean $create_func   default is true.  Basically we can say we don't WANT to have the runtime function
2650
+	 *                               created but just set our own callback for wp's add_meta_box.
2651
+	 * @throws DomainException
2652
+	 */
2653
+	public function _add_admin_page_meta_box(
2654
+		$action,
2655
+		$title,
2656
+		$callback,
2657
+		$callback_args,
2658
+		$column = 'normal',
2659
+		$priority = 'high',
2660
+		$create_func = true
2661
+	) {
2662
+		do_action('AHEE_log', __FILE__, __FUNCTION__, $callback);
2663
+		// 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.
2664
+		if (empty($callback_args) && $create_func) {
2665
+			$callback_args = [
2666
+				'template_path' => $this->_template_path,
2667
+				'template_args' => $this->_template_args,
2668
+			];
2669
+		}
2670
+		// 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)
2671
+		$call_back_func = $create_func
2672
+			? static function ($post, $metabox) {
2673
+				do_action('AHEE_log', __FILE__, __FUNCTION__, '');
2674
+				echo EEH_Template::display_template(
2675
+					$metabox['args']['template_path'],
2676
+					$metabox['args']['template_args'],
2677
+					true
2678
+				);
2679
+			}
2680
+			: $callback;
2681
+		$this->addMetaBox(
2682
+			str_replace('_', '-', $action) . '-mbox',
2683
+			$title,
2684
+			$call_back_func,
2685
+			$this->_wp_page_slug,
2686
+			$column,
2687
+			$priority,
2688
+			$callback_args
2689
+		);
2690
+	}
2691
+
2692
+
2693
+	/**
2694
+	 * generates HTML wrapper for and admin details page that contains metaboxes in columns
2695
+	 *
2696
+	 * @throws DomainException
2697
+	 * @throws EE_Error
2698
+	 * @throws InvalidArgumentException
2699
+	 * @throws InvalidDataTypeException
2700
+	 * @throws InvalidInterfaceException
2701
+	 */
2702
+	public function display_admin_page_with_metabox_columns()
2703
+	{
2704
+		$this->_template_args['post_body_content']  = $this->_template_args['admin_page_content'];
2705
+		$this->_template_args['admin_page_content'] = EEH_Template::display_template(
2706
+			$this->_column_template_path,
2707
+			$this->_template_args,
2708
+			true
2709
+		);
2710
+		// the final wrapper
2711
+		$this->admin_page_wrapper();
2712
+	}
2713
+
2714
+
2715
+	/**
2716
+	 * generates  HTML wrapper for an admin details page
2717
+	 *
2718
+	 * @return void
2719
+	 * @throws DomainException
2720
+	 * @throws EE_Error
2721
+	 * @throws InvalidArgumentException
2722
+	 * @throws InvalidDataTypeException
2723
+	 * @throws InvalidInterfaceException
2724
+	 */
2725
+	public function display_admin_page_with_sidebar()
2726
+	{
2727
+		$this->_display_admin_page(true);
2728
+	}
2729
+
2730
+
2731
+	/**
2732
+	 * generates  HTML wrapper for an admin details page (except no sidebar)
2733
+	 *
2734
+	 * @return void
2735
+	 * @throws DomainException
2736
+	 * @throws EE_Error
2737
+	 * @throws InvalidArgumentException
2738
+	 * @throws InvalidDataTypeException
2739
+	 * @throws InvalidInterfaceException
2740
+	 */
2741
+	public function display_admin_page_with_no_sidebar()
2742
+	{
2743
+		$this->_display_admin_page();
2744
+	}
2745
+
2746
+
2747
+	/**
2748
+	 * generates HTML wrapper for an EE about admin page (no sidebar)
2749
+	 *
2750
+	 * @return void
2751
+	 * @throws DomainException
2752
+	 * @throws EE_Error
2753
+	 * @throws InvalidArgumentException
2754
+	 * @throws InvalidDataTypeException
2755
+	 * @throws InvalidInterfaceException
2756
+	 */
2757
+	public function display_about_admin_page()
2758
+	{
2759
+		$this->_display_admin_page(false, true);
2760
+	}
2761
+
2762
+
2763
+	/**
2764
+	 * display_admin_page
2765
+	 * contains the code for actually displaying an admin page
2766
+	 *
2767
+	 * @param boolean $sidebar true with sidebar, false without
2768
+	 * @param boolean $about   use the about admin wrapper instead of the default.
2769
+	 * @return void
2770
+	 * @throws DomainException
2771
+	 * @throws EE_Error
2772
+	 * @throws InvalidArgumentException
2773
+	 * @throws InvalidDataTypeException
2774
+	 * @throws InvalidInterfaceException
2775
+	 */
2776
+	private function _display_admin_page($sidebar = false, $about = false)
2777
+	{
2778
+		do_action('AHEE_log', __FILE__, __FUNCTION__, '');
2779
+		// custom remove metaboxes hook to add or remove any metaboxes to/from Admin pages.
2780
+		do_action('AHEE__EE_Admin_Page___display_admin_page__modify_metaboxes');
2781
+		// set current wp page slug - looks like: event-espresso_page_event_categories
2782
+		// keep in mind "event-espresso" COULD be something else if the top level menu label has been translated.
2783
+
2784
+		$post_body_content = $this->_template_args['before_admin_page_content'] ?? '';
2785
+
2786
+		$this->_template_args['add_page_frame'] = $this->_req_action !== 'system_status'
2787
+												 && $this->_req_action !== 'data_reset'
2788
+												 && $this->_wp_page_slug !== 'event-espresso_page_espresso_packages'
2789
+												 && strpos($post_body_content, 'wp-list-table') === false;
2790
+
2791
+		$this->_template_args['current_page']              = $this->_wp_page_slug;
2792
+		$this->_template_args['admin_page_wrapper_div_id'] = $this->_cpt_route
2793
+			? 'poststuff'
2794
+			: 'espresso-default-admin';
2795
+		$this->_template_args['admin_page_wrapper_div_class'] = str_replace(
2796
+			'event-espresso_page_espresso_',
2797
+			'',
2798
+			$this->_wp_page_slug
2799
+		) . ' ' . $this->_req_action . '-route';
2800
+
2801
+		$template_path = $sidebar
2802
+			? EE_ADMIN_TEMPLATE . 'admin_details_wrapper.template.php'
2803
+			: EE_ADMIN_TEMPLATE . 'admin_details_wrapper_no_sidebar.template.php';
2804
+		if ($this->request->isAjax()) {
2805
+			$template_path = EE_ADMIN_TEMPLATE . 'admin_details_wrapper_no_sidebar_ajax.template.php';
2806
+		}
2807
+		$template_path = ! empty($this->_column_template_path) ? $this->_column_template_path : $template_path;
2808
+
2809
+		$this->_template_args['post_body_content']         = $this->_template_args['admin_page_content'] ?? '';
2810
+		$this->_template_args['before_admin_page_content'] = $post_body_content;
2811
+		$this->_template_args['after_admin_page_content']  = $this->_template_args['after_admin_page_content'] ?? '';
2812
+		$this->_template_args['admin_page_content']        = EEH_Template::display_template(
2813
+			$template_path,
2814
+			$this->_template_args,
2815
+			true
2816
+		);
2817
+		// the final template wrapper
2818
+		$this->admin_page_wrapper($about);
2819
+	}
2820
+
2821
+
2822
+	/**
2823
+	 * This is used to display caf preview pages.
2824
+	 *
2825
+	 * @param string $utm_campaign_source what is the key used for google analytics link
2826
+	 * @param bool   $display_sidebar     whether to use the sidebar template or the full template for the page.  TRUE
2827
+	 *                                    = SHOW sidebar, FALSE = no sidebar. Default no sidebar.
2828
+	 * @return void
2829
+	 * @throws DomainException
2830
+	 * @throws EE_Error
2831
+	 * @throws InvalidArgumentException
2832
+	 * @throws InvalidDataTypeException
2833
+	 * @throws InvalidInterfaceException
2834
+	 * @since 4.3.2
2835
+	 */
2836
+	public function display_admin_caf_preview_page($utm_campaign_source = '', $display_sidebar = true)
2837
+	{
2838
+		// let's generate a default preview action button if there isn't one already present.
2839
+		$this->_labels['buttons']['buy_now']           = esc_html__(
2840
+			'Upgrade to Event Espresso 4 Right Now',
2841
+			'event_espresso'
2842
+		);
2843
+		$buy_now_url                                   = add_query_arg(
2844
+			[
2845
+				'ee_ver'       => 'ee4',
2846
+				'utm_source'   => 'ee4_plugin_admin',
2847
+				'utm_medium'   => 'link',
2848
+				'utm_campaign' => $utm_campaign_source,
2849
+				'utm_content'  => 'buy_now_button',
2850
+			],
2851
+			'https://eventespresso.com/pricing/'
2852
+		);
2853
+		$this->_template_args['preview_action_button'] = ! isset($this->_template_args['preview_action_button'])
2854
+			? $this->get_action_link_or_button(
2855
+				'',
2856
+				'buy_now',
2857
+				[],
2858
+				'button button--primary button--big',
2859
+				esc_url_raw($buy_now_url),
2860
+				true
2861
+			)
2862
+			: $this->_template_args['preview_action_button'];
2863
+		$this->_template_args['admin_page_content']    = EEH_Template::display_template(
2864
+			EE_ADMIN_TEMPLATE . 'admin_caf_full_page_preview.template.php',
2865
+			$this->_template_args,
2866
+			true
2867
+		);
2868
+		$this->_display_admin_page($display_sidebar);
2869
+	}
2870
+
2871
+
2872
+	/**
2873
+	 * display_admin_list_table_page_with_sidebar
2874
+	 * generates HTML wrapper for an admin_page with list_table
2875
+	 *
2876
+	 * @return void
2877
+	 * @throws DomainException
2878
+	 * @throws EE_Error
2879
+	 * @throws InvalidArgumentException
2880
+	 * @throws InvalidDataTypeException
2881
+	 * @throws InvalidInterfaceException
2882
+	 */
2883
+	public function display_admin_list_table_page_with_sidebar()
2884
+	{
2885
+		$this->_display_admin_list_table_page(true);
2886
+	}
2887
+
2888
+
2889
+	/**
2890
+	 * display_admin_list_table_page_with_no_sidebar
2891
+	 * generates HTML wrapper for an admin_page with list_table (but with no sidebar)
2892
+	 *
2893
+	 * @return void
2894
+	 * @throws DomainException
2895
+	 * @throws EE_Error
2896
+	 * @throws InvalidArgumentException
2897
+	 * @throws InvalidDataTypeException
2898
+	 * @throws InvalidInterfaceException
2899
+	 */
2900
+	public function display_admin_list_table_page_with_no_sidebar()
2901
+	{
2902
+		$this->_display_admin_list_table_page();
2903
+	}
2904
+
2905
+
2906
+	/**
2907
+	 * generates html wrapper for an admin_list_table page
2908
+	 *
2909
+	 * @param boolean $sidebar whether to display with sidebar or not.
2910
+	 * @return void
2911
+	 * @throws DomainException
2912
+	 * @throws EE_Error
2913
+	 * @throws InvalidArgumentException
2914
+	 * @throws InvalidDataTypeException
2915
+	 * @throws InvalidInterfaceException
2916
+	 */
2917
+	private function _display_admin_list_table_page($sidebar = false)
2918
+	{
2919
+		// setup search attributes
2920
+		$this->_set_search_attributes();
2921
+		$this->_template_args['current_page']     = $this->_wp_page_slug;
2922
+		$template_path                            = EE_ADMIN_TEMPLATE . 'admin_list_wrapper.template.php';
2923
+		$this->_template_args['table_url']        = $this->request->isAjax()
2924
+			? add_query_arg(['noheader' => 'true', 'route' => $this->_req_action], $this->_admin_base_url)
2925
+			: add_query_arg(['route' => $this->_req_action], $this->_admin_base_url);
2926
+		$this->_template_args['list_table']       = $this->_list_table_object;
2927
+		$this->_template_args['current_route']    = $this->_req_action;
2928
+		$this->_template_args['list_table_class'] = get_class($this->_list_table_object);
2929
+		$ajax_sorting_callback                    = $this->_list_table_object->get_ajax_sorting_callback();
2930
+		if (! empty($ajax_sorting_callback)) {
2931
+			$sortable_list_table_form_fields = wp_nonce_field(
2932
+				$ajax_sorting_callback . '_nonce',
2933
+				$ajax_sorting_callback . '_nonce',
2934
+				false,
2935
+				false
2936
+			);
2937
+			$sortable_list_table_form_fields .= '<input type="hidden" id="ajax_table_sort_page" name="ajax_table_sort_page" value="'
2938
+												. $this->page_slug
2939
+												. '" />';
2940
+			$sortable_list_table_form_fields .= '<input type="hidden" id="ajax_table_sort_action" name="ajax_table_sort_action" value="'
2941
+												. $ajax_sorting_callback
2942
+												. '" />';
2943
+		} else {
2944
+			$sortable_list_table_form_fields = '';
2945
+		}
2946
+		$this->_template_args['sortable_list_table_form_fields'] = $sortable_list_table_form_fields;
2947
+
2948
+		$hidden_form_fields = $this->_template_args['list_table_hidden_fields'] ?? '';
2949
+
2950
+		$nonce_ref          = $this->_req_action . '_nonce';
2951
+		$hidden_form_fields .= '
2952 2952
             <input type="hidden" name="' . $nonce_ref . '" value="' . wp_create_nonce($nonce_ref) . '">';
2953 2953
 
2954
-        $this->_template_args['list_table_hidden_fields']        = $hidden_form_fields;
2955
-        // display message about search results?
2956
-        $search = $this->request->getRequestParam('s');
2957
-        $this->_template_args['before_list_table'] .= ! empty($search)
2958
-            ? '<p class="ee-search-results">' . sprintf(
2959
-                esc_html__('Displaying search results for the search string: %1$s', 'event_espresso'),
2960
-                trim($search, '%')
2961
-            ) . '</p>'
2962
-            : '';
2963
-        // filter before_list_table template arg
2964
-        $this->_template_args['before_list_table'] = apply_filters(
2965
-            'FHEE__EE_Admin_Page___display_admin_list_table_page__before_list_table__template_arg',
2966
-            $this->_template_args['before_list_table'],
2967
-            $this->page_slug,
2968
-            $this->request->requestParams(),
2969
-            $this->_req_action
2970
-        );
2971
-        // convert to array and filter again
2972
-        // arrays are easier to inject new items in a specific location,
2973
-        // but would not be backwards compatible, so we have to add a new filter
2974
-        $this->_template_args['before_list_table'] = implode(
2975
-            " \n",
2976
-            (array) apply_filters(
2977
-                'FHEE__EE_Admin_Page___display_admin_list_table_page__before_list_table__template_args_array',
2978
-                (array) $this->_template_args['before_list_table'],
2979
-                $this->page_slug,
2980
-                $this->request->requestParams(),
2981
-                $this->_req_action
2982
-            )
2983
-        );
2984
-        // filter after_list_table template arg
2985
-        $this->_template_args['after_list_table'] = apply_filters(
2986
-            'FHEE__EE_Admin_Page___display_admin_list_table_page__after_list_table__template_arg',
2987
-            $this->_template_args['after_list_table'],
2988
-            $this->page_slug,
2989
-            $this->request->requestParams(),
2990
-            $this->_req_action
2991
-        );
2992
-        // convert to array and filter again
2993
-        // arrays are easier to inject new items in a specific location,
2994
-        // but would not be backwards compatible, so we have to add a new filter
2995
-        $this->_template_args['after_list_table']   = implode(
2996
-            " \n",
2997
-            (array) apply_filters(
2998
-                'FHEE__EE_Admin_Page___display_admin_list_table_page__after_list_table__template_args_array',
2999
-                (array) $this->_template_args['after_list_table'],
3000
-                $this->page_slug,
3001
-                $this->request->requestParams(),
3002
-                $this->_req_action
3003
-            )
3004
-        );
3005
-        $this->_template_args['admin_page_content'] = EEH_Template::display_template(
3006
-            $template_path,
3007
-            $this->_template_args,
3008
-            true
3009
-        );
3010
-        // the final template wrapper
3011
-        if ($sidebar) {
3012
-            $this->display_admin_page_with_sidebar();
3013
-        } else {
3014
-            $this->display_admin_page_with_no_sidebar();
3015
-        }
3016
-    }
3017
-
3018
-
3019
-    /**
3020
-     * This just prepares a legend using the given items and the admin_details_legend.template.php file and returns the
3021
-     * html string for the legend.
3022
-     * $items are expected in an array in the following format:
3023
-     * $legend_items = array(
3024
-     *        'item_id' => array(
3025
-     *            'icon' => 'http://url_to_icon_being_described.png',
3026
-     *            'desc' => esc_html__('localized description of item');
3027
-     *        )
3028
-     * );
3029
-     *
3030
-     * @param array $items see above for format of array
3031
-     * @return string html string of legend
3032
-     * @throws DomainException
3033
-     */
3034
-    protected function _display_legend($items)
3035
-    {
3036
-        $this->_template_args['items'] = apply_filters(
3037
-            'FHEE__EE_Admin_Page___display_legend__items',
3038
-            (array) $items,
3039
-            $this
3040
-        );
3041
-        /** @var StatusChangeNotice $status_change_notice */
3042
-        $status_change_notice = $this->loader->getShared(
3043
-            'EventEspresso\core\domain\services\admin\notices\status_change\StatusChangeNotice'
3044
-        );
3045
-        $this->_template_args['status_change_notice'] = $status_change_notice->display(
3046
-            '__admin-legend',
3047
-            $this->page_slug
3048
-        );
3049
-        return EEH_Template::display_template(
3050
-            EE_ADMIN_TEMPLATE . 'admin_details_legend.template.php',
3051
-            $this->_template_args,
3052
-            true
3053
-        );
3054
-    }
3055
-
3056
-
3057
-    /**
3058
-     * This is used whenever we're DOING_AJAX to return a formatted json array that our calling javascript can expect
3059
-     * The returned json object is created from an array in the following format:
3060
-     * array(
3061
-     *  'error' => FALSE, //(default FALSE), contains any errors and/or exceptions (exceptions return json early),
3062
-     *  'success' => FALSE, //(default FALSE) - contains any special success message.
3063
-     *  'notices' => '', // - contains any EE_Error formatted notices
3064
-     *  'content' => 'string can be html', //this is a string of formatted content (can be html)
3065
-     *  'data' => array() //this can be any key/value pairs that a method returns for later json parsing by the js.
3066
-     *  We're also going to include the template args with every package (so js can pick out any specific template args
3067
-     *  that might be included in here)
3068
-     * )
3069
-     * The json object is populated by whatever is set in the $_template_args property.
3070
-     *
3071
-     * @param bool  $sticky_notices    Used to indicate whether you want to ensure notices are added to a transient
3072
-     *                                 instead of displayed.
3073
-     * @param array $notices_arguments Use this to pass any additional args on to the _process_notices.
3074
-     * @return void
3075
-     * @throws EE_Error
3076
-     * @throws InvalidArgumentException
3077
-     * @throws InvalidDataTypeException
3078
-     * @throws InvalidInterfaceException
3079
-     */
3080
-    protected function _return_json($sticky_notices = false, $notices_arguments = [])
3081
-    {
3082
-        // make sure any EE_Error notices have been handled.
3083
-        $this->_process_notices($notices_arguments, true, $sticky_notices);
3084
-        $data = isset($this->_template_args['data']) ? $this->_template_args['data'] : [];
3085
-        unset($this->_template_args['data']);
3086
-        $json = [
3087
-            'error'     => isset($this->_template_args['error']) ? $this->_template_args['error'] : false,
3088
-            'success'   => isset($this->_template_args['success']) ? $this->_template_args['success'] : false,
3089
-            'errors'    => isset($this->_template_args['errors']) ? $this->_template_args['errors'] : false,
3090
-            'attention' => isset($this->_template_args['attention']) ? $this->_template_args['attention'] : false,
3091
-            'notices'   => EE_Error::get_notices(),
3092
-            'content'   => isset($this->_template_args['admin_page_content'])
3093
-                ? $this->_template_args['admin_page_content'] : '',
3094
-            'data'      => array_merge($data, ['template_args' => $this->_template_args]),
3095
-            'isEEajax'  => true
3096
-            // special flag so any ajax.Success methods in js can identify this return package as a EEajax package.
3097
-        ];
3098
-        // make sure there are no php errors or headers_sent.  Then we can set correct json header.
3099
-        if (null === error_get_last() || ! headers_sent()) {
3100
-            header('Content-Type: application/json; charset=UTF-8');
3101
-        }
3102
-        echo wp_json_encode($json);
3103
-        exit();
3104
-    }
3105
-
3106
-
3107
-    /**
3108
-     * Simply a wrapper for the protected method so we can call this outside the class (ONLY when doing ajax)
3109
-     *
3110
-     * @return void
3111
-     * @throws EE_Error
3112
-     * @throws InvalidArgumentException
3113
-     * @throws InvalidDataTypeException
3114
-     * @throws InvalidInterfaceException
3115
-     */
3116
-    public function return_json()
3117
-    {
3118
-        if ($this->request->isAjax()) {
3119
-            $this->_return_json();
3120
-        } else {
3121
-            throw new EE_Error(
3122
-                sprintf(
3123
-                    esc_html__('The public %s method can only be called when DOING_AJAX = TRUE', 'event_espresso'),
3124
-                    __FUNCTION__
3125
-                )
3126
-            );
3127
-        }
3128
-    }
3129
-
3130
-
3131
-    /**
3132
-     * This provides a way for child hook classes to send along themselves by reference so methods/properties within
3133
-     * them can be accessed by EE_Admin_child pages. This is assigned to the $_hook_obj property.
3134
-     *
3135
-     * @param EE_Admin_Hooks $hook_obj This will be the object for the EE_Admin_Hooks child
3136
-     */
3137
-    public function set_hook_object(EE_Admin_Hooks $hook_obj)
3138
-    {
3139
-        $this->_hook_obj = $hook_obj;
3140
-    }
3141
-
3142
-
3143
-    /**
3144
-     *        generates  HTML wrapper with Tabbed nav for an admin page
3145
-     *
3146
-     * @param boolean $about whether to use the special about page wrapper or default.
3147
-     * @return void
3148
-     * @throws DomainException
3149
-     * @throws EE_Error
3150
-     * @throws InvalidArgumentException
3151
-     * @throws InvalidDataTypeException
3152
-     * @throws InvalidInterfaceException
3153
-     */
3154
-    public function admin_page_wrapper($about = false)
3155
-    {
3156
-        do_action('AHEE_log', __FILE__, __FUNCTION__, '');
3157
-        $this->_nav_tabs                                   = $this->_get_main_nav_tabs();
3158
-        $this->_template_args['nav_tabs']                  = $this->_nav_tabs;
3159
-        $this->_template_args['admin_page_title']          = $this->_admin_page_title;
3160
-
3161
-        $this->_template_args['before_admin_page_content'] = apply_filters(
3162
-            "FHEE_before_admin_page_content{$this->_current_page}{$this->_current_view}",
3163
-            $this->_template_args['before_admin_page_content'] ?? ''
3164
-        );
3165
-
3166
-        $this->_template_args['after_admin_page_content']  = apply_filters(
3167
-            "FHEE_after_admin_page_content{$this->_current_page}{$this->_current_view}",
3168
-            $this->_template_args['after_admin_page_content'] ?? ''
3169
-        );
3170
-        $this->_template_args['after_admin_page_content']  .= $this->_set_help_popup_content();
3171
-
3172
-        if ($this->request->isAjax()) {
3173
-            $this->_template_args['admin_page_content'] = EEH_Template::display_template(
3174
-                // $template_path,
3175
-                EE_ADMIN_TEMPLATE . 'admin_wrapper_ajax.template.php',
3176
-                $this->_template_args,
3177
-                true
3178
-            );
3179
-            $this->_return_json();
3180
-        }
3181
-        // load settings page wrapper template
3182
-        $template_path = $about
3183
-            ? EE_ADMIN_TEMPLATE . 'about_admin_wrapper.template.php'
3184
-            : EE_ADMIN_TEMPLATE . 'admin_wrapper.template.php';
3185
-
3186
-        EEH_Template::display_template($template_path, $this->_template_args);
3187
-    }
3188
-
3189
-
3190
-    /**
3191
-     * This returns the admin_nav tabs html using the configuration in the _nav_tabs property
3192
-     *
3193
-     * @return string html
3194
-     * @throws EE_Error
3195
-     */
3196
-    protected function _get_main_nav_tabs()
3197
-    {
3198
-        // let's generate the html using the EEH_Tabbed_Content helper.
3199
-        // We do this here so that it's possible for child classes to add in nav tabs dynamically at the last minute
3200
-        // (rather than setting in the page_routes array)
3201
-        return EEH_Tabbed_Content::display_admin_nav_tabs($this->_nav_tabs, $this->page_slug);
3202
-    }
3203
-
3204
-
3205
-    /**
3206
-     *        sort nav tabs
3207
-     *
3208
-     * @param $a
3209
-     * @param $b
3210
-     * @return int
3211
-     */
3212
-    private function _sort_nav_tabs($a, $b)
3213
-    {
3214
-        if ($a['order'] === $b['order']) {
3215
-            return 0;
3216
-        }
3217
-        return ($a['order'] < $b['order']) ? -1 : 1;
3218
-    }
3219
-
3220
-
3221
-    /**
3222
-     * generates HTML for the forms used on admin pages
3223
-     *
3224
-     * @param array  $input_vars - array of input field details
3225
-     * @param string $generator  indicates which generator to use: options are 'string' or 'array'
3226
-     * @param bool   $id
3227
-     * @return array|string
3228
-     * @uses   EEH_Form_Fields::get_form_fields (/helper/EEH_Form_Fields.helper.php)
3229
-     * @uses   EEH_Form_Fields::get_form_fields_array (/helper/EEH_Form_Fields.helper.php)
3230
-     */
3231
-    protected function _generate_admin_form_fields($input_vars = [], $generator = 'string', $id = false)
3232
-    {
3233
-        return $generator === 'string'
3234
-            ? EEH_Form_Fields::get_form_fields($input_vars, $id)
3235
-            : EEH_Form_Fields::get_form_fields_array($input_vars);
3236
-    }
3237
-
3238
-
3239
-    /**
3240
-     * generates the "Save" and "Save & Close" buttons for edit forms
3241
-     *
3242
-     * @param bool             $both     if true then both buttons will be generated.  If false then just the "Save &
3243
-     *                                   Close" button.
3244
-     * @param array            $text     if included, generator will use the given text for the buttons ( array([0] =>
3245
-     *                                   'Save', [1] => 'save & close')
3246
-     * @param array            $actions  if included allows us to set the actions that each button will carry out (i.e.
3247
-     *                                   via the "name" value in the button).  We can also use this to just dump
3248
-     *                                   default actions by submitting some other value.
3249
-     * @param bool|string|null $referrer if false then we just do the default action on save and close.  Other wise it
3250
-     *                                   will use the $referrer string. IF null, then we don't do ANYTHING on save and
3251
-     *                                   close (normal form handling).
3252
-     */
3253
-    protected function _set_save_buttons($both = true, $text = [], $actions = [], $referrer = null)
3254
-    {
3255
-        // make sure $text and $actions are in an array
3256
-        $text          = (array) $text;
3257
-        $actions       = (array) $actions;
3258
-        $referrer_url  = ! empty($referrer) ? $referrer : $this->request->getServerParam('REQUEST_URI');
3259
-        $button_text   = ! empty($text)
3260
-            ? $text
3261
-            : [
3262
-                esc_html__('Save', 'event_espresso'),
3263
-                esc_html__('Save and Close', 'event_espresso'),
3264
-            ];
3265
-        $default_names = ['save', 'save_and_close'];
3266
-        $buttons = '';
3267
-        foreach ($button_text as $key => $button) {
3268
-            $ref     = $default_names[ $key ];
3269
-            $name    = ! empty($actions) ? $actions[ $key ] : $ref;
3270
-            $buttons .= '<input type="submit" class="button button--primary ' . $ref . '" '
3271
-                        . 'value="' . $button . '" name="' . $name . '" '
3272
-                        . 'id="' . $this->_current_view . '_' . $ref . '" />';
3273
-            if (! $both) {
3274
-                break;
3275
-            }
3276
-        }
3277
-        // add in a hidden index for the current page (so save and close redirects properly)
3278
-        $buttons .= '<input type="hidden" id="save_and_close_referrer" name="save_and_close_referrer" value="'
3279
-                   . $referrer_url
3280
-                   . '" />';
3281
-        $this->_template_args['save_buttons'] = $buttons;
3282
-    }
3283
-
3284
-
3285
-    /**
3286
-     * Wrapper for the protected function.  Allows plugins/addons to call this to set the form tags.
3287
-     *
3288
-     * @param string $route
3289
-     * @param array  $additional_hidden_fields
3290
-     * @see   $this->_set_add_edit_form_tags() for details on params
3291
-     * @since 4.6.0
3292
-     */
3293
-    public function set_add_edit_form_tags($route = '', $additional_hidden_fields = [])
3294
-    {
3295
-        $this->_set_add_edit_form_tags($route, $additional_hidden_fields);
3296
-    }
3297
-
3298
-
3299
-    /**
3300
-     * set form open and close tags on add/edit pages.
3301
-     *
3302
-     * @param string $route                    the route you want the form to direct to
3303
-     * @param array  $additional_hidden_fields any additional hidden fields required in the form header
3304
-     * @return void
3305
-     */
3306
-    protected function _set_add_edit_form_tags($route = '', $additional_hidden_fields = [])
3307
-    {
3308
-        if (empty($route)) {
3309
-            $user_msg = esc_html__(
3310
-                'An error occurred. No action was set for this page\'s form.',
3311
-                'event_espresso'
3312
-            );
3313
-            $dev_msg  = $user_msg . "\n"
3314
-                        . sprintf(
3315
-                            esc_html__('The $route argument is required for the %s->%s method.', 'event_espresso'),
3316
-                            __FUNCTION__,
3317
-                            __CLASS__
3318
-                        );
3319
-            EE_Error::add_error($user_msg . '||' . $dev_msg, __FILE__, __FUNCTION__, __LINE__);
3320
-        }
3321
-        // open form
3322
-        $action = $this->_admin_base_url;
3323
-        $this->_template_args['before_admin_page_content'] = "
2954
+		$this->_template_args['list_table_hidden_fields']        = $hidden_form_fields;
2955
+		// display message about search results?
2956
+		$search = $this->request->getRequestParam('s');
2957
+		$this->_template_args['before_list_table'] .= ! empty($search)
2958
+			? '<p class="ee-search-results">' . sprintf(
2959
+				esc_html__('Displaying search results for the search string: %1$s', 'event_espresso'),
2960
+				trim($search, '%')
2961
+			) . '</p>'
2962
+			: '';
2963
+		// filter before_list_table template arg
2964
+		$this->_template_args['before_list_table'] = apply_filters(
2965
+			'FHEE__EE_Admin_Page___display_admin_list_table_page__before_list_table__template_arg',
2966
+			$this->_template_args['before_list_table'],
2967
+			$this->page_slug,
2968
+			$this->request->requestParams(),
2969
+			$this->_req_action
2970
+		);
2971
+		// convert to array and filter again
2972
+		// arrays are easier to inject new items in a specific location,
2973
+		// but would not be backwards compatible, so we have to add a new filter
2974
+		$this->_template_args['before_list_table'] = implode(
2975
+			" \n",
2976
+			(array) apply_filters(
2977
+				'FHEE__EE_Admin_Page___display_admin_list_table_page__before_list_table__template_args_array',
2978
+				(array) $this->_template_args['before_list_table'],
2979
+				$this->page_slug,
2980
+				$this->request->requestParams(),
2981
+				$this->_req_action
2982
+			)
2983
+		);
2984
+		// filter after_list_table template arg
2985
+		$this->_template_args['after_list_table'] = apply_filters(
2986
+			'FHEE__EE_Admin_Page___display_admin_list_table_page__after_list_table__template_arg',
2987
+			$this->_template_args['after_list_table'],
2988
+			$this->page_slug,
2989
+			$this->request->requestParams(),
2990
+			$this->_req_action
2991
+		);
2992
+		// convert to array and filter again
2993
+		// arrays are easier to inject new items in a specific location,
2994
+		// but would not be backwards compatible, so we have to add a new filter
2995
+		$this->_template_args['after_list_table']   = implode(
2996
+			" \n",
2997
+			(array) apply_filters(
2998
+				'FHEE__EE_Admin_Page___display_admin_list_table_page__after_list_table__template_args_array',
2999
+				(array) $this->_template_args['after_list_table'],
3000
+				$this->page_slug,
3001
+				$this->request->requestParams(),
3002
+				$this->_req_action
3003
+			)
3004
+		);
3005
+		$this->_template_args['admin_page_content'] = EEH_Template::display_template(
3006
+			$template_path,
3007
+			$this->_template_args,
3008
+			true
3009
+		);
3010
+		// the final template wrapper
3011
+		if ($sidebar) {
3012
+			$this->display_admin_page_with_sidebar();
3013
+		} else {
3014
+			$this->display_admin_page_with_no_sidebar();
3015
+		}
3016
+	}
3017
+
3018
+
3019
+	/**
3020
+	 * This just prepares a legend using the given items and the admin_details_legend.template.php file and returns the
3021
+	 * html string for the legend.
3022
+	 * $items are expected in an array in the following format:
3023
+	 * $legend_items = array(
3024
+	 *        'item_id' => array(
3025
+	 *            'icon' => 'http://url_to_icon_being_described.png',
3026
+	 *            'desc' => esc_html__('localized description of item');
3027
+	 *        )
3028
+	 * );
3029
+	 *
3030
+	 * @param array $items see above for format of array
3031
+	 * @return string html string of legend
3032
+	 * @throws DomainException
3033
+	 */
3034
+	protected function _display_legend($items)
3035
+	{
3036
+		$this->_template_args['items'] = apply_filters(
3037
+			'FHEE__EE_Admin_Page___display_legend__items',
3038
+			(array) $items,
3039
+			$this
3040
+		);
3041
+		/** @var StatusChangeNotice $status_change_notice */
3042
+		$status_change_notice = $this->loader->getShared(
3043
+			'EventEspresso\core\domain\services\admin\notices\status_change\StatusChangeNotice'
3044
+		);
3045
+		$this->_template_args['status_change_notice'] = $status_change_notice->display(
3046
+			'__admin-legend',
3047
+			$this->page_slug
3048
+		);
3049
+		return EEH_Template::display_template(
3050
+			EE_ADMIN_TEMPLATE . 'admin_details_legend.template.php',
3051
+			$this->_template_args,
3052
+			true
3053
+		);
3054
+	}
3055
+
3056
+
3057
+	/**
3058
+	 * This is used whenever we're DOING_AJAX to return a formatted json array that our calling javascript can expect
3059
+	 * The returned json object is created from an array in the following format:
3060
+	 * array(
3061
+	 *  'error' => FALSE, //(default FALSE), contains any errors and/or exceptions (exceptions return json early),
3062
+	 *  'success' => FALSE, //(default FALSE) - contains any special success message.
3063
+	 *  'notices' => '', // - contains any EE_Error formatted notices
3064
+	 *  'content' => 'string can be html', //this is a string of formatted content (can be html)
3065
+	 *  'data' => array() //this can be any key/value pairs that a method returns for later json parsing by the js.
3066
+	 *  We're also going to include the template args with every package (so js can pick out any specific template args
3067
+	 *  that might be included in here)
3068
+	 * )
3069
+	 * The json object is populated by whatever is set in the $_template_args property.
3070
+	 *
3071
+	 * @param bool  $sticky_notices    Used to indicate whether you want to ensure notices are added to a transient
3072
+	 *                                 instead of displayed.
3073
+	 * @param array $notices_arguments Use this to pass any additional args on to the _process_notices.
3074
+	 * @return void
3075
+	 * @throws EE_Error
3076
+	 * @throws InvalidArgumentException
3077
+	 * @throws InvalidDataTypeException
3078
+	 * @throws InvalidInterfaceException
3079
+	 */
3080
+	protected function _return_json($sticky_notices = false, $notices_arguments = [])
3081
+	{
3082
+		// make sure any EE_Error notices have been handled.
3083
+		$this->_process_notices($notices_arguments, true, $sticky_notices);
3084
+		$data = isset($this->_template_args['data']) ? $this->_template_args['data'] : [];
3085
+		unset($this->_template_args['data']);
3086
+		$json = [
3087
+			'error'     => isset($this->_template_args['error']) ? $this->_template_args['error'] : false,
3088
+			'success'   => isset($this->_template_args['success']) ? $this->_template_args['success'] : false,
3089
+			'errors'    => isset($this->_template_args['errors']) ? $this->_template_args['errors'] : false,
3090
+			'attention' => isset($this->_template_args['attention']) ? $this->_template_args['attention'] : false,
3091
+			'notices'   => EE_Error::get_notices(),
3092
+			'content'   => isset($this->_template_args['admin_page_content'])
3093
+				? $this->_template_args['admin_page_content'] : '',
3094
+			'data'      => array_merge($data, ['template_args' => $this->_template_args]),
3095
+			'isEEajax'  => true
3096
+			// special flag so any ajax.Success methods in js can identify this return package as a EEajax package.
3097
+		];
3098
+		// make sure there are no php errors or headers_sent.  Then we can set correct json header.
3099
+		if (null === error_get_last() || ! headers_sent()) {
3100
+			header('Content-Type: application/json; charset=UTF-8');
3101
+		}
3102
+		echo wp_json_encode($json);
3103
+		exit();
3104
+	}
3105
+
3106
+
3107
+	/**
3108
+	 * Simply a wrapper for the protected method so we can call this outside the class (ONLY when doing ajax)
3109
+	 *
3110
+	 * @return void
3111
+	 * @throws EE_Error
3112
+	 * @throws InvalidArgumentException
3113
+	 * @throws InvalidDataTypeException
3114
+	 * @throws InvalidInterfaceException
3115
+	 */
3116
+	public function return_json()
3117
+	{
3118
+		if ($this->request->isAjax()) {
3119
+			$this->_return_json();
3120
+		} else {
3121
+			throw new EE_Error(
3122
+				sprintf(
3123
+					esc_html__('The public %s method can only be called when DOING_AJAX = TRUE', 'event_espresso'),
3124
+					__FUNCTION__
3125
+				)
3126
+			);
3127
+		}
3128
+	}
3129
+
3130
+
3131
+	/**
3132
+	 * This provides a way for child hook classes to send along themselves by reference so methods/properties within
3133
+	 * them can be accessed by EE_Admin_child pages. This is assigned to the $_hook_obj property.
3134
+	 *
3135
+	 * @param EE_Admin_Hooks $hook_obj This will be the object for the EE_Admin_Hooks child
3136
+	 */
3137
+	public function set_hook_object(EE_Admin_Hooks $hook_obj)
3138
+	{
3139
+		$this->_hook_obj = $hook_obj;
3140
+	}
3141
+
3142
+
3143
+	/**
3144
+	 *        generates  HTML wrapper with Tabbed nav for an admin page
3145
+	 *
3146
+	 * @param boolean $about whether to use the special about page wrapper or default.
3147
+	 * @return void
3148
+	 * @throws DomainException
3149
+	 * @throws EE_Error
3150
+	 * @throws InvalidArgumentException
3151
+	 * @throws InvalidDataTypeException
3152
+	 * @throws InvalidInterfaceException
3153
+	 */
3154
+	public function admin_page_wrapper($about = false)
3155
+	{
3156
+		do_action('AHEE_log', __FILE__, __FUNCTION__, '');
3157
+		$this->_nav_tabs                                   = $this->_get_main_nav_tabs();
3158
+		$this->_template_args['nav_tabs']                  = $this->_nav_tabs;
3159
+		$this->_template_args['admin_page_title']          = $this->_admin_page_title;
3160
+
3161
+		$this->_template_args['before_admin_page_content'] = apply_filters(
3162
+			"FHEE_before_admin_page_content{$this->_current_page}{$this->_current_view}",
3163
+			$this->_template_args['before_admin_page_content'] ?? ''
3164
+		);
3165
+
3166
+		$this->_template_args['after_admin_page_content']  = apply_filters(
3167
+			"FHEE_after_admin_page_content{$this->_current_page}{$this->_current_view}",
3168
+			$this->_template_args['after_admin_page_content'] ?? ''
3169
+		);
3170
+		$this->_template_args['after_admin_page_content']  .= $this->_set_help_popup_content();
3171
+
3172
+		if ($this->request->isAjax()) {
3173
+			$this->_template_args['admin_page_content'] = EEH_Template::display_template(
3174
+				// $template_path,
3175
+				EE_ADMIN_TEMPLATE . 'admin_wrapper_ajax.template.php',
3176
+				$this->_template_args,
3177
+				true
3178
+			);
3179
+			$this->_return_json();
3180
+		}
3181
+		// load settings page wrapper template
3182
+		$template_path = $about
3183
+			? EE_ADMIN_TEMPLATE . 'about_admin_wrapper.template.php'
3184
+			: EE_ADMIN_TEMPLATE . 'admin_wrapper.template.php';
3185
+
3186
+		EEH_Template::display_template($template_path, $this->_template_args);
3187
+	}
3188
+
3189
+
3190
+	/**
3191
+	 * This returns the admin_nav tabs html using the configuration in the _nav_tabs property
3192
+	 *
3193
+	 * @return string html
3194
+	 * @throws EE_Error
3195
+	 */
3196
+	protected function _get_main_nav_tabs()
3197
+	{
3198
+		// let's generate the html using the EEH_Tabbed_Content helper.
3199
+		// We do this here so that it's possible for child classes to add in nav tabs dynamically at the last minute
3200
+		// (rather than setting in the page_routes array)
3201
+		return EEH_Tabbed_Content::display_admin_nav_tabs($this->_nav_tabs, $this->page_slug);
3202
+	}
3203
+
3204
+
3205
+	/**
3206
+	 *        sort nav tabs
3207
+	 *
3208
+	 * @param $a
3209
+	 * @param $b
3210
+	 * @return int
3211
+	 */
3212
+	private function _sort_nav_tabs($a, $b)
3213
+	{
3214
+		if ($a['order'] === $b['order']) {
3215
+			return 0;
3216
+		}
3217
+		return ($a['order'] < $b['order']) ? -1 : 1;
3218
+	}
3219
+
3220
+
3221
+	/**
3222
+	 * generates HTML for the forms used on admin pages
3223
+	 *
3224
+	 * @param array  $input_vars - array of input field details
3225
+	 * @param string $generator  indicates which generator to use: options are 'string' or 'array'
3226
+	 * @param bool   $id
3227
+	 * @return array|string
3228
+	 * @uses   EEH_Form_Fields::get_form_fields (/helper/EEH_Form_Fields.helper.php)
3229
+	 * @uses   EEH_Form_Fields::get_form_fields_array (/helper/EEH_Form_Fields.helper.php)
3230
+	 */
3231
+	protected function _generate_admin_form_fields($input_vars = [], $generator = 'string', $id = false)
3232
+	{
3233
+		return $generator === 'string'
3234
+			? EEH_Form_Fields::get_form_fields($input_vars, $id)
3235
+			: EEH_Form_Fields::get_form_fields_array($input_vars);
3236
+	}
3237
+
3238
+
3239
+	/**
3240
+	 * generates the "Save" and "Save & Close" buttons for edit forms
3241
+	 *
3242
+	 * @param bool             $both     if true then both buttons will be generated.  If false then just the "Save &
3243
+	 *                                   Close" button.
3244
+	 * @param array            $text     if included, generator will use the given text for the buttons ( array([0] =>
3245
+	 *                                   'Save', [1] => 'save & close')
3246
+	 * @param array            $actions  if included allows us to set the actions that each button will carry out (i.e.
3247
+	 *                                   via the "name" value in the button).  We can also use this to just dump
3248
+	 *                                   default actions by submitting some other value.
3249
+	 * @param bool|string|null $referrer if false then we just do the default action on save and close.  Other wise it
3250
+	 *                                   will use the $referrer string. IF null, then we don't do ANYTHING on save and
3251
+	 *                                   close (normal form handling).
3252
+	 */
3253
+	protected function _set_save_buttons($both = true, $text = [], $actions = [], $referrer = null)
3254
+	{
3255
+		// make sure $text and $actions are in an array
3256
+		$text          = (array) $text;
3257
+		$actions       = (array) $actions;
3258
+		$referrer_url  = ! empty($referrer) ? $referrer : $this->request->getServerParam('REQUEST_URI');
3259
+		$button_text   = ! empty($text)
3260
+			? $text
3261
+			: [
3262
+				esc_html__('Save', 'event_espresso'),
3263
+				esc_html__('Save and Close', 'event_espresso'),
3264
+			];
3265
+		$default_names = ['save', 'save_and_close'];
3266
+		$buttons = '';
3267
+		foreach ($button_text as $key => $button) {
3268
+			$ref     = $default_names[ $key ];
3269
+			$name    = ! empty($actions) ? $actions[ $key ] : $ref;
3270
+			$buttons .= '<input type="submit" class="button button--primary ' . $ref . '" '
3271
+						. 'value="' . $button . '" name="' . $name . '" '
3272
+						. 'id="' . $this->_current_view . '_' . $ref . '" />';
3273
+			if (! $both) {
3274
+				break;
3275
+			}
3276
+		}
3277
+		// add in a hidden index for the current page (so save and close redirects properly)
3278
+		$buttons .= '<input type="hidden" id="save_and_close_referrer" name="save_and_close_referrer" value="'
3279
+				   . $referrer_url
3280
+				   . '" />';
3281
+		$this->_template_args['save_buttons'] = $buttons;
3282
+	}
3283
+
3284
+
3285
+	/**
3286
+	 * Wrapper for the protected function.  Allows plugins/addons to call this to set the form tags.
3287
+	 *
3288
+	 * @param string $route
3289
+	 * @param array  $additional_hidden_fields
3290
+	 * @see   $this->_set_add_edit_form_tags() for details on params
3291
+	 * @since 4.6.0
3292
+	 */
3293
+	public function set_add_edit_form_tags($route = '', $additional_hidden_fields = [])
3294
+	{
3295
+		$this->_set_add_edit_form_tags($route, $additional_hidden_fields);
3296
+	}
3297
+
3298
+
3299
+	/**
3300
+	 * set form open and close tags on add/edit pages.
3301
+	 *
3302
+	 * @param string $route                    the route you want the form to direct to
3303
+	 * @param array  $additional_hidden_fields any additional hidden fields required in the form header
3304
+	 * @return void
3305
+	 */
3306
+	protected function _set_add_edit_form_tags($route = '', $additional_hidden_fields = [])
3307
+	{
3308
+		if (empty($route)) {
3309
+			$user_msg = esc_html__(
3310
+				'An error occurred. No action was set for this page\'s form.',
3311
+				'event_espresso'
3312
+			);
3313
+			$dev_msg  = $user_msg . "\n"
3314
+						. sprintf(
3315
+							esc_html__('The $route argument is required for the %s->%s method.', 'event_espresso'),
3316
+							__FUNCTION__,
3317
+							__CLASS__
3318
+						);
3319
+			EE_Error::add_error($user_msg . '||' . $dev_msg, __FILE__, __FUNCTION__, __LINE__);
3320
+		}
3321
+		// open form
3322
+		$action = $this->_admin_base_url;
3323
+		$this->_template_args['before_admin_page_content'] = "
3324 3324
             <form name='form' method='post' action='{$action}' id='{$route}_event_form' class='ee-admin-page-form' >
3325 3325
             ";
3326
-        // add nonce
3327
-        $nonce                                             =
3328
-            wp_nonce_field($route . '_nonce', $route . '_nonce', false, false);
3329
-        $this->_template_args['before_admin_page_content'] .= "\n\t" . $nonce;
3330
-        // add REQUIRED form action
3331
-        $hidden_fields = [
3332
-            'action' => ['type' => 'hidden', 'value' => $route],
3333
-        ];
3334
-        // merge arrays
3335
-        $hidden_fields = is_array($additional_hidden_fields)
3336
-            ? array_merge($hidden_fields, $additional_hidden_fields)
3337
-            : $hidden_fields;
3338
-        // generate form fields
3339
-        $form_fields = $this->_generate_admin_form_fields($hidden_fields, 'array');
3340
-        // add fields to form
3341
-        foreach ((array) $form_fields as $form_field) {
3342
-            $this->_template_args['before_admin_page_content'] .= "\n\t" . $form_field['field'];
3343
-        }
3344
-        // close form
3345
-        $this->_template_args['after_admin_page_content'] = '</form>';
3346
-    }
3347
-
3348
-
3349
-    /**
3350
-     * Public Wrapper for _redirect_after_action() method since its
3351
-     * discovered it would be useful for external code to have access.
3352
-     *
3353
-     * @param bool   $success
3354
-     * @param string $what
3355
-     * @param string $action_desc
3356
-     * @param array  $query_args
3357
-     * @param bool   $override_overwrite
3358
-     * @throws EE_Error
3359
-     * @see   EE_Admin_Page::_redirect_after_action() for params.
3360
-     * @since 4.5.0
3361
-     */
3362
-    public function redirect_after_action(
3363
-        $success = false,
3364
-        $what = 'item',
3365
-        $action_desc = 'processed',
3366
-        $query_args = [],
3367
-        $override_overwrite = false
3368
-    ) {
3369
-        $this->_redirect_after_action(
3370
-            $success,
3371
-            $what,
3372
-            $action_desc,
3373
-            $query_args,
3374
-            $override_overwrite
3375
-        );
3376
-    }
3377
-
3378
-
3379
-    /**
3380
-     * Helper method for merging existing request data with the returned redirect url.
3381
-     *
3382
-     * This is typically used for redirects after an action so that if the original view was a filtered view those
3383
-     * filters are still applied.
3384
-     *
3385
-     * @param array $new_route_data
3386
-     * @return array
3387
-     */
3388
-    protected function mergeExistingRequestParamsWithRedirectArgs(array $new_route_data)
3389
-    {
3390
-        foreach ($this->request->requestParams() as $ref => $value) {
3391
-            // unset nonces
3392
-            if (strpos($ref, 'nonce') !== false) {
3393
-                $this->request->unSetRequestParam($ref);
3394
-                continue;
3395
-            }
3396
-            // urlencode values.
3397
-            $value = is_array($value) ? array_map('urlencode', $value) : urlencode($value);
3398
-            $this->request->setRequestParam($ref, $value);
3399
-        }
3400
-        return array_merge($this->request->requestParams(), $new_route_data);
3401
-    }
3402
-
3403
-
3404
-    /**
3405
-     * @param int|float|string $success      - whether success was for two or more records, or just one, or none
3406
-     * @param string           $what         - what the action was performed on
3407
-     * @param string           $action_desc  - what was done ie: updated, deleted, etc
3408
-     * @param array $query_args              - an array of query_args to be added to the URL to redirect to
3409
-     * @param BOOL $override_overwrite       - by default all EE_Error::success messages are overwritten,
3410
-     *                                         this allows you to override this so that they show.
3411
-     * @return void
3412
-     * @throws EE_Error
3413
-     * @throws InvalidArgumentException
3414
-     * @throws InvalidDataTypeException
3415
-     * @throws InvalidInterfaceException
3416
-     */
3417
-    protected function _redirect_after_action(
3418
-        $success = 0,
3419
-        string $what = 'item',
3420
-        string $action_desc = 'processed',
3421
-        array $query_args = [],
3422
-        bool $override_overwrite = false
3423
-    ) {
3424
-        do_action('AHEE_log', __FILE__, __FUNCTION__, '');
3425
-        $notices      = EE_Error::get_notices(false);
3426
-        // overwrite default success messages //BUT ONLY if overwrite not overridden
3427
-        if (! $override_overwrite || ! empty($notices['errors'])) {
3428
-            EE_Error::overwrite_success();
3429
-        }
3430
-        if (! $override_overwrite && ! empty($what) && ! empty($action_desc) && empty($notices['errors'])) {
3431
-            // how many records affected ? more than one record ? or just one ?
3432
-            EE_Error::add_success(
3433
-                sprintf(
3434
-                    esc_html(
3435
-                        _n(
3436
-                            'The "%1$s" has been successfully %2$s.',
3437
-                            'The "%1$s" have been successfully %2$s.',
3438
-                            $success,
3439
-                            'event_espresso'
3440
-                        )
3441
-                    ),
3442
-                    $what,
3443
-                    $action_desc
3444
-                ),
3445
-                __FILE__,
3446
-                __FUNCTION__,
3447
-                __LINE__
3448
-            );
3449
-        }
3450
-        // check that $query_args isn't something crazy
3451
-        if (! is_array($query_args)) {
3452
-            $query_args = [];
3453
-        }
3454
-        /**
3455
-         * Allow injecting actions before the query_args are modified for possible different
3456
-         * redirections on save and close actions
3457
-         *
3458
-         * @param array $query_args       The original query_args array coming into the
3459
-         *                                method.
3460
-         * @since 4.2.0
3461
-         */
3462
-        do_action(
3463
-            "AHEE__{$this->class_name}___redirect_after_action__before_redirect_modification_{$this->_req_action}",
3464
-            $query_args
3465
-        );
3466
-        // set redirect url.
3467
-        // Note if there is a "page" index in the $query_args then we go with vanilla admin.php route,
3468
-        // otherwise we go with whatever is set as the _admin_base_url
3469
-        $redirect_url = isset($query_args['page']) ? admin_url('admin.php') : $this->_admin_base_url;
3470
-        // calculate where we're going (if we have a "save and close" button pushed)
3471
-        if (
3472
-            $this->request->requestParamIsSet('save_and_close')
3473
-            && $this->request->requestParamIsSet('save_and_close_referrer')
3474
-        ) {
3475
-            // even though we have the save_and_close referrer, we need to parse the url for the action in order to generate a nonce
3476
-            $parsed_url = parse_url($this->request->getRequestParam('save_and_close_referrer', '', 'url'));
3477
-            // regenerate query args array from referrer URL
3478
-            parse_str($parsed_url['query'], $query_args);
3479
-            // correct page and action will be in the query args now
3480
-            $redirect_url = admin_url('admin.php');
3481
-        }
3482
-        // merge any default query_args set in _default_route_query_args property
3483
-        if (! empty($this->_default_route_query_args) && ! $this->_is_UI_request) {
3484
-            $args_to_merge = [];
3485
-            foreach ($this->_default_route_query_args as $query_param => $query_value) {
3486
-                // is there a wp_referer array in our _default_route_query_args property?
3487
-                if ($query_param === 'wp_referer') {
3488
-                    $query_value = (array) $query_value;
3489
-                    foreach ($query_value as $reference => $value) {
3490
-                        if (strpos($reference, 'nonce') !== false) {
3491
-                            continue;
3492
-                        }
3493
-                        // finally we will override any arguments in the referer with
3494
-                        // what might be set on the _default_route_query_args array.
3495
-                        if (isset($this->_default_route_query_args[ $reference ])) {
3496
-                            $args_to_merge[ $reference ] = urlencode($this->_default_route_query_args[ $reference ]);
3497
-                        } else {
3498
-                            $args_to_merge[ $reference ] = urlencode($value);
3499
-                        }
3500
-                    }
3501
-                    continue;
3502
-                }
3503
-                $args_to_merge[ $query_param ] = $query_value;
3504
-            }
3505
-            // now let's merge these arguments but override with what was specifically sent in to the
3506
-            // redirect.
3507
-            $query_args = array_merge($args_to_merge, $query_args);
3508
-        }
3509
-        $this->_process_notices($query_args);
3510
-        // generate redirect url
3511
-        // if redirecting to anything other than the main page, add a nonce
3512
-        if (isset($query_args['action'])) {
3513
-            // manually generate wp_nonce and merge that with the query vars
3514
-            // becuz the wp_nonce_url function wrecks havoc on some vars
3515
-            $query_args['_wpnonce'] = wp_create_nonce($query_args['action'] . '_nonce');
3516
-        }
3517
-        // we're adding some hooks and filters in here for processing any things just before redirects
3518
-        // (example: an admin page has done an insert or update and we want to run something after that).
3519
-        do_action('AHEE_redirect_' . $this->class_name . $this->_req_action, $query_args);
3520
-        $redirect_url = apply_filters(
3521
-            'FHEE_redirect_' . $this->class_name . $this->_req_action,
3522
-            EE_Admin_Page::add_query_args_and_nonce($query_args, $redirect_url),
3523
-            $query_args
3524
-        );
3525
-        // check if we're doing ajax.  If we are then lets just return the results and js can handle how it wants.
3526
-        if ($this->request->isAjax()) {
3527
-            $default_data                    = [
3528
-                'close'        => true,
3529
-                'redirect_url' => $redirect_url,
3530
-                'where'        => 'main',
3531
-                'what'         => 'append',
3532
-            ];
3533
-            $this->_template_args['success'] = $success;
3534
-            $this->_template_args['data']    = ! empty($this->_template_args['data']) ? array_merge(
3535
-                $default_data,
3536
-                $this->_template_args['data']
3537
-            ) : $default_data;
3538
-            $this->_return_json();
3539
-        }
3540
-        wp_safe_redirect($redirect_url);
3541
-        exit();
3542
-    }
3543
-
3544
-
3545
-    /**
3546
-     * process any notices before redirecting (or returning ajax request)
3547
-     * This method sets the $this->_template_args['notices'] attribute;
3548
-     *
3549
-     * @param array $query_args         any query args that need to be used for notice transient ('action')
3550
-     * @param bool  $skip_route_verify  This is typically used when we are processing notices REALLY early and
3551
-     *                                  page_routes haven't been defined yet.
3552
-     * @param bool  $sticky_notices     This is used to flag that regardless of whether this is doing_ajax or not, we
3553
-     *                                  still save a transient for the notice.
3554
-     * @return void
3555
-     * @throws EE_Error
3556
-     * @throws InvalidArgumentException
3557
-     * @throws InvalidDataTypeException
3558
-     * @throws InvalidInterfaceException
3559
-     */
3560
-    protected function _process_notices($query_args = [], $skip_route_verify = false, $sticky_notices = true)
3561
-    {
3562
-        // first let's set individual error properties if doing_ajax and the properties aren't already set.
3563
-        if ($this->request->isAjax()) {
3564
-            $notices = EE_Error::get_notices(false);
3565
-            if (empty($this->_template_args['success'])) {
3566
-                $this->_template_args['success'] = isset($notices['success']) ? $notices['success'] : false;
3567
-            }
3568
-            if (empty($this->_template_args['errors'])) {
3569
-                $this->_template_args['errors'] = isset($notices['errors']) ? $notices['errors'] : false;
3570
-            }
3571
-            if (empty($this->_template_args['attention'])) {
3572
-                $this->_template_args['attention'] = isset($notices['attention']) ? $notices['attention'] : false;
3573
-            }
3574
-        }
3575
-        $this->_template_args['notices'] = EE_Error::get_notices();
3576
-        // IF this isn't ajax we need to create a transient for the notices using the route (however, overridden if $sticky_notices == true)
3577
-        if (! $this->request->isAjax() || $sticky_notices) {
3578
-            $route = isset($query_args['action']) ? $query_args['action'] : 'default';
3579
-            $this->_add_transient(
3580
-                $route,
3581
-                $this->_template_args['notices'],
3582
-                true,
3583
-                $skip_route_verify
3584
-            );
3585
-        }
3586
-    }
3587
-
3588
-
3589
-    /**
3590
-     * get_action_link_or_button
3591
-     * returns the button html for adding, editing, or deleting an item (depending on given type)
3592
-     *
3593
-     * @param string $action        use this to indicate which action the url is generated with.
3594
-     * @param string $type          accepted strings must be defined in the $_labels['button'] array(as the key)
3595
-     *                              property.
3596
-     * @param array  $extra_request if the button requires extra params you can include them in $key=>$value pairs.
3597
-     * @param string $class         Use this to give the class for the button. Defaults to 'button--primary'
3598
-     * @param string $base_url      If this is not provided
3599
-     *                              the _admin_base_url will be used as the default for the button base_url.
3600
-     *                              Otherwise this value will be used.
3601
-     * @param bool   $exclude_nonce If true then no nonce will be in the generated button link.
3602
-     * @return string
3603
-     * @throws InvalidArgumentException
3604
-     * @throws InvalidInterfaceException
3605
-     * @throws InvalidDataTypeException
3606
-     * @throws EE_Error
3607
-     */
3608
-    public function get_action_link_or_button(
3609
-        $action,
3610
-        $type = 'add',
3611
-        $extra_request = [],
3612
-        $class = 'button button--primary',
3613
-        $base_url = '',
3614
-        $exclude_nonce = false
3615
-    ) {
3616
-        // first let's validate the action (if $base_url is FALSE otherwise validation will happen further along)
3617
-        if (empty($base_url) && ! isset($this->_page_routes[ $action ])) {
3618
-            throw new EE_Error(
3619
-                sprintf(
3620
-                    esc_html__(
3621
-                        'There is no page route for given action for the button.  This action was given: %s',
3622
-                        'event_espresso'
3623
-                    ),
3624
-                    $action
3625
-                )
3626
-            );
3627
-        }
3628
-        if (! isset($this->_labels['buttons'][ $type ])) {
3629
-            throw new EE_Error(
3630
-                sprintf(
3631
-                    esc_html__(
3632
-                        'There is no label for the given button type (%s). Labels are set in the <code>_page_config</code> property.',
3633
-                        'event_espresso'
3634
-                    ),
3635
-                    $type
3636
-                )
3637
-            );
3638
-        }
3639
-        // finally check user access for this button.
3640
-        $has_access = $this->check_user_access($action, true);
3641
-        if (! $has_access) {
3642
-            return '';
3643
-        }
3644
-        $_base_url  = ! $base_url ? $this->_admin_base_url : $base_url;
3645
-        $query_args = [
3646
-            'action' => $action,
3647
-        ];
3648
-        // merge extra_request args but make sure our original action takes precedence and doesn't get overwritten.
3649
-        if (! empty($extra_request)) {
3650
-            $query_args = array_merge($extra_request, $query_args);
3651
-        }
3652
-        $url = EE_Admin_Page::add_query_args_and_nonce($query_args, $_base_url, false, $exclude_nonce);
3653
-        return EEH_Template::get_button_or_link($url, $this->_labels['buttons'][ $type ], $class);
3654
-    }
3655
-
3656
-
3657
-    /**
3658
-     * _per_page_screen_option
3659
-     * Utility function for adding in a per_page_option in the screen_options_dropdown.
3660
-     *
3661
-     * @return void
3662
-     * @throws InvalidArgumentException
3663
-     * @throws InvalidInterfaceException
3664
-     * @throws InvalidDataTypeException
3665
-     */
3666
-    protected function _per_page_screen_option()
3667
-    {
3668
-        $option = 'per_page';
3669
-        $args   = [
3670
-            'label'   => apply_filters(
3671
-                'FHEE__EE_Admin_Page___per_page_screen_options___label',
3672
-                $this->_admin_page_title,
3673
-                $this
3674
-            ),
3675
-            'default' => (int) apply_filters(
3676
-                'FHEE__EE_Admin_Page___per_page_screen_options__default',
3677
-                20
3678
-            ),
3679
-            'option'  => $this->_current_page . '_' . $this->_current_view . '_per_page',
3680
-        ];
3681
-        // ONLY add the screen option if the user has access to it.
3682
-        if ($this->check_user_access($this->_current_view, true)) {
3683
-            add_screen_option($option, $args);
3684
-        }
3685
-    }
3686
-
3687
-
3688
-    /**
3689
-     * set_per_page_screen_option
3690
-     * All this does is make sure that WordPress saves any per_page screen options (if set) for the current page.
3691
-     * we have to do this rather than running inside the 'set-screen-options' hook because it runs earlier than
3692
-     * admin_menu.
3693
-     *
3694
-     * @return void
3695
-     */
3696
-    private function _set_per_page_screen_options()
3697
-    {
3698
-        if ($this->request->requestParamIsSet('wp_screen_options')) {
3699
-            check_admin_referer('screen-options-nonce', 'screenoptionnonce');
3700
-            if (! $user = wp_get_current_user()) {
3701
-                return;
3702
-            }
3703
-            $option = $this->request->getRequestParam('wp_screen_options[option]', '', 'key');
3704
-            if (! $option) {
3705
-                return;
3706
-            }
3707
-            $value  = $this->request->getRequestParam('wp_screen_options[value]', 0, 'int');
3708
-            $map_option = $option;
3709
-            $option     = str_replace('-', '_', $option);
3710
-            switch ($map_option) {
3711
-                case $this->_current_page . '_' . $this->_current_view . '_per_page':
3712
-                    $max_value = apply_filters(
3713
-                        'FHEE__EE_Admin_Page___set_per_page_screen_options__max_value',
3714
-                        999,
3715
-                        $this->_current_page,
3716
-                        $this->_current_view
3717
-                    );
3718
-                    if ($value < 1) {
3719
-                        return;
3720
-                    }
3721
-                    $value = min($value, $max_value);
3722
-                    break;
3723
-                default:
3724
-                    $value = apply_filters(
3725
-                        'FHEE__EE_Admin_Page___set_per_page_screen_options__value',
3726
-                        false,
3727
-                        $option,
3728
-                        $value
3729
-                    );
3730
-                    if (false === $value) {
3731
-                        return;
3732
-                    }
3733
-                    break;
3734
-            }
3735
-            update_user_meta($user->ID, $option, $value);
3736
-            wp_safe_redirect(remove_query_arg(['pagenum', 'apage', 'paged'], wp_get_referer()));
3737
-            exit;
3738
-        }
3739
-    }
3740
-
3741
-
3742
-    /**
3743
-     * This just allows for setting the $_template_args property if it needs to be set outside the object
3744
-     *
3745
-     * @param array $data array that will be assigned to template args.
3746
-     */
3747
-    public function set_template_args($data)
3748
-    {
3749
-        $this->_template_args = array_merge($this->_template_args, (array) $data);
3750
-    }
3751
-
3752
-
3753
-    /**
3754
-     * This makes available the WP transient system for temporarily moving data between routes
3755
-     *
3756
-     * @param string $route             the route that should receive the transient
3757
-     * @param array  $data              the data that gets sent
3758
-     * @param bool   $notices           If this is for notices then we use this to indicate so, otherwise its just a
3759
-     *                                  normal route transient.
3760
-     * @param bool   $skip_route_verify Used to indicate we want to skip route verification.  This is usually ONLY used
3761
-     *                                  when we are adding a transient before page_routes have been defined.
3762
-     * @return void
3763
-     * @throws EE_Error
3764
-     */
3765
-    protected function _add_transient($route, $data, $notices = false, $skip_route_verify = false)
3766
-    {
3767
-        $user_id = get_current_user_id();
3768
-        if (! $skip_route_verify) {
3769
-            $this->_verify_route($route);
3770
-        }
3771
-        // now let's set the string for what kind of transient we're setting
3772
-        $transient = $notices
3773
-            ? 'ee_rte_n_tx_' . $route . '_' . $user_id
3774
-            : 'rte_tx_' . $route . '_' . $user_id;
3775
-        $data      = $notices ? ['notices' => $data] : $data;
3776
-        // is there already a transient for this route?  If there is then let's ADD to that transient
3777
-        $existing = is_multisite() && is_network_admin()
3778
-            ? get_site_transient($transient)
3779
-            : get_transient($transient);
3780
-        if ($existing) {
3781
-            $data = array_merge((array) $data, (array) $existing);
3782
-        }
3783
-        if (is_multisite() && is_network_admin()) {
3784
-            set_site_transient($transient, $data, 8);
3785
-        } else {
3786
-            set_transient($transient, $data, 8);
3787
-        }
3788
-    }
3789
-
3790
-
3791
-    /**
3792
-     * this retrieves the temporary transient that has been set for moving data between routes.
3793
-     *
3794
-     * @param bool   $notices true we get notices transient. False we just return normal route transient
3795
-     * @param string $route
3796
-     * @return mixed data
3797
-     */
3798
-    protected function _get_transient($notices = false, $route = '')
3799
-    {
3800
-        $user_id   = get_current_user_id();
3801
-        $route     = ! $route ? $this->_req_action : $route;
3802
-        $transient = $notices
3803
-            ? 'ee_rte_n_tx_' . $route . '_' . $user_id
3804
-            : 'rte_tx_' . $route . '_' . $user_id;
3805
-        $data      = is_multisite() && is_network_admin()
3806
-            ? get_site_transient($transient)
3807
-            : get_transient($transient);
3808
-        // delete transient after retrieval (just in case it hasn't expired);
3809
-        if (is_multisite() && is_network_admin()) {
3810
-            delete_site_transient($transient);
3811
-        } else {
3812
-            delete_transient($transient);
3813
-        }
3814
-        return $notices && isset($data['notices']) ? $data['notices'] : $data;
3815
-    }
3816
-
3817
-
3818
-    /**
3819
-     * The purpose of this method is just to run garbage collection on any EE transients that might have expired but
3820
-     * would not be called later. This will be assigned to run on a specific EE Admin page. (place the method in the
3821
-     * default route callback on the EE_Admin page you want it run.)
3822
-     *
3823
-     * @return void
3824
-     */
3825
-    protected function _transient_garbage_collection()
3826
-    {
3827
-        global $wpdb;
3828
-        // retrieve all existing transients
3829
-        $query =
3830
-            "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE '%rte_tx_%' OR option_name LIKE '%rte_n_tx_%'";
3831
-        if ($results = $wpdb->get_results($query)) {
3832
-            foreach ($results as $result) {
3833
-                $transient = str_replace('_transient_', '', $result->option_name);
3834
-                get_transient($transient);
3835
-                if (is_multisite() && is_network_admin()) {
3836
-                    get_site_transient($transient);
3837
-                }
3838
-            }
3839
-        }
3840
-    }
3841
-
3842
-
3843
-    /**
3844
-     * get_view
3845
-     *
3846
-     * @return string content of _view property
3847
-     */
3848
-    public function get_view()
3849
-    {
3850
-        return $this->_view;
3851
-    }
3852
-
3853
-
3854
-    /**
3855
-     * getter for the protected $_views property
3856
-     *
3857
-     * @return array
3858
-     */
3859
-    public function get_views()
3860
-    {
3861
-        return $this->_views;
3862
-    }
3863
-
3864
-
3865
-    /**
3866
-     * get_current_page
3867
-     *
3868
-     * @return string _current_page property value
3869
-     */
3870
-    public function get_current_page()
3871
-    {
3872
-        return $this->_current_page;
3873
-    }
3874
-
3875
-
3876
-    /**
3877
-     * get_current_view
3878
-     *
3879
-     * @return string _current_view property value
3880
-     */
3881
-    public function get_current_view()
3882
-    {
3883
-        return $this->_current_view;
3884
-    }
3885
-
3886
-
3887
-    /**
3888
-     * get_current_screen
3889
-     *
3890
-     * @return object The current WP_Screen object
3891
-     */
3892
-    public function get_current_screen()
3893
-    {
3894
-        return $this->_current_screen;
3895
-    }
3896
-
3897
-
3898
-    /**
3899
-     * get_current_page_view_url
3900
-     *
3901
-     * @return string This returns the url for the current_page_view.
3902
-     */
3903
-    public function get_current_page_view_url()
3904
-    {
3905
-        return $this->_current_page_view_url;
3906
-    }
3907
-
3908
-
3909
-    /**
3910
-     * just returns the Request
3911
-     *
3912
-     * @return RequestInterface
3913
-     */
3914
-    public function get_request()
3915
-    {
3916
-        return $this->request;
3917
-    }
3918
-
3919
-
3920
-    /**
3921
-     * just returns the _req_data property
3922
-     *
3923
-     * @return array
3924
-     */
3925
-    public function get_request_data()
3926
-    {
3927
-        return $this->request->requestParams();
3928
-    }
3929
-
3930
-
3931
-    /**
3932
-     * returns the _req_data protected property
3933
-     *
3934
-     * @return string
3935
-     */
3936
-    public function get_req_action()
3937
-    {
3938
-        return $this->_req_action;
3939
-    }
3940
-
3941
-
3942
-    /**
3943
-     * @return bool  value of $_is_caf property
3944
-     */
3945
-    public function is_caf()
3946
-    {
3947
-        return $this->_is_caf;
3948
-    }
3949
-
3950
-
3951
-    /**
3952
-     * @return mixed
3953
-     */
3954
-    public function default_espresso_metaboxes()
3955
-    {
3956
-        return $this->_default_espresso_metaboxes;
3957
-    }
3958
-
3959
-
3960
-    /**
3961
-     * @return mixed
3962
-     */
3963
-    public function admin_base_url()
3964
-    {
3965
-        return $this->_admin_base_url;
3966
-    }
3967
-
3968
-
3969
-    /**
3970
-     * @return mixed
3971
-     */
3972
-    public function wp_page_slug()
3973
-    {
3974
-        return $this->_wp_page_slug;
3975
-    }
3976
-
3977
-
3978
-    /**
3979
-     * updates  espresso configuration settings
3980
-     *
3981
-     * @param string                   $tab
3982
-     * @param EE_Config_Base|EE_Config $config
3983
-     * @param string                   $file file where error occurred
3984
-     * @param string                   $func function  where error occurred
3985
-     * @param string                   $line line no where error occurred
3986
-     * @return boolean
3987
-     */
3988
-    protected function _update_espresso_configuration($tab, $config, $file = '', $func = '', $line = '')
3989
-    {
3990
-        // remove any options that are NOT going to be saved with the config settings.
3991
-        if (isset($config->core->ee_ueip_optin)) {
3992
-            // TODO: remove the following two lines and make sure values are migrated from 3.1
3993
-            update_option('ee_ueip_optin', $config->core->ee_ueip_optin);
3994
-            update_option('ee_ueip_has_notified', true);
3995
-        }
3996
-        // and save it (note we're also doing the network save here)
3997
-        $net_saved    = ! is_main_site() || EE_Network_Config::instance()->update_config(false, false);
3998
-        $config_saved = EE_Config::instance()->update_espresso_config(false, false);
3999
-        if ($config_saved && $net_saved) {
4000
-            EE_Error::add_success(sprintf(esc_html__('"%s" have been successfully updated.', 'event_espresso'), $tab));
4001
-            return true;
4002
-        }
4003
-        EE_Error::add_error(sprintf(esc_html__('The "%s" were not updated.', 'event_espresso'), $tab), $file, $func, $line);
4004
-        return false;
4005
-    }
4006
-
4007
-
4008
-    /**
4009
-     * Returns an array to be used for EE_FOrm_Fields.helper.php's select_input as the $values argument.
4010
-     *
4011
-     * @return array
4012
-     */
4013
-    public function get_yes_no_values()
4014
-    {
4015
-        return $this->_yes_no_values;
4016
-    }
4017
-
4018
-
4019
-    /**
4020
-     * @return string
4021
-     * @throws ReflectionException
4022
-     * @since $VID:$
4023
-     */
4024
-    protected function _get_dir()
4025
-    {
4026
-        $reflector = new ReflectionClass($this->class_name);
4027
-        return dirname($reflector->getFileName());
4028
-    }
4029
-
4030
-
4031
-    /**
4032
-     * A helper for getting a "next link".
4033
-     *
4034
-     * @param string $url   The url to link to
4035
-     * @param string $class The class to use.
4036
-     * @return string
4037
-     */
4038
-    protected function _next_link($url, $class = 'dashicons dashicons-arrow-right')
4039
-    {
4040
-        return '<a class="' . $class . '" href="' . $url . '"></a>';
4041
-    }
4042
-
4043
-
4044
-    /**
4045
-     * A helper for getting a "previous link".
4046
-     *
4047
-     * @param string $url   The url to link to
4048
-     * @param string $class The class to use.
4049
-     * @return string
4050
-     */
4051
-    protected function _previous_link($url, $class = 'dashicons dashicons-arrow-left')
4052
-    {
4053
-        return '<a class="' . $class . '" href="' . $url . '"></a>';
4054
-    }
4055
-
4056
-
4057
-
4058
-
4059
-
4060
-
4061
-
4062
-    // below are some messages related methods that should be available across the EE_Admin system.  Note, these methods are NOT page specific
4063
-
4064
-
4065
-    /**
4066
-     * This processes an request to resend a registration and assumes we have a _REG_ID for doing so. So if the caller
4067
-     * 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
4068
-     * _req_data array.
4069
-     *
4070
-     * @return bool success/fail
4071
-     * @throws EE_Error
4072
-     * @throws InvalidArgumentException
4073
-     * @throws ReflectionException
4074
-     * @throws InvalidDataTypeException
4075
-     * @throws InvalidInterfaceException
4076
-     */
4077
-    protected function _process_resend_registration()
4078
-    {
4079
-        $this->_template_args['success'] = EED_Messages::process_resend($this->_req_data);
4080
-        do_action(
4081
-            'AHEE__EE_Admin_Page___process_resend_registration',
4082
-            $this->_template_args['success'],
4083
-            $this->request->requestParams()
4084
-        );
4085
-        return $this->_template_args['success'];
4086
-    }
4087
-
4088
-
4089
-    /**
4090
-     * This automatically processes any payment message notifications when manual payment has been applied.
4091
-     *
4092
-     * @param EE_Payment $payment
4093
-     * @return bool success/fail
4094
-     */
4095
-    protected function _process_payment_notification(EE_Payment $payment)
4096
-    {
4097
-        add_filter('FHEE__EE_Payment_Processor__process_registration_payments__display_notifications', '__return_true');
4098
-        do_action('AHEE__EE_Admin_Page___process_admin_payment_notification', $payment);
4099
-        $this->_template_args['success'] = apply_filters(
4100
-            'FHEE__EE_Admin_Page___process_admin_payment_notification__success',
4101
-            false,
4102
-            $payment
4103
-        );
4104
-        return $this->_template_args['success'];
4105
-    }
4106
-
4107
-
4108
-    /**
4109
-     * @param EEM_Base      $entity_model
4110
-     * @param string        $entity_PK_name name of the primary key field used as a request param, ie: id, ID, etc
4111
-     * @param string        $action         one of the EE_Admin_List_Table::ACTION_* constants: delete, restore, trash
4112
-     * @param string        $delete_column  name of the field that denotes whether entity is trashed
4113
-     * @param callable|null $callback       called after entity is trashed, restored, or deleted
4114
-     * @return int|float
4115
-     * @throws EE_Error
4116
-     */
4117
-    protected function trashRestoreDeleteEntities(
4118
-        EEM_Base $entity_model,
4119
-        $entity_PK_name,
4120
-        $action = EE_Admin_List_Table::ACTION_DELETE,
4121
-        $delete_column = '',
4122
-        callable $callback = null
4123
-    ) {
4124
-        $entity_PK      = $entity_model->get_primary_key_field();
4125
-        $entity_PK_name = $entity_PK_name ?: $entity_PK->get_name();
4126
-        $entity_PK_type = $this->resolveEntityFieldDataType($entity_PK);
4127
-        // grab ID if deleting a single entity
4128
-        if ($this->request->requestParamIsSet($entity_PK_name)) {
4129
-            $ID = $this->request->getRequestParam($entity_PK_name, 0, $entity_PK_type);
4130
-            return $this->trashRestoreDeleteEntity($entity_model, $ID, $action, $delete_column, $callback) ? 1 : 0;
4131
-        }
4132
-        // or grab checkbox array if bulk deleting
4133
-        $checkboxes = $this->request->getRequestParam('checkbox', [], $entity_PK_type, true);
4134
-        if (empty($checkboxes)) {
4135
-            return 0;
4136
-        }
4137
-        $success = 0;
4138
-        $IDs     = array_keys($checkboxes);
4139
-        // cycle thru bulk action checkboxes
4140
-        foreach ($IDs as $ID) {
4141
-            // increment $success
4142
-            if ($this->trashRestoreDeleteEntity($entity_model, $ID, $action, $delete_column, $callback)) {
4143
-                $success++;
4144
-            }
4145
-        }
4146
-        $count = (int) count($checkboxes);
4147
-        // if multiple entities were deleted successfully, then $deleted will be full count of deletions,
4148
-        // otherwise it will be a fraction of ( actual deletions / total entities to be deleted )
4149
-        return $success === $count ? $count : $success / $count;
4150
-    }
4151
-
4152
-
4153
-    /**
4154
-     * @param EE_Primary_Key_Field_Base $entity_PK
4155
-     * @return string
4156
-     * @throws EE_Error
4157
-     * @since   4.10.30.p
4158
-     */
4159
-    private function resolveEntityFieldDataType(EE_Primary_Key_Field_Base $entity_PK)
4160
-    {
4161
-        $entity_PK_type = $entity_PK->getSchemaType();
4162
-        switch ($entity_PK_type) {
4163
-            case 'boolean':
4164
-                return 'bool';
4165
-            case 'integer':
4166
-                return 'int';
4167
-            case 'number':
4168
-                return 'float';
4169
-            case 'string':
4170
-                return 'string';
4171
-        }
4172
-        throw new RuntimeException(
4173
-            sprintf(
4174
-                esc_html__(
4175
-                    '"%1$s" is an invalid schema type for the %2$s primary key.',
4176
-                    'event_espresso'
4177
-                ),
4178
-                $entity_PK_type,
4179
-                $entity_PK->get_name()
4180
-            )
4181
-        );
4182
-    }
4183
-
4184
-
4185
-    /**
4186
-     * @param EEM_Base      $entity_model
4187
-     * @param int|string    $entity_ID
4188
-     * @param string        $action        one of the EE_Admin_List_Table::ACTION_* constants: delete, restore, trash
4189
-     * @param string        $delete_column name of the field that denotes whether entity is trashed
4190
-     * @param callable|null $callback      called after entity is trashed, restored, or deleted
4191
-     * @return bool
4192
-     */
4193
-    protected function trashRestoreDeleteEntity(
4194
-        EEM_Base $entity_model,
4195
-        $entity_ID,
4196
-        string $action,
4197
-        string $delete_column,
4198
-        ?callable $callback = null
4199
-    ): bool {
4200
-        $entity_ID = absint($entity_ID);
4201
-        if (! $entity_ID) {
4202
-            $this->trashRestoreDeleteError($action, $entity_model);
4203
-        }
4204
-        $result = 0;
4205
-        try {
4206
-            $entity = $entity_model->get_one_by_ID($entity_ID);
4207
-            if (! $entity instanceof EE_Base_Class) {
4208
-                throw new DomainException(
4209
-                    sprintf(
4210
-                        esc_html__(
4211
-                            'Missing or invalid %1$s entity with ID of "%2$s" returned from db.',
4212
-                            'event_espresso'
4213
-                        ),
4214
-                        str_replace('EEM_', '', $entity_model->get_this_model_name()),
4215
-                        $entity_ID
4216
-                    )
4217
-                );
4218
-            }
4219
-            switch ($action) {
4220
-                case EE_Admin_List_Table::ACTION_DELETE:
4221
-                    $result = (bool) $entity->delete_permanently();
4222
-                    break;
4223
-                case EE_Admin_List_Table::ACTION_RESTORE:
4224
-                    $result = $entity->delete_or_restore(false);
4225
-                    break;
4226
-                case EE_Admin_List_Table::ACTION_TRASH:
4227
-                    $result = $entity->delete_or_restore();
4228
-                    break;
4229
-            }
4230
-        } catch (Exception $exception) {
4231
-            $this->trashRestoreDeleteError($action, $entity_model, $exception);
4232
-        }
4233
-        if (is_callable($callback)) {
4234
-            call_user_func_array($callback, [$entity_model, $entity_ID, $action, $result, $delete_column]);
4235
-        }
4236
-        return $result;
4237
-    }
4238
-
4239
-
4240
-    /**
4241
-     * @param EEM_Base $entity_model
4242
-     * @param string   $delete_column
4243
-     * @since 4.10.30.p
4244
-     */
4245
-    private function validateDeleteColumn(EEM_Base $entity_model, $delete_column)
4246
-    {
4247
-        if (empty($delete_column)) {
4248
-            throw new DomainException(
4249
-                sprintf(
4250
-                    esc_html__(
4251
-                        'You need to specify the name of the "delete column" on the %2$s model, in order to trash or restore an entity.',
4252
-                        'event_espresso'
4253
-                    ),
4254
-                    $entity_model->get_this_model_name()
4255
-                )
4256
-            );
4257
-        }
4258
-        if (! $entity_model->has_field($delete_column)) {
4259
-            throw new DomainException(
4260
-                sprintf(
4261
-                    esc_html__(
4262
-                        'The %1$s field does not exist on the %2$s model.',
4263
-                        'event_espresso'
4264
-                    ),
4265
-                    $delete_column,
4266
-                    $entity_model->get_this_model_name()
4267
-                )
4268
-            );
4269
-        }
4270
-    }
4271
-
4272
-
4273
-    /**
4274
-     * @param EEM_Base       $entity_model
4275
-     * @param Exception|null $exception
4276
-     * @param string         $action
4277
-     * @since 4.10.30.p
4278
-     */
4279
-    private function trashRestoreDeleteError($action, EEM_Base $entity_model, Exception $exception = null)
4280
-    {
4281
-        if ($exception instanceof Exception) {
4282
-            throw new RuntimeException(
4283
-                sprintf(
4284
-                    esc_html__(
4285
-                        'Could not %1$s the %2$s because the following error occurred: %3$s',
4286
-                        'event_espresso'
4287
-                    ),
4288
-                    $action,
4289
-                    $entity_model->get_this_model_name(),
4290
-                    $exception->getMessage()
4291
-                )
4292
-            );
4293
-        }
4294
-        throw new RuntimeException(
4295
-            sprintf(
4296
-                esc_html__(
4297
-                    'Could not %1$s the %2$s because an invalid ID was received.',
4298
-                    'event_espresso'
4299
-                ),
4300
-                $action,
4301
-                $entity_model->get_this_model_name()
4302
-            )
4303
-        );
4304
-    }
3326
+		// add nonce
3327
+		$nonce                                             =
3328
+			wp_nonce_field($route . '_nonce', $route . '_nonce', false, false);
3329
+		$this->_template_args['before_admin_page_content'] .= "\n\t" . $nonce;
3330
+		// add REQUIRED form action
3331
+		$hidden_fields = [
3332
+			'action' => ['type' => 'hidden', 'value' => $route],
3333
+		];
3334
+		// merge arrays
3335
+		$hidden_fields = is_array($additional_hidden_fields)
3336
+			? array_merge($hidden_fields, $additional_hidden_fields)
3337
+			: $hidden_fields;
3338
+		// generate form fields
3339
+		$form_fields = $this->_generate_admin_form_fields($hidden_fields, 'array');
3340
+		// add fields to form
3341
+		foreach ((array) $form_fields as $form_field) {
3342
+			$this->_template_args['before_admin_page_content'] .= "\n\t" . $form_field['field'];
3343
+		}
3344
+		// close form
3345
+		$this->_template_args['after_admin_page_content'] = '</form>';
3346
+	}
3347
+
3348
+
3349
+	/**
3350
+	 * Public Wrapper for _redirect_after_action() method since its
3351
+	 * discovered it would be useful for external code to have access.
3352
+	 *
3353
+	 * @param bool   $success
3354
+	 * @param string $what
3355
+	 * @param string $action_desc
3356
+	 * @param array  $query_args
3357
+	 * @param bool   $override_overwrite
3358
+	 * @throws EE_Error
3359
+	 * @see   EE_Admin_Page::_redirect_after_action() for params.
3360
+	 * @since 4.5.0
3361
+	 */
3362
+	public function redirect_after_action(
3363
+		$success = false,
3364
+		$what = 'item',
3365
+		$action_desc = 'processed',
3366
+		$query_args = [],
3367
+		$override_overwrite = false
3368
+	) {
3369
+		$this->_redirect_after_action(
3370
+			$success,
3371
+			$what,
3372
+			$action_desc,
3373
+			$query_args,
3374
+			$override_overwrite
3375
+		);
3376
+	}
3377
+
3378
+
3379
+	/**
3380
+	 * Helper method for merging existing request data with the returned redirect url.
3381
+	 *
3382
+	 * This is typically used for redirects after an action so that if the original view was a filtered view those
3383
+	 * filters are still applied.
3384
+	 *
3385
+	 * @param array $new_route_data
3386
+	 * @return array
3387
+	 */
3388
+	protected function mergeExistingRequestParamsWithRedirectArgs(array $new_route_data)
3389
+	{
3390
+		foreach ($this->request->requestParams() as $ref => $value) {
3391
+			// unset nonces
3392
+			if (strpos($ref, 'nonce') !== false) {
3393
+				$this->request->unSetRequestParam($ref);
3394
+				continue;
3395
+			}
3396
+			// urlencode values.
3397
+			$value = is_array($value) ? array_map('urlencode', $value) : urlencode($value);
3398
+			$this->request->setRequestParam($ref, $value);
3399
+		}
3400
+		return array_merge($this->request->requestParams(), $new_route_data);
3401
+	}
3402
+
3403
+
3404
+	/**
3405
+	 * @param int|float|string $success      - whether success was for two or more records, or just one, or none
3406
+	 * @param string           $what         - what the action was performed on
3407
+	 * @param string           $action_desc  - what was done ie: updated, deleted, etc
3408
+	 * @param array $query_args              - an array of query_args to be added to the URL to redirect to
3409
+	 * @param BOOL $override_overwrite       - by default all EE_Error::success messages are overwritten,
3410
+	 *                                         this allows you to override this so that they show.
3411
+	 * @return void
3412
+	 * @throws EE_Error
3413
+	 * @throws InvalidArgumentException
3414
+	 * @throws InvalidDataTypeException
3415
+	 * @throws InvalidInterfaceException
3416
+	 */
3417
+	protected function _redirect_after_action(
3418
+		$success = 0,
3419
+		string $what = 'item',
3420
+		string $action_desc = 'processed',
3421
+		array $query_args = [],
3422
+		bool $override_overwrite = false
3423
+	) {
3424
+		do_action('AHEE_log', __FILE__, __FUNCTION__, '');
3425
+		$notices      = EE_Error::get_notices(false);
3426
+		// overwrite default success messages //BUT ONLY if overwrite not overridden
3427
+		if (! $override_overwrite || ! empty($notices['errors'])) {
3428
+			EE_Error::overwrite_success();
3429
+		}
3430
+		if (! $override_overwrite && ! empty($what) && ! empty($action_desc) && empty($notices['errors'])) {
3431
+			// how many records affected ? more than one record ? or just one ?
3432
+			EE_Error::add_success(
3433
+				sprintf(
3434
+					esc_html(
3435
+						_n(
3436
+							'The "%1$s" has been successfully %2$s.',
3437
+							'The "%1$s" have been successfully %2$s.',
3438
+							$success,
3439
+							'event_espresso'
3440
+						)
3441
+					),
3442
+					$what,
3443
+					$action_desc
3444
+				),
3445
+				__FILE__,
3446
+				__FUNCTION__,
3447
+				__LINE__
3448
+			);
3449
+		}
3450
+		// check that $query_args isn't something crazy
3451
+		if (! is_array($query_args)) {
3452
+			$query_args = [];
3453
+		}
3454
+		/**
3455
+		 * Allow injecting actions before the query_args are modified for possible different
3456
+		 * redirections on save and close actions
3457
+		 *
3458
+		 * @param array $query_args       The original query_args array coming into the
3459
+		 *                                method.
3460
+		 * @since 4.2.0
3461
+		 */
3462
+		do_action(
3463
+			"AHEE__{$this->class_name}___redirect_after_action__before_redirect_modification_{$this->_req_action}",
3464
+			$query_args
3465
+		);
3466
+		// set redirect url.
3467
+		// Note if there is a "page" index in the $query_args then we go with vanilla admin.php route,
3468
+		// otherwise we go with whatever is set as the _admin_base_url
3469
+		$redirect_url = isset($query_args['page']) ? admin_url('admin.php') : $this->_admin_base_url;
3470
+		// calculate where we're going (if we have a "save and close" button pushed)
3471
+		if (
3472
+			$this->request->requestParamIsSet('save_and_close')
3473
+			&& $this->request->requestParamIsSet('save_and_close_referrer')
3474
+		) {
3475
+			// even though we have the save_and_close referrer, we need to parse the url for the action in order to generate a nonce
3476
+			$parsed_url = parse_url($this->request->getRequestParam('save_and_close_referrer', '', 'url'));
3477
+			// regenerate query args array from referrer URL
3478
+			parse_str($parsed_url['query'], $query_args);
3479
+			// correct page and action will be in the query args now
3480
+			$redirect_url = admin_url('admin.php');
3481
+		}
3482
+		// merge any default query_args set in _default_route_query_args property
3483
+		if (! empty($this->_default_route_query_args) && ! $this->_is_UI_request) {
3484
+			$args_to_merge = [];
3485
+			foreach ($this->_default_route_query_args as $query_param => $query_value) {
3486
+				// is there a wp_referer array in our _default_route_query_args property?
3487
+				if ($query_param === 'wp_referer') {
3488
+					$query_value = (array) $query_value;
3489
+					foreach ($query_value as $reference => $value) {
3490
+						if (strpos($reference, 'nonce') !== false) {
3491
+							continue;
3492
+						}
3493
+						// finally we will override any arguments in the referer with
3494
+						// what might be set on the _default_route_query_args array.
3495
+						if (isset($this->_default_route_query_args[ $reference ])) {
3496
+							$args_to_merge[ $reference ] = urlencode($this->_default_route_query_args[ $reference ]);
3497
+						} else {
3498
+							$args_to_merge[ $reference ] = urlencode($value);
3499
+						}
3500
+					}
3501
+					continue;
3502
+				}
3503
+				$args_to_merge[ $query_param ] = $query_value;
3504
+			}
3505
+			// now let's merge these arguments but override with what was specifically sent in to the
3506
+			// redirect.
3507
+			$query_args = array_merge($args_to_merge, $query_args);
3508
+		}
3509
+		$this->_process_notices($query_args);
3510
+		// generate redirect url
3511
+		// if redirecting to anything other than the main page, add a nonce
3512
+		if (isset($query_args['action'])) {
3513
+			// manually generate wp_nonce and merge that with the query vars
3514
+			// becuz the wp_nonce_url function wrecks havoc on some vars
3515
+			$query_args['_wpnonce'] = wp_create_nonce($query_args['action'] . '_nonce');
3516
+		}
3517
+		// we're adding some hooks and filters in here for processing any things just before redirects
3518
+		// (example: an admin page has done an insert or update and we want to run something after that).
3519
+		do_action('AHEE_redirect_' . $this->class_name . $this->_req_action, $query_args);
3520
+		$redirect_url = apply_filters(
3521
+			'FHEE_redirect_' . $this->class_name . $this->_req_action,
3522
+			EE_Admin_Page::add_query_args_and_nonce($query_args, $redirect_url),
3523
+			$query_args
3524
+		);
3525
+		// check if we're doing ajax.  If we are then lets just return the results and js can handle how it wants.
3526
+		if ($this->request->isAjax()) {
3527
+			$default_data                    = [
3528
+				'close'        => true,
3529
+				'redirect_url' => $redirect_url,
3530
+				'where'        => 'main',
3531
+				'what'         => 'append',
3532
+			];
3533
+			$this->_template_args['success'] = $success;
3534
+			$this->_template_args['data']    = ! empty($this->_template_args['data']) ? array_merge(
3535
+				$default_data,
3536
+				$this->_template_args['data']
3537
+			) : $default_data;
3538
+			$this->_return_json();
3539
+		}
3540
+		wp_safe_redirect($redirect_url);
3541
+		exit();
3542
+	}
3543
+
3544
+
3545
+	/**
3546
+	 * process any notices before redirecting (or returning ajax request)
3547
+	 * This method sets the $this->_template_args['notices'] attribute;
3548
+	 *
3549
+	 * @param array $query_args         any query args that need to be used for notice transient ('action')
3550
+	 * @param bool  $skip_route_verify  This is typically used when we are processing notices REALLY early and
3551
+	 *                                  page_routes haven't been defined yet.
3552
+	 * @param bool  $sticky_notices     This is used to flag that regardless of whether this is doing_ajax or not, we
3553
+	 *                                  still save a transient for the notice.
3554
+	 * @return void
3555
+	 * @throws EE_Error
3556
+	 * @throws InvalidArgumentException
3557
+	 * @throws InvalidDataTypeException
3558
+	 * @throws InvalidInterfaceException
3559
+	 */
3560
+	protected function _process_notices($query_args = [], $skip_route_verify = false, $sticky_notices = true)
3561
+	{
3562
+		// first let's set individual error properties if doing_ajax and the properties aren't already set.
3563
+		if ($this->request->isAjax()) {
3564
+			$notices = EE_Error::get_notices(false);
3565
+			if (empty($this->_template_args['success'])) {
3566
+				$this->_template_args['success'] = isset($notices['success']) ? $notices['success'] : false;
3567
+			}
3568
+			if (empty($this->_template_args['errors'])) {
3569
+				$this->_template_args['errors'] = isset($notices['errors']) ? $notices['errors'] : false;
3570
+			}
3571
+			if (empty($this->_template_args['attention'])) {
3572
+				$this->_template_args['attention'] = isset($notices['attention']) ? $notices['attention'] : false;
3573
+			}
3574
+		}
3575
+		$this->_template_args['notices'] = EE_Error::get_notices();
3576
+		// IF this isn't ajax we need to create a transient for the notices using the route (however, overridden if $sticky_notices == true)
3577
+		if (! $this->request->isAjax() || $sticky_notices) {
3578
+			$route = isset($query_args['action']) ? $query_args['action'] : 'default';
3579
+			$this->_add_transient(
3580
+				$route,
3581
+				$this->_template_args['notices'],
3582
+				true,
3583
+				$skip_route_verify
3584
+			);
3585
+		}
3586
+	}
3587
+
3588
+
3589
+	/**
3590
+	 * get_action_link_or_button
3591
+	 * returns the button html for adding, editing, or deleting an item (depending on given type)
3592
+	 *
3593
+	 * @param string $action        use this to indicate which action the url is generated with.
3594
+	 * @param string $type          accepted strings must be defined in the $_labels['button'] array(as the key)
3595
+	 *                              property.
3596
+	 * @param array  $extra_request if the button requires extra params you can include them in $key=>$value pairs.
3597
+	 * @param string $class         Use this to give the class for the button. Defaults to 'button--primary'
3598
+	 * @param string $base_url      If this is not provided
3599
+	 *                              the _admin_base_url will be used as the default for the button base_url.
3600
+	 *                              Otherwise this value will be used.
3601
+	 * @param bool   $exclude_nonce If true then no nonce will be in the generated button link.
3602
+	 * @return string
3603
+	 * @throws InvalidArgumentException
3604
+	 * @throws InvalidInterfaceException
3605
+	 * @throws InvalidDataTypeException
3606
+	 * @throws EE_Error
3607
+	 */
3608
+	public function get_action_link_or_button(
3609
+		$action,
3610
+		$type = 'add',
3611
+		$extra_request = [],
3612
+		$class = 'button button--primary',
3613
+		$base_url = '',
3614
+		$exclude_nonce = false
3615
+	) {
3616
+		// first let's validate the action (if $base_url is FALSE otherwise validation will happen further along)
3617
+		if (empty($base_url) && ! isset($this->_page_routes[ $action ])) {
3618
+			throw new EE_Error(
3619
+				sprintf(
3620
+					esc_html__(
3621
+						'There is no page route for given action for the button.  This action was given: %s',
3622
+						'event_espresso'
3623
+					),
3624
+					$action
3625
+				)
3626
+			);
3627
+		}
3628
+		if (! isset($this->_labels['buttons'][ $type ])) {
3629
+			throw new EE_Error(
3630
+				sprintf(
3631
+					esc_html__(
3632
+						'There is no label for the given button type (%s). Labels are set in the <code>_page_config</code> property.',
3633
+						'event_espresso'
3634
+					),
3635
+					$type
3636
+				)
3637
+			);
3638
+		}
3639
+		// finally check user access for this button.
3640
+		$has_access = $this->check_user_access($action, true);
3641
+		if (! $has_access) {
3642
+			return '';
3643
+		}
3644
+		$_base_url  = ! $base_url ? $this->_admin_base_url : $base_url;
3645
+		$query_args = [
3646
+			'action' => $action,
3647
+		];
3648
+		// merge extra_request args but make sure our original action takes precedence and doesn't get overwritten.
3649
+		if (! empty($extra_request)) {
3650
+			$query_args = array_merge($extra_request, $query_args);
3651
+		}
3652
+		$url = EE_Admin_Page::add_query_args_and_nonce($query_args, $_base_url, false, $exclude_nonce);
3653
+		return EEH_Template::get_button_or_link($url, $this->_labels['buttons'][ $type ], $class);
3654
+	}
3655
+
3656
+
3657
+	/**
3658
+	 * _per_page_screen_option
3659
+	 * Utility function for adding in a per_page_option in the screen_options_dropdown.
3660
+	 *
3661
+	 * @return void
3662
+	 * @throws InvalidArgumentException
3663
+	 * @throws InvalidInterfaceException
3664
+	 * @throws InvalidDataTypeException
3665
+	 */
3666
+	protected function _per_page_screen_option()
3667
+	{
3668
+		$option = 'per_page';
3669
+		$args   = [
3670
+			'label'   => apply_filters(
3671
+				'FHEE__EE_Admin_Page___per_page_screen_options___label',
3672
+				$this->_admin_page_title,
3673
+				$this
3674
+			),
3675
+			'default' => (int) apply_filters(
3676
+				'FHEE__EE_Admin_Page___per_page_screen_options__default',
3677
+				20
3678
+			),
3679
+			'option'  => $this->_current_page . '_' . $this->_current_view . '_per_page',
3680
+		];
3681
+		// ONLY add the screen option if the user has access to it.
3682
+		if ($this->check_user_access($this->_current_view, true)) {
3683
+			add_screen_option($option, $args);
3684
+		}
3685
+	}
3686
+
3687
+
3688
+	/**
3689
+	 * set_per_page_screen_option
3690
+	 * All this does is make sure that WordPress saves any per_page screen options (if set) for the current page.
3691
+	 * we have to do this rather than running inside the 'set-screen-options' hook because it runs earlier than
3692
+	 * admin_menu.
3693
+	 *
3694
+	 * @return void
3695
+	 */
3696
+	private function _set_per_page_screen_options()
3697
+	{
3698
+		if ($this->request->requestParamIsSet('wp_screen_options')) {
3699
+			check_admin_referer('screen-options-nonce', 'screenoptionnonce');
3700
+			if (! $user = wp_get_current_user()) {
3701
+				return;
3702
+			}
3703
+			$option = $this->request->getRequestParam('wp_screen_options[option]', '', 'key');
3704
+			if (! $option) {
3705
+				return;
3706
+			}
3707
+			$value  = $this->request->getRequestParam('wp_screen_options[value]', 0, 'int');
3708
+			$map_option = $option;
3709
+			$option     = str_replace('-', '_', $option);
3710
+			switch ($map_option) {
3711
+				case $this->_current_page . '_' . $this->_current_view . '_per_page':
3712
+					$max_value = apply_filters(
3713
+						'FHEE__EE_Admin_Page___set_per_page_screen_options__max_value',
3714
+						999,
3715
+						$this->_current_page,
3716
+						$this->_current_view
3717
+					);
3718
+					if ($value < 1) {
3719
+						return;
3720
+					}
3721
+					$value = min($value, $max_value);
3722
+					break;
3723
+				default:
3724
+					$value = apply_filters(
3725
+						'FHEE__EE_Admin_Page___set_per_page_screen_options__value',
3726
+						false,
3727
+						$option,
3728
+						$value
3729
+					);
3730
+					if (false === $value) {
3731
+						return;
3732
+					}
3733
+					break;
3734
+			}
3735
+			update_user_meta($user->ID, $option, $value);
3736
+			wp_safe_redirect(remove_query_arg(['pagenum', 'apage', 'paged'], wp_get_referer()));
3737
+			exit;
3738
+		}
3739
+	}
3740
+
3741
+
3742
+	/**
3743
+	 * This just allows for setting the $_template_args property if it needs to be set outside the object
3744
+	 *
3745
+	 * @param array $data array that will be assigned to template args.
3746
+	 */
3747
+	public function set_template_args($data)
3748
+	{
3749
+		$this->_template_args = array_merge($this->_template_args, (array) $data);
3750
+	}
3751
+
3752
+
3753
+	/**
3754
+	 * This makes available the WP transient system for temporarily moving data between routes
3755
+	 *
3756
+	 * @param string $route             the route that should receive the transient
3757
+	 * @param array  $data              the data that gets sent
3758
+	 * @param bool   $notices           If this is for notices then we use this to indicate so, otherwise its just a
3759
+	 *                                  normal route transient.
3760
+	 * @param bool   $skip_route_verify Used to indicate we want to skip route verification.  This is usually ONLY used
3761
+	 *                                  when we are adding a transient before page_routes have been defined.
3762
+	 * @return void
3763
+	 * @throws EE_Error
3764
+	 */
3765
+	protected function _add_transient($route, $data, $notices = false, $skip_route_verify = false)
3766
+	{
3767
+		$user_id = get_current_user_id();
3768
+		if (! $skip_route_verify) {
3769
+			$this->_verify_route($route);
3770
+		}
3771
+		// now let's set the string for what kind of transient we're setting
3772
+		$transient = $notices
3773
+			? 'ee_rte_n_tx_' . $route . '_' . $user_id
3774
+			: 'rte_tx_' . $route . '_' . $user_id;
3775
+		$data      = $notices ? ['notices' => $data] : $data;
3776
+		// is there already a transient for this route?  If there is then let's ADD to that transient
3777
+		$existing = is_multisite() && is_network_admin()
3778
+			? get_site_transient($transient)
3779
+			: get_transient($transient);
3780
+		if ($existing) {
3781
+			$data = array_merge((array) $data, (array) $existing);
3782
+		}
3783
+		if (is_multisite() && is_network_admin()) {
3784
+			set_site_transient($transient, $data, 8);
3785
+		} else {
3786
+			set_transient($transient, $data, 8);
3787
+		}
3788
+	}
3789
+
3790
+
3791
+	/**
3792
+	 * this retrieves the temporary transient that has been set for moving data between routes.
3793
+	 *
3794
+	 * @param bool   $notices true we get notices transient. False we just return normal route transient
3795
+	 * @param string $route
3796
+	 * @return mixed data
3797
+	 */
3798
+	protected function _get_transient($notices = false, $route = '')
3799
+	{
3800
+		$user_id   = get_current_user_id();
3801
+		$route     = ! $route ? $this->_req_action : $route;
3802
+		$transient = $notices
3803
+			? 'ee_rte_n_tx_' . $route . '_' . $user_id
3804
+			: 'rte_tx_' . $route . '_' . $user_id;
3805
+		$data      = is_multisite() && is_network_admin()
3806
+			? get_site_transient($transient)
3807
+			: get_transient($transient);
3808
+		// delete transient after retrieval (just in case it hasn't expired);
3809
+		if (is_multisite() && is_network_admin()) {
3810
+			delete_site_transient($transient);
3811
+		} else {
3812
+			delete_transient($transient);
3813
+		}
3814
+		return $notices && isset($data['notices']) ? $data['notices'] : $data;
3815
+	}
3816
+
3817
+
3818
+	/**
3819
+	 * The purpose of this method is just to run garbage collection on any EE transients that might have expired but
3820
+	 * would not be called later. This will be assigned to run on a specific EE Admin page. (place the method in the
3821
+	 * default route callback on the EE_Admin page you want it run.)
3822
+	 *
3823
+	 * @return void
3824
+	 */
3825
+	protected function _transient_garbage_collection()
3826
+	{
3827
+		global $wpdb;
3828
+		// retrieve all existing transients
3829
+		$query =
3830
+			"SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE '%rte_tx_%' OR option_name LIKE '%rte_n_tx_%'";
3831
+		if ($results = $wpdb->get_results($query)) {
3832
+			foreach ($results as $result) {
3833
+				$transient = str_replace('_transient_', '', $result->option_name);
3834
+				get_transient($transient);
3835
+				if (is_multisite() && is_network_admin()) {
3836
+					get_site_transient($transient);
3837
+				}
3838
+			}
3839
+		}
3840
+	}
3841
+
3842
+
3843
+	/**
3844
+	 * get_view
3845
+	 *
3846
+	 * @return string content of _view property
3847
+	 */
3848
+	public function get_view()
3849
+	{
3850
+		return $this->_view;
3851
+	}
3852
+
3853
+
3854
+	/**
3855
+	 * getter for the protected $_views property
3856
+	 *
3857
+	 * @return array
3858
+	 */
3859
+	public function get_views()
3860
+	{
3861
+		return $this->_views;
3862
+	}
3863
+
3864
+
3865
+	/**
3866
+	 * get_current_page
3867
+	 *
3868
+	 * @return string _current_page property value
3869
+	 */
3870
+	public function get_current_page()
3871
+	{
3872
+		return $this->_current_page;
3873
+	}
3874
+
3875
+
3876
+	/**
3877
+	 * get_current_view
3878
+	 *
3879
+	 * @return string _current_view property value
3880
+	 */
3881
+	public function get_current_view()
3882
+	{
3883
+		return $this->_current_view;
3884
+	}
3885
+
3886
+
3887
+	/**
3888
+	 * get_current_screen
3889
+	 *
3890
+	 * @return object The current WP_Screen object
3891
+	 */
3892
+	public function get_current_screen()
3893
+	{
3894
+		return $this->_current_screen;
3895
+	}
3896
+
3897
+
3898
+	/**
3899
+	 * get_current_page_view_url
3900
+	 *
3901
+	 * @return string This returns the url for the current_page_view.
3902
+	 */
3903
+	public function get_current_page_view_url()
3904
+	{
3905
+		return $this->_current_page_view_url;
3906
+	}
3907
+
3908
+
3909
+	/**
3910
+	 * just returns the Request
3911
+	 *
3912
+	 * @return RequestInterface
3913
+	 */
3914
+	public function get_request()
3915
+	{
3916
+		return $this->request;
3917
+	}
3918
+
3919
+
3920
+	/**
3921
+	 * just returns the _req_data property
3922
+	 *
3923
+	 * @return array
3924
+	 */
3925
+	public function get_request_data()
3926
+	{
3927
+		return $this->request->requestParams();
3928
+	}
3929
+
3930
+
3931
+	/**
3932
+	 * returns the _req_data protected property
3933
+	 *
3934
+	 * @return string
3935
+	 */
3936
+	public function get_req_action()
3937
+	{
3938
+		return $this->_req_action;
3939
+	}
3940
+
3941
+
3942
+	/**
3943
+	 * @return bool  value of $_is_caf property
3944
+	 */
3945
+	public function is_caf()
3946
+	{
3947
+		return $this->_is_caf;
3948
+	}
3949
+
3950
+
3951
+	/**
3952
+	 * @return mixed
3953
+	 */
3954
+	public function default_espresso_metaboxes()
3955
+	{
3956
+		return $this->_default_espresso_metaboxes;
3957
+	}
3958
+
3959
+
3960
+	/**
3961
+	 * @return mixed
3962
+	 */
3963
+	public function admin_base_url()
3964
+	{
3965
+		return $this->_admin_base_url;
3966
+	}
3967
+
3968
+
3969
+	/**
3970
+	 * @return mixed
3971
+	 */
3972
+	public function wp_page_slug()
3973
+	{
3974
+		return $this->_wp_page_slug;
3975
+	}
3976
+
3977
+
3978
+	/**
3979
+	 * updates  espresso configuration settings
3980
+	 *
3981
+	 * @param string                   $tab
3982
+	 * @param EE_Config_Base|EE_Config $config
3983
+	 * @param string                   $file file where error occurred
3984
+	 * @param string                   $func function  where error occurred
3985
+	 * @param string                   $line line no where error occurred
3986
+	 * @return boolean
3987
+	 */
3988
+	protected function _update_espresso_configuration($tab, $config, $file = '', $func = '', $line = '')
3989
+	{
3990
+		// remove any options that are NOT going to be saved with the config settings.
3991
+		if (isset($config->core->ee_ueip_optin)) {
3992
+			// TODO: remove the following two lines and make sure values are migrated from 3.1
3993
+			update_option('ee_ueip_optin', $config->core->ee_ueip_optin);
3994
+			update_option('ee_ueip_has_notified', true);
3995
+		}
3996
+		// and save it (note we're also doing the network save here)
3997
+		$net_saved    = ! is_main_site() || EE_Network_Config::instance()->update_config(false, false);
3998
+		$config_saved = EE_Config::instance()->update_espresso_config(false, false);
3999
+		if ($config_saved && $net_saved) {
4000
+			EE_Error::add_success(sprintf(esc_html__('"%s" have been successfully updated.', 'event_espresso'), $tab));
4001
+			return true;
4002
+		}
4003
+		EE_Error::add_error(sprintf(esc_html__('The "%s" were not updated.', 'event_espresso'), $tab), $file, $func, $line);
4004
+		return false;
4005
+	}
4006
+
4007
+
4008
+	/**
4009
+	 * Returns an array to be used for EE_FOrm_Fields.helper.php's select_input as the $values argument.
4010
+	 *
4011
+	 * @return array
4012
+	 */
4013
+	public function get_yes_no_values()
4014
+	{
4015
+		return $this->_yes_no_values;
4016
+	}
4017
+
4018
+
4019
+	/**
4020
+	 * @return string
4021
+	 * @throws ReflectionException
4022
+	 * @since $VID:$
4023
+	 */
4024
+	protected function _get_dir()
4025
+	{
4026
+		$reflector = new ReflectionClass($this->class_name);
4027
+		return dirname($reflector->getFileName());
4028
+	}
4029
+
4030
+
4031
+	/**
4032
+	 * A helper for getting a "next link".
4033
+	 *
4034
+	 * @param string $url   The url to link to
4035
+	 * @param string $class The class to use.
4036
+	 * @return string
4037
+	 */
4038
+	protected function _next_link($url, $class = 'dashicons dashicons-arrow-right')
4039
+	{
4040
+		return '<a class="' . $class . '" href="' . $url . '"></a>';
4041
+	}
4042
+
4043
+
4044
+	/**
4045
+	 * A helper for getting a "previous link".
4046
+	 *
4047
+	 * @param string $url   The url to link to
4048
+	 * @param string $class The class to use.
4049
+	 * @return string
4050
+	 */
4051
+	protected function _previous_link($url, $class = 'dashicons dashicons-arrow-left')
4052
+	{
4053
+		return '<a class="' . $class . '" href="' . $url . '"></a>';
4054
+	}
4055
+
4056
+
4057
+
4058
+
4059
+
4060
+
4061
+
4062
+	// below are some messages related methods that should be available across the EE_Admin system.  Note, these methods are NOT page specific
4063
+
4064
+
4065
+	/**
4066
+	 * This processes an request to resend a registration and assumes we have a _REG_ID for doing so. So if the caller
4067
+	 * 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
4068
+	 * _req_data array.
4069
+	 *
4070
+	 * @return bool success/fail
4071
+	 * @throws EE_Error
4072
+	 * @throws InvalidArgumentException
4073
+	 * @throws ReflectionException
4074
+	 * @throws InvalidDataTypeException
4075
+	 * @throws InvalidInterfaceException
4076
+	 */
4077
+	protected function _process_resend_registration()
4078
+	{
4079
+		$this->_template_args['success'] = EED_Messages::process_resend($this->_req_data);
4080
+		do_action(
4081
+			'AHEE__EE_Admin_Page___process_resend_registration',
4082
+			$this->_template_args['success'],
4083
+			$this->request->requestParams()
4084
+		);
4085
+		return $this->_template_args['success'];
4086
+	}
4087
+
4088
+
4089
+	/**
4090
+	 * This automatically processes any payment message notifications when manual payment has been applied.
4091
+	 *
4092
+	 * @param EE_Payment $payment
4093
+	 * @return bool success/fail
4094
+	 */
4095
+	protected function _process_payment_notification(EE_Payment $payment)
4096
+	{
4097
+		add_filter('FHEE__EE_Payment_Processor__process_registration_payments__display_notifications', '__return_true');
4098
+		do_action('AHEE__EE_Admin_Page___process_admin_payment_notification', $payment);
4099
+		$this->_template_args['success'] = apply_filters(
4100
+			'FHEE__EE_Admin_Page___process_admin_payment_notification__success',
4101
+			false,
4102
+			$payment
4103
+		);
4104
+		return $this->_template_args['success'];
4105
+	}
4106
+
4107
+
4108
+	/**
4109
+	 * @param EEM_Base      $entity_model
4110
+	 * @param string        $entity_PK_name name of the primary key field used as a request param, ie: id, ID, etc
4111
+	 * @param string        $action         one of the EE_Admin_List_Table::ACTION_* constants: delete, restore, trash
4112
+	 * @param string        $delete_column  name of the field that denotes whether entity is trashed
4113
+	 * @param callable|null $callback       called after entity is trashed, restored, or deleted
4114
+	 * @return int|float
4115
+	 * @throws EE_Error
4116
+	 */
4117
+	protected function trashRestoreDeleteEntities(
4118
+		EEM_Base $entity_model,
4119
+		$entity_PK_name,
4120
+		$action = EE_Admin_List_Table::ACTION_DELETE,
4121
+		$delete_column = '',
4122
+		callable $callback = null
4123
+	) {
4124
+		$entity_PK      = $entity_model->get_primary_key_field();
4125
+		$entity_PK_name = $entity_PK_name ?: $entity_PK->get_name();
4126
+		$entity_PK_type = $this->resolveEntityFieldDataType($entity_PK);
4127
+		// grab ID if deleting a single entity
4128
+		if ($this->request->requestParamIsSet($entity_PK_name)) {
4129
+			$ID = $this->request->getRequestParam($entity_PK_name, 0, $entity_PK_type);
4130
+			return $this->trashRestoreDeleteEntity($entity_model, $ID, $action, $delete_column, $callback) ? 1 : 0;
4131
+		}
4132
+		// or grab checkbox array if bulk deleting
4133
+		$checkboxes = $this->request->getRequestParam('checkbox', [], $entity_PK_type, true);
4134
+		if (empty($checkboxes)) {
4135
+			return 0;
4136
+		}
4137
+		$success = 0;
4138
+		$IDs     = array_keys($checkboxes);
4139
+		// cycle thru bulk action checkboxes
4140
+		foreach ($IDs as $ID) {
4141
+			// increment $success
4142
+			if ($this->trashRestoreDeleteEntity($entity_model, $ID, $action, $delete_column, $callback)) {
4143
+				$success++;
4144
+			}
4145
+		}
4146
+		$count = (int) count($checkboxes);
4147
+		// if multiple entities were deleted successfully, then $deleted will be full count of deletions,
4148
+		// otherwise it will be a fraction of ( actual deletions / total entities to be deleted )
4149
+		return $success === $count ? $count : $success / $count;
4150
+	}
4151
+
4152
+
4153
+	/**
4154
+	 * @param EE_Primary_Key_Field_Base $entity_PK
4155
+	 * @return string
4156
+	 * @throws EE_Error
4157
+	 * @since   4.10.30.p
4158
+	 */
4159
+	private function resolveEntityFieldDataType(EE_Primary_Key_Field_Base $entity_PK)
4160
+	{
4161
+		$entity_PK_type = $entity_PK->getSchemaType();
4162
+		switch ($entity_PK_type) {
4163
+			case 'boolean':
4164
+				return 'bool';
4165
+			case 'integer':
4166
+				return 'int';
4167
+			case 'number':
4168
+				return 'float';
4169
+			case 'string':
4170
+				return 'string';
4171
+		}
4172
+		throw new RuntimeException(
4173
+			sprintf(
4174
+				esc_html__(
4175
+					'"%1$s" is an invalid schema type for the %2$s primary key.',
4176
+					'event_espresso'
4177
+				),
4178
+				$entity_PK_type,
4179
+				$entity_PK->get_name()
4180
+			)
4181
+		);
4182
+	}
4183
+
4184
+
4185
+	/**
4186
+	 * @param EEM_Base      $entity_model
4187
+	 * @param int|string    $entity_ID
4188
+	 * @param string        $action        one of the EE_Admin_List_Table::ACTION_* constants: delete, restore, trash
4189
+	 * @param string        $delete_column name of the field that denotes whether entity is trashed
4190
+	 * @param callable|null $callback      called after entity is trashed, restored, or deleted
4191
+	 * @return bool
4192
+	 */
4193
+	protected function trashRestoreDeleteEntity(
4194
+		EEM_Base $entity_model,
4195
+		$entity_ID,
4196
+		string $action,
4197
+		string $delete_column,
4198
+		?callable $callback = null
4199
+	): bool {
4200
+		$entity_ID = absint($entity_ID);
4201
+		if (! $entity_ID) {
4202
+			$this->trashRestoreDeleteError($action, $entity_model);
4203
+		}
4204
+		$result = 0;
4205
+		try {
4206
+			$entity = $entity_model->get_one_by_ID($entity_ID);
4207
+			if (! $entity instanceof EE_Base_Class) {
4208
+				throw new DomainException(
4209
+					sprintf(
4210
+						esc_html__(
4211
+							'Missing or invalid %1$s entity with ID of "%2$s" returned from db.',
4212
+							'event_espresso'
4213
+						),
4214
+						str_replace('EEM_', '', $entity_model->get_this_model_name()),
4215
+						$entity_ID
4216
+					)
4217
+				);
4218
+			}
4219
+			switch ($action) {
4220
+				case EE_Admin_List_Table::ACTION_DELETE:
4221
+					$result = (bool) $entity->delete_permanently();
4222
+					break;
4223
+				case EE_Admin_List_Table::ACTION_RESTORE:
4224
+					$result = $entity->delete_or_restore(false);
4225
+					break;
4226
+				case EE_Admin_List_Table::ACTION_TRASH:
4227
+					$result = $entity->delete_or_restore();
4228
+					break;
4229
+			}
4230
+		} catch (Exception $exception) {
4231
+			$this->trashRestoreDeleteError($action, $entity_model, $exception);
4232
+		}
4233
+		if (is_callable($callback)) {
4234
+			call_user_func_array($callback, [$entity_model, $entity_ID, $action, $result, $delete_column]);
4235
+		}
4236
+		return $result;
4237
+	}
4238
+
4239
+
4240
+	/**
4241
+	 * @param EEM_Base $entity_model
4242
+	 * @param string   $delete_column
4243
+	 * @since 4.10.30.p
4244
+	 */
4245
+	private function validateDeleteColumn(EEM_Base $entity_model, $delete_column)
4246
+	{
4247
+		if (empty($delete_column)) {
4248
+			throw new DomainException(
4249
+				sprintf(
4250
+					esc_html__(
4251
+						'You need to specify the name of the "delete column" on the %2$s model, in order to trash or restore an entity.',
4252
+						'event_espresso'
4253
+					),
4254
+					$entity_model->get_this_model_name()
4255
+				)
4256
+			);
4257
+		}
4258
+		if (! $entity_model->has_field($delete_column)) {
4259
+			throw new DomainException(
4260
+				sprintf(
4261
+					esc_html__(
4262
+						'The %1$s field does not exist on the %2$s model.',
4263
+						'event_espresso'
4264
+					),
4265
+					$delete_column,
4266
+					$entity_model->get_this_model_name()
4267
+				)
4268
+			);
4269
+		}
4270
+	}
4271
+
4272
+
4273
+	/**
4274
+	 * @param EEM_Base       $entity_model
4275
+	 * @param Exception|null $exception
4276
+	 * @param string         $action
4277
+	 * @since 4.10.30.p
4278
+	 */
4279
+	private function trashRestoreDeleteError($action, EEM_Base $entity_model, Exception $exception = null)
4280
+	{
4281
+		if ($exception instanceof Exception) {
4282
+			throw new RuntimeException(
4283
+				sprintf(
4284
+					esc_html__(
4285
+						'Could not %1$s the %2$s because the following error occurred: %3$s',
4286
+						'event_espresso'
4287
+					),
4288
+					$action,
4289
+					$entity_model->get_this_model_name(),
4290
+					$exception->getMessage()
4291
+				)
4292
+			);
4293
+		}
4294
+		throw new RuntimeException(
4295
+			sprintf(
4296
+				esc_html__(
4297
+					'Could not %1$s the %2$s because an invalid ID was received.',
4298
+					'event_espresso'
4299
+				),
4300
+				$action,
4301
+				$entity_model->get_this_model_name()
4302
+			)
4303
+		);
4304
+	}
4305 4305
 }
Please login to merge, or discard this patch.
Spacing   +180 added lines, -180 removed lines patch added patch discarded remove patch
@@ -631,7 +631,7 @@  discard block
 block discarded – undo
631 631
         $ee_menu_slugs = (array) $ee_menu_slugs;
632 632
         if (
633 633
             ! $this->request->isAjax()
634
-            && (! $this->_current_page || ! isset($ee_menu_slugs[ $this->_current_page ]))
634
+            && ( ! $this->_current_page || ! isset($ee_menu_slugs[$this->_current_page]))
635 635
         ) {
636 636
             return;
637 637
         }
@@ -651,7 +651,7 @@  discard block
 block discarded – undo
651 651
             : $req_action;
652 652
 
653 653
         $this->_current_view = $this->_req_action;
654
-        $this->_req_nonce    = $this->_req_action . '_nonce';
654
+        $this->_req_nonce    = $this->_req_action.'_nonce';
655 655
         $this->_define_page_props();
656 656
         $this->_current_page_view_url = add_query_arg(
657 657
             ['page' => $this->_current_page, 'action' => $this->_current_view],
@@ -681,33 +681,33 @@  discard block
 block discarded – undo
681 681
         }
682 682
         // filter routes and page_config so addons can add their stuff. Filtering done per class
683 683
         $this->_page_routes = apply_filters(
684
-            'FHEE__' . $this->class_name . '__page_setup__page_routes',
684
+            'FHEE__'.$this->class_name.'__page_setup__page_routes',
685 685
             $this->_page_routes,
686 686
             $this
687 687
         );
688 688
         $this->_page_config = apply_filters(
689
-            'FHEE__' . $this->class_name . '__page_setup__page_config',
689
+            'FHEE__'.$this->class_name.'__page_setup__page_config',
690 690
             $this->_page_config,
691 691
             $this
692 692
         );
693 693
         if ($this->base_class_name !== '') {
694 694
             $this->_page_routes = apply_filters(
695
-                'FHEE__' . $this->base_class_name . '__page_setup__page_routes',
695
+                'FHEE__'.$this->base_class_name.'__page_setup__page_routes',
696 696
                 $this->_page_routes,
697 697
                 $this
698 698
             );
699 699
             $this->_page_config = apply_filters(
700
-                'FHEE__' . $this->base_class_name . '__page_setup__page_config',
700
+                'FHEE__'.$this->base_class_name.'__page_setup__page_config',
701 701
                 $this->_page_config,
702 702
                 $this
703 703
             );
704 704
         }
705 705
         // if AHEE__EE_Admin_Page__route_admin_request_$this->_current_view method is present
706 706
         // then we call it hooked into the AHEE__EE_Admin_Page__route_admin_request action
707
-        if (method_exists($this, 'AHEE__EE_Admin_Page__route_admin_request_' . $this->_current_view)) {
707
+        if (method_exists($this, 'AHEE__EE_Admin_Page__route_admin_request_'.$this->_current_view)) {
708 708
             add_action(
709 709
                 'AHEE__EE_Admin_Page__route_admin_request',
710
-                [$this, 'AHEE__EE_Admin_Page__route_admin_request_' . $this->_current_view],
710
+                [$this, 'AHEE__EE_Admin_Page__route_admin_request_'.$this->_current_view],
711 711
                 10,
712 712
                 2
713 713
             );
@@ -720,8 +720,8 @@  discard block
 block discarded – undo
720 720
             if ($this->_is_UI_request) {
721 721
                 // admin_init stuff - global, all views for this page class, specific view
722 722
                 add_action('admin_init', [$this, 'admin_init'], 10);
723
-                if (method_exists($this, 'admin_init_' . $this->_current_view)) {
724
-                    add_action('admin_init', [$this, 'admin_init_' . $this->_current_view], 15);
723
+                if (method_exists($this, 'admin_init_'.$this->_current_view)) {
724
+                    add_action('admin_init', [$this, 'admin_init_'.$this->_current_view], 15);
725 725
                 }
726 726
             } else {
727 727
                 // hijack regular WP loading and route admin request immediately
@@ -740,12 +740,12 @@  discard block
 block discarded – undo
740 740
      */
741 741
     private function _do_other_page_hooks()
742 742
     {
743
-        $registered_pages = apply_filters('FHEE_do_other_page_hooks_' . $this->page_slug, []);
743
+        $registered_pages = apply_filters('FHEE_do_other_page_hooks_'.$this->page_slug, []);
744 744
         foreach ($registered_pages as $page) {
745 745
             // now let's setup the file name and class that should be present
746 746
             $classname = str_replace('.class.php', '', $page);
747 747
             // autoloaders should take care of loading file
748
-            if (! class_exists($classname)) {
748
+            if ( ! class_exists($classname)) {
749 749
                 $error_msg[] = sprintf(
750 750
                     esc_html__(
751 751
                         'Something went wrong with loading the %s admin hooks page.',
@@ -762,7 +762,7 @@  discard block
 block discarded – undo
762 762
                                    ),
763 763
                                    $page,
764 764
                                    '<br />',
765
-                                   '<strong>' . $classname . '</strong>'
765
+                                   '<strong>'.$classname.'</strong>'
766 766
                                );
767 767
                 throw new EE_Error(implode('||', $error_msg));
768 768
             }
@@ -804,13 +804,13 @@  discard block
 block discarded – undo
804 804
         // load admin_notices - global, page class, and view specific
805 805
         add_action('admin_notices', [$this, 'admin_notices_global'], 5);
806 806
         add_action('admin_notices', [$this, 'admin_notices'], 10);
807
-        if (method_exists($this, 'admin_notices_' . $this->_current_view)) {
808
-            add_action('admin_notices', [$this, 'admin_notices_' . $this->_current_view], 15);
807
+        if (method_exists($this, 'admin_notices_'.$this->_current_view)) {
808
+            add_action('admin_notices', [$this, 'admin_notices_'.$this->_current_view], 15);
809 809
         }
810 810
         // load network admin_notices - global, page class, and view specific
811 811
         add_action('network_admin_notices', [$this, 'network_admin_notices_global'], 5);
812
-        if (method_exists($this, 'network_admin_notices_' . $this->_current_view)) {
813
-            add_action('network_admin_notices', [$this, 'network_admin_notices_' . $this->_current_view]);
812
+        if (method_exists($this, 'network_admin_notices_'.$this->_current_view)) {
813
+            add_action('network_admin_notices', [$this, 'network_admin_notices_'.$this->_current_view]);
814 814
         }
815 815
         // this will save any per_page screen options if they are present
816 816
         $this->_set_per_page_screen_options();
@@ -931,7 +931,7 @@  discard block
 block discarded – undo
931 931
     protected function _verify_routes()
932 932
     {
933 933
         do_action('AHEE_log', __FILE__, __FUNCTION__, '');
934
-        if (! $this->_current_page && ! $this->request->isAjax()) {
934
+        if ( ! $this->_current_page && ! $this->request->isAjax()) {
935 935
             return false;
936 936
         }
937 937
         $this->_route = false;
@@ -943,7 +943,7 @@  discard block
 block discarded – undo
943 943
                 $this->_admin_page_title
944 944
             );
945 945
             // developer error msg
946
-            $error_msg .= '||' . $error_msg
946
+            $error_msg .= '||'.$error_msg
947 947
                           . esc_html__(
948 948
                               ' Make sure the "set_page_routes()" method exists, and is setting the "_page_routes" array properly.',
949 949
                               'event_espresso'
@@ -952,8 +952,8 @@  discard block
 block discarded – undo
952 952
         }
953 953
         // and that the requested page route exists
954 954
         if (array_key_exists($this->_req_action, $this->_page_routes)) {
955
-            $this->_route        = $this->_page_routes[ $this->_req_action ];
956
-            $this->_route_config = $this->_page_config[ $this->_req_action ] ?? [];
955
+            $this->_route        = $this->_page_routes[$this->_req_action];
956
+            $this->_route_config = $this->_page_config[$this->_req_action] ?? [];
957 957
         } else {
958 958
             // user error msg
959 959
             $error_msg = sprintf(
@@ -964,7 +964,7 @@  discard block
 block discarded – undo
964 964
                 $this->_admin_page_title
965 965
             );
966 966
             // developer error msg
967
-            $error_msg .= '||' . $error_msg
967
+            $error_msg .= '||'.$error_msg
968 968
                           . sprintf(
969 969
                               esc_html__(
970 970
                                   ' Create a key in the "_page_routes" array named "%s" and set its value to the appropriate method.',
@@ -975,7 +975,7 @@  discard block
 block discarded – undo
975 975
             throw new EE_Error($error_msg);
976 976
         }
977 977
         // and that a default route exists
978
-        if (! array_key_exists('default', $this->_page_routes)) {
978
+        if ( ! array_key_exists('default', $this->_page_routes)) {
979 979
             // user error msg
980 980
             $error_msg = sprintf(
981 981
                 esc_html__(
@@ -985,7 +985,7 @@  discard block
 block discarded – undo
985 985
                 $this->_admin_page_title
986 986
             );
987 987
             // developer error msg
988
-            $error_msg .= '||' . $error_msg
988
+            $error_msg .= '||'.$error_msg
989 989
                           . esc_html__(
990 990
                               ' Create a key in the "_page_routes" array named "default" and set its value to your default page method.',
991 991
                               'event_espresso'
@@ -1027,7 +1027,7 @@  discard block
 block discarded – undo
1027 1027
             $this->_admin_page_title
1028 1028
         );
1029 1029
         // developer error msg
1030
-        $error_msg .= '||' . $error_msg
1030
+        $error_msg .= '||'.$error_msg
1031 1031
                       . sprintf(
1032 1032
                           esc_html__(
1033 1033
                               ' Check the route you are using in your method (%s) and make sure it matches a route set in your "_page_routes" array property',
@@ -1055,7 +1055,7 @@  discard block
 block discarded – undo
1055 1055
     protected function _verify_nonce($nonce, $nonce_ref)
1056 1056
     {
1057 1057
         // verify nonce against expected value
1058
-        if (! wp_verify_nonce($nonce, $nonce_ref)) {
1058
+        if ( ! wp_verify_nonce($nonce, $nonce_ref)) {
1059 1059
             // these are not the droids you are looking for !!!
1060 1060
             $msg = sprintf(
1061 1061
                 esc_html__('%sNonce Fail.%s', 'event_espresso'),
@@ -1072,7 +1072,7 @@  discard block
 block discarded – undo
1072 1072
                     __CLASS__
1073 1073
                 );
1074 1074
             }
1075
-            if (! $this->request->isAjax()) {
1075
+            if ( ! $this->request->isAjax()) {
1076 1076
                 wp_die($msg);
1077 1077
             }
1078 1078
             EE_Error::add_error($msg, __FILE__, __FUNCTION__, __LINE__);
@@ -1096,7 +1096,7 @@  discard block
 block discarded – undo
1096 1096
      */
1097 1097
     protected function _route_admin_request()
1098 1098
     {
1099
-        if (! $this->_is_UI_request) {
1099
+        if ( ! $this->_is_UI_request) {
1100 1100
             $this->_verify_routes();
1101 1101
         }
1102 1102
         $nonce_check = ! isset($this->_route_config['require_nonce']) || $this->_route_config['require_nonce'];
@@ -1116,7 +1116,7 @@  discard block
 block discarded – undo
1116 1116
         $error_msg = '';
1117 1117
         // action right before calling route
1118 1118
         // (hook is something like 'AHEE__Registrations_Admin_Page__route_admin_request')
1119
-        if (! did_action('AHEE__EE_Admin_Page__route_admin_request')) {
1119
+        if ( ! did_action('AHEE__EE_Admin_Page__route_admin_request')) {
1120 1120
             do_action('AHEE__EE_Admin_Page__route_admin_request', $this->_current_view, $this);
1121 1121
         }
1122 1122
         // strip _wp_http_referer from the server REQUEST_URI
@@ -1130,7 +1130,7 @@  discard block
 block discarded – undo
1130 1130
         );
1131 1131
         $this->request->setRequestParam('_wp_http_referer', $cleaner_request_uri, true);
1132 1132
         $this->request->setServerParam('REQUEST_URI', $cleaner_request_uri, true);
1133
-        if (! empty($func)) {
1133
+        if ( ! empty($func)) {
1134 1134
             if (is_array($func)) {
1135 1135
                 [$class, $method] = $func;
1136 1136
             } elseif (strpos($func, '::') !== false) {
@@ -1140,7 +1140,7 @@  discard block
 block discarded – undo
1140 1140
                 $method = $func;
1141 1141
             }
1142 1142
             // is it neither a class method NOR a standalone function?
1143
-            if (! method_exists($class, $method) && ! function_exists($method)) {
1143
+            if ( ! method_exists($class, $method) && ! function_exists($method)) {
1144 1144
                 // user error msg
1145 1145
                 $error_msg = esc_html__(
1146 1146
                     'An error occurred. The  requested page route could not be found.',
@@ -1162,7 +1162,7 @@  discard block
 block discarded – undo
1162 1162
             }
1163 1163
             try {
1164 1164
                 $success = method_exists($class, $method) && call_user_func_array([$class, $method], $args);
1165
-                if (! $success && function_exists($method)) {
1165
+                if ( ! $success && function_exists($method)) {
1166 1166
                     call_user_func_array($method, $args);
1167 1167
                 }
1168 1168
             } catch (Throwable $throwable) {
@@ -1263,7 +1263,7 @@  discard block
 block discarded – undo
1263 1263
                 if (strpos($key, 'nonce') !== false) {
1264 1264
                     continue;
1265 1265
                 }
1266
-                $args[ 'wp_referer[' . $key . ']' ] = is_string($value) ? htmlspecialchars($value) : $value;
1266
+                $args['wp_referer['.$key.']'] = is_string($value) ? htmlspecialchars($value) : $value;
1267 1267
             }
1268 1268
         }
1269 1269
         return EEH_URL::add_query_args_and_nonce($args, $url, $exclude_nonce);
@@ -1303,12 +1303,12 @@  discard block
 block discarded – undo
1303 1303
      */
1304 1304
     protected function _add_help_tabs()
1305 1305
     {
1306
-        if (isset($this->_page_config[ $this->_req_action ])) {
1307
-            $config = $this->_page_config[ $this->_req_action ];
1306
+        if (isset($this->_page_config[$this->_req_action])) {
1307
+            $config = $this->_page_config[$this->_req_action];
1308 1308
             // let's see if there is a help_sidebar set for the current route and we'll set that up for usage as well.
1309 1309
             if (is_array($config) && isset($config['help_sidebar'])) {
1310 1310
                 // check that the callback given is valid
1311
-                if (! method_exists($this, $config['help_sidebar'])) {
1311
+                if ( ! method_exists($this, $config['help_sidebar'])) {
1312 1312
                     throw new EE_Error(
1313 1313
                         sprintf(
1314 1314
                             esc_html__(
@@ -1321,18 +1321,18 @@  discard block
 block discarded – undo
1321 1321
                     );
1322 1322
                 }
1323 1323
                 $content = apply_filters(
1324
-                    'FHEE__' . $this->class_name . '__add_help_tabs__help_sidebar',
1324
+                    'FHEE__'.$this->class_name.'__add_help_tabs__help_sidebar',
1325 1325
                     $this->{$config['help_sidebar']}()
1326 1326
                 );
1327 1327
                 $this->_current_screen->set_help_sidebar($content);
1328 1328
             }
1329
-            if (! isset($config['help_tabs'])) {
1329
+            if ( ! isset($config['help_tabs'])) {
1330 1330
                 return;
1331 1331
             } //no help tabs for this route
1332 1332
             foreach ((array) $config['help_tabs'] as $tab_id => $cfg) {
1333 1333
                 // we're here so there ARE help tabs!
1334 1334
                 // make sure we've got what we need
1335
-                if (! isset($cfg['title'])) {
1335
+                if ( ! isset($cfg['title'])) {
1336 1336
                     throw new EE_Error(
1337 1337
                         esc_html__(
1338 1338
                             'The _page_config array is not set up properly for help tabs.  It is missing a title',
@@ -1340,7 +1340,7 @@  discard block
 block discarded – undo
1340 1340
                         )
1341 1341
                     );
1342 1342
                 }
1343
-                if (! isset($cfg['filename']) && ! isset($cfg['callback']) && ! isset($cfg['content'])) {
1343
+                if ( ! isset($cfg['filename']) && ! isset($cfg['callback']) && ! isset($cfg['content'])) {
1344 1344
                     throw new EE_Error(
1345 1345
                         esc_html__(
1346 1346
                             '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',
@@ -1349,11 +1349,11 @@  discard block
 block discarded – undo
1349 1349
                     );
1350 1350
                 }
1351 1351
                 // first priority goes to content.
1352
-                if (! empty($cfg['content'])) {
1352
+                if ( ! empty($cfg['content'])) {
1353 1353
                     $content = ! empty($cfg['content']) ? $cfg['content'] : null;
1354 1354
                     // second priority goes to filename
1355
-                } elseif (! empty($cfg['filename'])) {
1356
-                    $file_path = $this->_get_dir() . '/help_tabs/' . $cfg['filename'] . '.help_tab.php';
1355
+                } elseif ( ! empty($cfg['filename'])) {
1356
+                    $file_path = $this->_get_dir().'/help_tabs/'.$cfg['filename'].'.help_tab.php';
1357 1357
                     // 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)
1358 1358
                     $file_path = ! is_readable($file_path) ? EE_ADMIN_PAGES
1359 1359
                                                              . basename($this->_get_dir())
@@ -1361,7 +1361,7 @@  discard block
 block discarded – undo
1361 1361
                                                              . $cfg['filename']
1362 1362
                                                              . '.help_tab.php' : $file_path;
1363 1363
                     // if file is STILL not readable then let's do a EE_Error so its more graceful than a fatal error.
1364
-                    if (! isset($cfg['callback']) && ! is_readable($file_path)) {
1364
+                    if ( ! isset($cfg['callback']) && ! is_readable($file_path)) {
1365 1365
                         EE_Error::add_error(
1366 1366
                             sprintf(
1367 1367
                                 esc_html__(
@@ -1409,7 +1409,7 @@  discard block
 block discarded – undo
1409 1409
                     return;
1410 1410
                 }
1411 1411
                 // setup config array for help tab method
1412
-                $id  = $this->page_slug . '-' . $this->_req_action . '-' . $tab_id;
1412
+                $id  = $this->page_slug.'-'.$this->_req_action.'-'.$tab_id;
1413 1413
                 $_ht = [
1414 1414
                     'id'       => $id,
1415 1415
                     'title'    => $cfg['title'],
@@ -1435,8 +1435,8 @@  discard block
 block discarded – undo
1435 1435
             $qtips = (array) $this->_route_config['qtips'];
1436 1436
             // load qtip loader
1437 1437
             $path = [
1438
-                $this->_get_dir() . '/qtips/',
1439
-                EE_ADMIN_PAGES . basename($this->_get_dir()) . '/qtips/',
1438
+                $this->_get_dir().'/qtips/',
1439
+                EE_ADMIN_PAGES.basename($this->_get_dir()).'/qtips/',
1440 1440
             ];
1441 1441
             EEH_Qtip_Loader::instance()->register($qtips, $path);
1442 1442
         }
@@ -1458,7 +1458,7 @@  discard block
 block discarded – undo
1458 1458
         $i = 0;
1459 1459
         $only_tab = count($this->_page_config) < 2;
1460 1460
         foreach ($this->_page_config as $slug => $config) {
1461
-            if (! is_array($config) || empty($config['nav'])) {
1461
+            if ( ! is_array($config) || empty($config['nav'])) {
1462 1462
                 continue;
1463 1463
             }
1464 1464
             // no nav tab for this config
@@ -1467,27 +1467,27 @@  discard block
 block discarded – undo
1467 1467
                 // nav tab is only to appear when route requested.
1468 1468
                 continue;
1469 1469
             }
1470
-            if (! $this->check_user_access($slug, true)) {
1470
+            if ( ! $this->check_user_access($slug, true)) {
1471 1471
                 // no nav tab because current user does not have access.
1472 1472
                 continue;
1473 1473
             }
1474
-            $css_class = isset($config['css_class']) ? $config['css_class'] . ' ' : '';
1474
+            $css_class = isset($config['css_class']) ? $config['css_class'].' ' : '';
1475 1475
             $css_class .= $only_tab ? ' ee-only-tab' : '';
1476 1476
 
1477
-            $this->_nav_tabs[ $slug ] = [
1477
+            $this->_nav_tabs[$slug] = [
1478 1478
                 'url'       => $config['nav']['url'] ?? EE_Admin_Page::add_query_args_and_nonce(
1479 1479
                     ['action' => $slug],
1480 1480
                     $this->_admin_base_url
1481 1481
                 ),
1482 1482
                 'link_text' => $this->navTabLabel($config['nav'], $slug),
1483
-                'css_class' => $this->_req_action === $slug ? $css_class . ' nav-tab-active' : $css_class,
1483
+                'css_class' => $this->_req_action === $slug ? $css_class.' nav-tab-active' : $css_class,
1484 1484
                 'order'     => $config['nav']['order'] ?? $i,
1485 1485
             ];
1486 1486
             $i++;
1487 1487
         }
1488 1488
         // if $this->_nav_tabs is empty then lets set the default
1489 1489
         if (empty($this->_nav_tabs)) {
1490
-            $this->_nav_tabs[ $this->_default_nav_tab_name ] = [
1490
+            $this->_nav_tabs[$this->_default_nav_tab_name] = [
1491 1491
                 'url'       => $this->_admin_base_url,
1492 1492
                 'link_text' => ucwords(str_replace('_', ' ', $this->_default_nav_tab_name)),
1493 1493
                 'css_class' => 'nav-tab-active',
@@ -1503,11 +1503,11 @@  discard block
 block discarded – undo
1503 1503
     {
1504 1504
         $label = $nav_tab['label'] ?? ucwords(str_replace('_', ' ', $slug));
1505 1505
         $icon = $nav_tab['icon'] ?? null;
1506
-        $icon = $icon ? '<span class="dashicons ' . $icon . '"></span>' : '';
1506
+        $icon = $icon ? '<span class="dashicons '.$icon.'"></span>' : '';
1507 1507
         return '
1508 1508
             <span class="ee-admin-screen-tab__label">
1509
-                ' . $icon . '
1510
-                <span class="ee-nav-label__text">' . $label . '</span>
1509
+                ' . $icon.'
1510
+                <span class="ee-nav-label__text">' . $label.'</span>
1511 1511
             </span>';
1512 1512
     }
1513 1513
 
@@ -1524,10 +1524,10 @@  discard block
 block discarded – undo
1524 1524
             foreach ($this->_route_config['labels'] as $label => $text) {
1525 1525
                 if (is_array($text)) {
1526 1526
                     foreach ($text as $sublabel => $subtext) {
1527
-                        $this->_labels[ $label ][ $sublabel ] = $subtext;
1527
+                        $this->_labels[$label][$sublabel] = $subtext;
1528 1528
                     }
1529 1529
                 } else {
1530
-                    $this->_labels[ $label ] = $text;
1530
+                    $this->_labels[$label] = $text;
1531 1531
                 }
1532 1532
             }
1533 1533
         }
@@ -1549,10 +1549,10 @@  discard block
 block discarded – undo
1549 1549
     {
1550 1550
         do_action('AHEE_log', __FILE__, __FUNCTION__, '');
1551 1551
         $route_to_check = empty($route_to_check) ? $this->_req_action : $route_to_check;
1552
-        $capability = ! empty($route_to_check) && isset($this->_page_routes[ $route_to_check ])
1553
-                                      && is_array($this->_page_routes[ $route_to_check ])
1554
-                        && ! empty($this->_page_routes[ $route_to_check ]['capability'])
1555
-            ? $this->_page_routes[ $route_to_check ]['capability']
1552
+        $capability = ! empty($route_to_check) && isset($this->_page_routes[$route_to_check])
1553
+                                      && is_array($this->_page_routes[$route_to_check])
1554
+                        && ! empty($this->_page_routes[$route_to_check]['capability'])
1555
+            ? $this->_page_routes[$route_to_check]['capability']
1556 1556
             : null;
1557 1557
 
1558 1558
         if (empty($capability) && empty($route_to_check)) {
@@ -1606,14 +1606,14 @@  discard block
 block discarded – undo
1606 1606
         string $priority = 'default',
1607 1607
         ?array $callback_args = null
1608 1608
     ) {
1609
-        if (! is_callable($callback)) {
1609
+        if ( ! is_callable($callback)) {
1610 1610
             return;
1611 1611
         }
1612 1612
 
1613 1613
         add_meta_box($box_id, $title, $callback, $screen, $context, $priority, $callback_args);
1614 1614
         add_filter(
1615 1615
             "postbox_classes_{$this->_wp_page_slug}_{$box_id}",
1616
-            function ($classes) {
1616
+            function($classes) {
1617 1617
                 array_push($classes, 'ee-admin-container');
1618 1618
                 return $classes;
1619 1619
             }
@@ -1707,7 +1707,7 @@  discard block
 block discarded – undo
1707 1707
         ';
1708 1708
 
1709 1709
         // current set timezone for timezone js
1710
-        echo '<span id="current_timezone" class="hidden">' . esc_html(EEH_DTT_Helper::get_timezone()) . '</span>';
1710
+        echo '<span id="current_timezone" class="hidden">'.esc_html(EEH_DTT_Helper::get_timezone()).'</span>';
1711 1711
     }
1712 1712
 
1713 1713
 
@@ -1741,7 +1741,7 @@  discard block
 block discarded – undo
1741 1741
         // loop through the array and setup content
1742 1742
         foreach ($help_array as $trigger => $help) {
1743 1743
             // make sure the array is setup properly
1744
-            if (! isset($help['title'], $help['content'])) {
1744
+            if ( ! isset($help['title'], $help['content'])) {
1745 1745
                 throw new EE_Error(
1746 1746
                     esc_html__(
1747 1747
                         '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',
@@ -1755,8 +1755,8 @@  discard block
 block discarded – undo
1755 1755
                 'help_popup_title'   => $help['title'],
1756 1756
                 'help_popup_content' => $help['content'],
1757 1757
             ];
1758
-            $content       .= EEH_Template::display_template(
1759
-                EE_ADMIN_TEMPLATE . 'admin_help_popup.template.php',
1758
+            $content .= EEH_Template::display_template(
1759
+                EE_ADMIN_TEMPLATE.'admin_help_popup.template.php',
1760 1760
                 $template_args,
1761 1761
                 true
1762 1762
             );
@@ -1778,15 +1778,15 @@  discard block
 block discarded – undo
1778 1778
     private function _get_help_content()
1779 1779
     {
1780 1780
         // what is the method we're looking for?
1781
-        $method_name = '_help_popup_content_' . $this->_req_action;
1781
+        $method_name = '_help_popup_content_'.$this->_req_action;
1782 1782
         // if method doesn't exist let's get out.
1783
-        if (! method_exists($this, $method_name)) {
1783
+        if ( ! method_exists($this, $method_name)) {
1784 1784
             return [];
1785 1785
         }
1786 1786
         // k we're good to go let's retrieve the help array
1787 1787
         $help_array = $this->{$method_name}();
1788 1788
         // make sure we've got an array!
1789
-        if (! is_array($help_array)) {
1789
+        if ( ! is_array($help_array)) {
1790 1790
             throw new EE_Error(
1791 1791
                 esc_html__(
1792 1792
                     'Something went wrong with help popup content generation. Expecting an array and well, this ain\'t no array bub.',
@@ -1818,8 +1818,8 @@  discard block
 block discarded – undo
1818 1818
         // 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
1819 1819
         $help_array   = $this->_get_help_content();
1820 1820
         $help_content = '';
1821
-        if (empty($help_array) || ! isset($help_array[ $trigger_id ])) {
1822
-            $help_array[ $trigger_id ] = [
1821
+        if (empty($help_array) || ! isset($help_array[$trigger_id])) {
1822
+            $help_array[$trigger_id] = [
1823 1823
                 'title'   => esc_html__('Missing Content', 'event_espresso'),
1824 1824
                 'content' => esc_html__(
1825 1825
                     '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.)',
@@ -1914,7 +1914,7 @@  discard block
 block discarded – undo
1914 1914
 
1915 1915
         add_filter(
1916 1916
             'admin_body_class',
1917
-            function ($classes) {
1917
+            function($classes) {
1918 1918
                 if (strpos($classes, 'espresso-admin') === false) {
1919 1919
                     $classes .= ' espresso-admin';
1920 1920
                 }
@@ -2005,12 +2005,12 @@  discard block
 block discarded – undo
2005 2005
     protected function _set_list_table()
2006 2006
     {
2007 2007
         // first is this a list_table view?
2008
-        if (! isset($this->_route_config['list_table'])) {
2008
+        if ( ! isset($this->_route_config['list_table'])) {
2009 2009
             return;
2010 2010
         } //not a list_table view so get out.
2011 2011
         // list table functions are per view specific (because some admin pages might have more than one list table!)
2012
-        $list_table_view = '_set_list_table_views_' . $this->_req_action;
2013
-        if (! method_exists($this, $list_table_view) || $this->{$list_table_view}() === false) {
2012
+        $list_table_view = '_set_list_table_views_'.$this->_req_action;
2013
+        if ( ! method_exists($this, $list_table_view) || $this->{$list_table_view}() === false) {
2014 2014
             // user error msg
2015 2015
             $error_msg = esc_html__(
2016 2016
                 'An error occurred. The requested list table views could not be found.',
@@ -2030,10 +2030,10 @@  discard block
 block discarded – undo
2030 2030
         }
2031 2031
         // let's provide the ability to filter the views per PAGE AND ROUTE, per PAGE, and globally
2032 2032
         $this->_views = apply_filters(
2033
-            'FHEE_list_table_views_' . $this->page_slug . '_' . $this->_req_action,
2033
+            'FHEE_list_table_views_'.$this->page_slug.'_'.$this->_req_action,
2034 2034
             $this->_views
2035 2035
         );
2036
-        $this->_views = apply_filters('FHEE_list_table_views_' . $this->page_slug, $this->_views);
2036
+        $this->_views = apply_filters('FHEE_list_table_views_'.$this->page_slug, $this->_views);
2037 2037
         $this->_views = apply_filters('FHEE_list_table_views', $this->_views);
2038 2038
         $this->_set_list_table_view();
2039 2039
         $this->_set_list_table_object();
@@ -2068,7 +2068,7 @@  discard block
 block discarded – undo
2068 2068
     protected function _set_list_table_object()
2069 2069
     {
2070 2070
         if (isset($this->_route_config['list_table'])) {
2071
-            if (! class_exists($this->_route_config['list_table'])) {
2071
+            if ( ! class_exists($this->_route_config['list_table'])) {
2072 2072
                 throw new EE_Error(
2073 2073
                     sprintf(
2074 2074
                         esc_html__(
@@ -2106,17 +2106,17 @@  discard block
 block discarded – undo
2106 2106
         foreach ($this->_views as $key => $view) {
2107 2107
             $query_args = [];
2108 2108
             // check for current view
2109
-            $this->_views[ $key ]['class']               = $this->_view === $view['slug'] ? 'current' : '';
2109
+            $this->_views[$key]['class']               = $this->_view === $view['slug'] ? 'current' : '';
2110 2110
             $query_args['action']                        = $this->_req_action;
2111
-            $query_args[ $this->_req_action . '_nonce' ] = wp_create_nonce($query_args['action'] . '_nonce');
2111
+            $query_args[$this->_req_action.'_nonce'] = wp_create_nonce($query_args['action'].'_nonce');
2112 2112
             $query_args['status']                        = $view['slug'];
2113 2113
             // merge any other arguments sent in.
2114
-            if (isset($extra_query_args[ $view['slug'] ])) {
2115
-                foreach ($extra_query_args[ $view['slug'] ] as $extra_query_arg) {
2114
+            if (isset($extra_query_args[$view['slug']])) {
2115
+                foreach ($extra_query_args[$view['slug']] as $extra_query_arg) {
2116 2116
                     $query_args[] = $extra_query_arg;
2117 2117
                 }
2118 2118
             }
2119
-            $this->_views[ $key ]['url'] = EE_Admin_Page::add_query_args_and_nonce($query_args, $this->_admin_base_url);
2119
+            $this->_views[$key]['url'] = EE_Admin_Page::add_query_args_and_nonce($query_args, $this->_admin_base_url);
2120 2120
         }
2121 2121
         return $this->_views;
2122 2122
     }
@@ -2147,14 +2147,14 @@  discard block
 block discarded – undo
2147 2147
 					<select id="entries-per-page-slct" name="entries-per-page-slct">';
2148 2148
         foreach ($values as $value) {
2149 2149
             if ($value < $max_entries) {
2150
-                $selected                  = $value === $per_page ? ' selected="' . $per_page . '"' : '';
2150
+                $selected = $value === $per_page ? ' selected="'.$per_page.'"' : '';
2151 2151
                 $entries_per_page_dropdown .= '
2152
-						<option value="' . $value . '"' . $selected . '>' . $value . '&nbsp;&nbsp;</option>';
2152
+						<option value="' . $value.'"'.$selected.'>'.$value.'&nbsp;&nbsp;</option>';
2153 2153
             }
2154 2154
         }
2155
-        $selected                  = $max_entries === $per_page ? ' selected="' . $per_page . '"' : '';
2155
+        $selected = $max_entries === $per_page ? ' selected="'.$per_page.'"' : '';
2156 2156
         $entries_per_page_dropdown .= '
2157
-						<option value="' . $max_entries . '"' . $selected . '>All&nbsp;&nbsp;</option>';
2157
+						<option value="' . $max_entries.'"'.$selected.'>All&nbsp;&nbsp;</option>';
2158 2158
         $entries_per_page_dropdown .= '
2159 2159
 					</select>
2160 2160
 					entries
@@ -2178,7 +2178,7 @@  discard block
 block discarded – undo
2178 2178
             empty($this->_search_btn_label) ? $this->page_label
2179 2179
                 : $this->_search_btn_label
2180 2180
         );
2181
-        $this->_template_args['search']['callback']  = 'search_' . $this->page_slug;
2181
+        $this->_template_args['search']['callback'] = 'search_'.$this->page_slug;
2182 2182
     }
2183 2183
 
2184 2184
 
@@ -2266,7 +2266,7 @@  discard block
 block discarded – undo
2266 2266
             $total_columns                                       = ! empty($screen_columns)
2267 2267
                 ? $screen_columns
2268 2268
                 : $this->_route_config['columns'][1];
2269
-            $this->_template_args['current_screen_widget_class'] = 'columns-' . $total_columns;
2269
+            $this->_template_args['current_screen_widget_class'] = 'columns-'.$total_columns;
2270 2270
             $this->_template_args['current_page']                = $this->_wp_page_slug;
2271 2271
             $this->_template_args['screen']                      = $this->_current_screen;
2272 2272
             $this->_column_template_path                         = EE_ADMIN_TEMPLATE
@@ -2312,7 +2312,7 @@  discard block
 block discarded – undo
2312 2312
      */
2313 2313
     protected function _espresso_ratings_request()
2314 2314
     {
2315
-        if (! apply_filters('FHEE_show_ratings_request_meta_box', true)) {
2315
+        if ( ! apply_filters('FHEE_show_ratings_request_meta_box', true)) {
2316 2316
             return;
2317 2317
         }
2318 2318
         $ratings_box_title = apply_filters(
@@ -2339,28 +2339,28 @@  discard block
 block discarded – undo
2339 2339
      */
2340 2340
     public function espresso_ratings_request()
2341 2341
     {
2342
-        EEH_Template::display_template(EE_ADMIN_TEMPLATE . 'espresso_ratings_request_content.template.php');
2342
+        EEH_Template::display_template(EE_ADMIN_TEMPLATE.'espresso_ratings_request_content.template.php');
2343 2343
     }
2344 2344
 
2345 2345
 
2346 2346
     public static function cached_rss_display($rss_id, $url)
2347 2347
     {
2348
-        $loading   = '<p class="widget-loading hide-if-no-js">'
2348
+        $loading = '<p class="widget-loading hide-if-no-js">'
2349 2349
                      . esc_html__('Loading&#8230;', 'event_espresso')
2350 2350
                      . '</p><p class="hide-if-js">'
2351 2351
                      . esc_html__('This widget requires JavaScript.', 'event_espresso')
2352 2352
                      . '</p>';
2353
-        $pre       = '<div class="espresso-rss-display">' . "\n\t";
2354
-        $pre       .= '<span id="' . esc_attr($rss_id) . '_url" class="hidden">' . esc_url_raw($url) . '</span>';
2355
-        $post      = '</div>' . "\n";
2356
-        $cache_key = 'ee_rss_' . md5($rss_id);
2353
+        $pre       = '<div class="espresso-rss-display">'."\n\t";
2354
+        $pre .= '<span id="'.esc_attr($rss_id).'_url" class="hidden">'.esc_url_raw($url).'</span>';
2355
+        $post      = '</div>'."\n";
2356
+        $cache_key = 'ee_rss_'.md5($rss_id);
2357 2357
         $output    = get_transient($cache_key);
2358 2358
         if ($output !== false) {
2359
-            echo wp_kses($pre . $output . $post, AllowedTags::getWithFormTags());
2359
+            echo wp_kses($pre.$output.$post, AllowedTags::getWithFormTags());
2360 2360
             return true;
2361 2361
         }
2362
-        if (! (defined('DOING_AJAX') && DOING_AJAX)) {
2363
-            echo wp_kses($pre . $loading . $post, AllowedTags::getWithFormTags());
2362
+        if ( ! (defined('DOING_AJAX') && DOING_AJAX)) {
2363
+            echo wp_kses($pre.$loading.$post, AllowedTags::getWithFormTags());
2364 2364
             return false;
2365 2365
         }
2366 2366
         ob_start();
@@ -2427,19 +2427,19 @@  discard block
 block discarded – undo
2427 2427
     public function espresso_sponsors_post_box()
2428 2428
     {
2429 2429
         EEH_Template::display_template(
2430
-            EE_ADMIN_TEMPLATE . 'admin_general_metabox_contents_espresso_sponsors.template.php'
2430
+            EE_ADMIN_TEMPLATE.'admin_general_metabox_contents_espresso_sponsors.template.php'
2431 2431
         );
2432 2432
     }
2433 2433
 
2434 2434
 
2435 2435
     private function _publish_post_box()
2436 2436
     {
2437
-        $meta_box_ref = 'espresso_' . $this->page_slug . '_editor_overview';
2437
+        $meta_box_ref = 'espresso_'.$this->page_slug.'_editor_overview';
2438 2438
         // if there is a array('label' => array('publishbox' => 'some title') ) present in the _page_config array
2439 2439
         // then we'll use that for the metabox label.
2440 2440
         // Otherwise we'll just use publish (publishbox itself could be an array of labels indexed by routes)
2441
-        if (! empty($this->_labels['publishbox'])) {
2442
-            $box_label = is_array($this->_labels['publishbox']) ? $this->_labels['publishbox'][ $this->_req_action ]
2441
+        if ( ! empty($this->_labels['publishbox'])) {
2442
+            $box_label = is_array($this->_labels['publishbox']) ? $this->_labels['publishbox'][$this->_req_action]
2443 2443
                 : $this->_labels['publishbox'];
2444 2444
         } else {
2445 2445
             $box_label = esc_html__('Publish', 'event_espresso');
@@ -2468,7 +2468,7 @@  discard block
 block discarded – undo
2468 2468
             ? $this->_template_args['publish_box_extra_content']
2469 2469
             : '';
2470 2470
         echo EEH_Template::display_template(
2471
-            EE_ADMIN_TEMPLATE . 'admin_details_publish_metabox.template.php',
2471
+            EE_ADMIN_TEMPLATE.'admin_details_publish_metabox.template.php',
2472 2472
             $this->_template_args,
2473 2473
             true
2474 2474
         );
@@ -2556,18 +2556,18 @@  discard block
 block discarded – undo
2556 2556
             );
2557 2557
         }
2558 2558
         $this->_template_args['publish_delete_link'] = $delete_link;
2559
-        if (! empty($name) && ! empty($id)) {
2560
-            $hidden_field_arr[ $name ] = [
2559
+        if ( ! empty($name) && ! empty($id)) {
2560
+            $hidden_field_arr[$name] = [
2561 2561
                 'type'  => 'hidden',
2562 2562
                 'value' => $id,
2563 2563
             ];
2564
-            $hf                        = $this->_generate_admin_form_fields($hidden_field_arr, 'array');
2564
+            $hf = $this->_generate_admin_form_fields($hidden_field_arr, 'array');
2565 2565
         } else {
2566 2566
             $hf = '';
2567 2567
         }
2568 2568
         // add hidden field
2569 2569
         $this->_template_args['publish_hidden_fields'] = is_array($hf) && ! empty($name)
2570
-            ? $hf[ $name ]['field']
2570
+            ? $hf[$name]['field']
2571 2571
             : $hf;
2572 2572
     }
2573 2573
 
@@ -2669,7 +2669,7 @@  discard block
 block discarded – undo
2669 2669
         }
2670 2670
         // 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)
2671 2671
         $call_back_func = $create_func
2672
-            ? static function ($post, $metabox) {
2672
+            ? static function($post, $metabox) {
2673 2673
                 do_action('AHEE_log', __FILE__, __FUNCTION__, '');
2674 2674
                 echo EEH_Template::display_template(
2675 2675
                     $metabox['args']['template_path'],
@@ -2679,7 +2679,7 @@  discard block
 block discarded – undo
2679 2679
             }
2680 2680
             : $callback;
2681 2681
         $this->addMetaBox(
2682
-            str_replace('_', '-', $action) . '-mbox',
2682
+            str_replace('_', '-', $action).'-mbox',
2683 2683
             $title,
2684 2684
             $call_back_func,
2685 2685
             $this->_wp_page_slug,
@@ -2796,13 +2796,13 @@  discard block
 block discarded – undo
2796 2796
             'event-espresso_page_espresso_',
2797 2797
             '',
2798 2798
             $this->_wp_page_slug
2799
-        ) . ' ' . $this->_req_action . '-route';
2799
+        ).' '.$this->_req_action.'-route';
2800 2800
 
2801 2801
         $template_path = $sidebar
2802 2802
             ? EE_ADMIN_TEMPLATE . 'admin_details_wrapper.template.php'
2803
-            : EE_ADMIN_TEMPLATE . 'admin_details_wrapper_no_sidebar.template.php';
2803
+            : EE_ADMIN_TEMPLATE.'admin_details_wrapper_no_sidebar.template.php';
2804 2804
         if ($this->request->isAjax()) {
2805
-            $template_path = EE_ADMIN_TEMPLATE . 'admin_details_wrapper_no_sidebar_ajax.template.php';
2805
+            $template_path = EE_ADMIN_TEMPLATE.'admin_details_wrapper_no_sidebar_ajax.template.php';
2806 2806
         }
2807 2807
         $template_path = ! empty($this->_column_template_path) ? $this->_column_template_path : $template_path;
2808 2808
 
@@ -2836,11 +2836,11 @@  discard block
 block discarded – undo
2836 2836
     public function display_admin_caf_preview_page($utm_campaign_source = '', $display_sidebar = true)
2837 2837
     {
2838 2838
         // let's generate a default preview action button if there isn't one already present.
2839
-        $this->_labels['buttons']['buy_now']           = esc_html__(
2839
+        $this->_labels['buttons']['buy_now'] = esc_html__(
2840 2840
             'Upgrade to Event Espresso 4 Right Now',
2841 2841
             'event_espresso'
2842 2842
         );
2843
-        $buy_now_url                                   = add_query_arg(
2843
+        $buy_now_url = add_query_arg(
2844 2844
             [
2845 2845
                 'ee_ver'       => 'ee4',
2846 2846
                 'utm_source'   => 'ee4_plugin_admin',
@@ -2860,8 +2860,8 @@  discard block
 block discarded – undo
2860 2860
                 true
2861 2861
             )
2862 2862
             : $this->_template_args['preview_action_button'];
2863
-        $this->_template_args['admin_page_content']    = EEH_Template::display_template(
2864
-            EE_ADMIN_TEMPLATE . 'admin_caf_full_page_preview.template.php',
2863
+        $this->_template_args['admin_page_content'] = EEH_Template::display_template(
2864
+            EE_ADMIN_TEMPLATE.'admin_caf_full_page_preview.template.php',
2865 2865
             $this->_template_args,
2866 2866
             true
2867 2867
         );
@@ -2919,7 +2919,7 @@  discard block
 block discarded – undo
2919 2919
         // setup search attributes
2920 2920
         $this->_set_search_attributes();
2921 2921
         $this->_template_args['current_page']     = $this->_wp_page_slug;
2922
-        $template_path                            = EE_ADMIN_TEMPLATE . 'admin_list_wrapper.template.php';
2922
+        $template_path                            = EE_ADMIN_TEMPLATE.'admin_list_wrapper.template.php';
2923 2923
         $this->_template_args['table_url']        = $this->request->isAjax()
2924 2924
             ? add_query_arg(['noheader' => 'true', 'route' => $this->_req_action], $this->_admin_base_url)
2925 2925
             : add_query_arg(['route' => $this->_req_action], $this->_admin_base_url);
@@ -2927,10 +2927,10 @@  discard block
 block discarded – undo
2927 2927
         $this->_template_args['current_route']    = $this->_req_action;
2928 2928
         $this->_template_args['list_table_class'] = get_class($this->_list_table_object);
2929 2929
         $ajax_sorting_callback                    = $this->_list_table_object->get_ajax_sorting_callback();
2930
-        if (! empty($ajax_sorting_callback)) {
2930
+        if ( ! empty($ajax_sorting_callback)) {
2931 2931
             $sortable_list_table_form_fields = wp_nonce_field(
2932
-                $ajax_sorting_callback . '_nonce',
2933
-                $ajax_sorting_callback . '_nonce',
2932
+                $ajax_sorting_callback.'_nonce',
2933
+                $ajax_sorting_callback.'_nonce',
2934 2934
                 false,
2935 2935
                 false
2936 2936
             );
@@ -2947,18 +2947,18 @@  discard block
 block discarded – undo
2947 2947
 
2948 2948
         $hidden_form_fields = $this->_template_args['list_table_hidden_fields'] ?? '';
2949 2949
 
2950
-        $nonce_ref          = $this->_req_action . '_nonce';
2950
+        $nonce_ref          = $this->_req_action.'_nonce';
2951 2951
         $hidden_form_fields .= '
2952
-            <input type="hidden" name="' . $nonce_ref . '" value="' . wp_create_nonce($nonce_ref) . '">';
2952
+            <input type="hidden" name="' . $nonce_ref.'" value="'.wp_create_nonce($nonce_ref).'">';
2953 2953
 
2954
-        $this->_template_args['list_table_hidden_fields']        = $hidden_form_fields;
2954
+        $this->_template_args['list_table_hidden_fields'] = $hidden_form_fields;
2955 2955
         // display message about search results?
2956 2956
         $search = $this->request->getRequestParam('s');
2957 2957
         $this->_template_args['before_list_table'] .= ! empty($search)
2958
-            ? '<p class="ee-search-results">' . sprintf(
2958
+            ? '<p class="ee-search-results">'.sprintf(
2959 2959
                 esc_html__('Displaying search results for the search string: %1$s', 'event_espresso'),
2960 2960
                 trim($search, '%')
2961
-            ) . '</p>'
2961
+            ).'</p>'
2962 2962
             : '';
2963 2963
         // filter before_list_table template arg
2964 2964
         $this->_template_args['before_list_table'] = apply_filters(
@@ -2992,7 +2992,7 @@  discard block
 block discarded – undo
2992 2992
         // convert to array and filter again
2993 2993
         // arrays are easier to inject new items in a specific location,
2994 2994
         // but would not be backwards compatible, so we have to add a new filter
2995
-        $this->_template_args['after_list_table']   = implode(
2995
+        $this->_template_args['after_list_table'] = implode(
2996 2996
             " \n",
2997 2997
             (array) apply_filters(
2998 2998
                 'FHEE__EE_Admin_Page___display_admin_list_table_page__after_list_table__template_args_array',
@@ -3047,7 +3047,7 @@  discard block
 block discarded – undo
3047 3047
             $this->page_slug
3048 3048
         );
3049 3049
         return EEH_Template::display_template(
3050
-            EE_ADMIN_TEMPLATE . 'admin_details_legend.template.php',
3050
+            EE_ADMIN_TEMPLATE.'admin_details_legend.template.php',
3051 3051
             $this->_template_args,
3052 3052
             true
3053 3053
         );
@@ -3163,16 +3163,16 @@  discard block
 block discarded – undo
3163 3163
             $this->_template_args['before_admin_page_content'] ?? ''
3164 3164
         );
3165 3165
 
3166
-        $this->_template_args['after_admin_page_content']  = apply_filters(
3166
+        $this->_template_args['after_admin_page_content'] = apply_filters(
3167 3167
             "FHEE_after_admin_page_content{$this->_current_page}{$this->_current_view}",
3168 3168
             $this->_template_args['after_admin_page_content'] ?? ''
3169 3169
         );
3170
-        $this->_template_args['after_admin_page_content']  .= $this->_set_help_popup_content();
3170
+        $this->_template_args['after_admin_page_content'] .= $this->_set_help_popup_content();
3171 3171
 
3172 3172
         if ($this->request->isAjax()) {
3173 3173
             $this->_template_args['admin_page_content'] = EEH_Template::display_template(
3174 3174
                 // $template_path,
3175
-                EE_ADMIN_TEMPLATE . 'admin_wrapper_ajax.template.php',
3175
+                EE_ADMIN_TEMPLATE.'admin_wrapper_ajax.template.php',
3176 3176
                 $this->_template_args,
3177 3177
                 true
3178 3178
             );
@@ -3181,7 +3181,7 @@  discard block
 block discarded – undo
3181 3181
         // load settings page wrapper template
3182 3182
         $template_path = $about
3183 3183
             ? EE_ADMIN_TEMPLATE . 'about_admin_wrapper.template.php'
3184
-            : EE_ADMIN_TEMPLATE . 'admin_wrapper.template.php';
3184
+            : EE_ADMIN_TEMPLATE.'admin_wrapper.template.php';
3185 3185
 
3186 3186
         EEH_Template::display_template($template_path, $this->_template_args);
3187 3187
     }
@@ -3265,12 +3265,12 @@  discard block
 block discarded – undo
3265 3265
         $default_names = ['save', 'save_and_close'];
3266 3266
         $buttons = '';
3267 3267
         foreach ($button_text as $key => $button) {
3268
-            $ref     = $default_names[ $key ];
3269
-            $name    = ! empty($actions) ? $actions[ $key ] : $ref;
3270
-            $buttons .= '<input type="submit" class="button button--primary ' . $ref . '" '
3271
-                        . 'value="' . $button . '" name="' . $name . '" '
3272
-                        . 'id="' . $this->_current_view . '_' . $ref . '" />';
3273
-            if (! $both) {
3268
+            $ref     = $default_names[$key];
3269
+            $name    = ! empty($actions) ? $actions[$key] : $ref;
3270
+            $buttons .= '<input type="submit" class="button button--primary '.$ref.'" '
3271
+                        . 'value="'.$button.'" name="'.$name.'" '
3272
+                        . 'id="'.$this->_current_view.'_'.$ref.'" />';
3273
+            if ( ! $both) {
3274 3274
                 break;
3275 3275
             }
3276 3276
         }
@@ -3310,13 +3310,13 @@  discard block
 block discarded – undo
3310 3310
                 'An error occurred. No action was set for this page\'s form.',
3311 3311
                 'event_espresso'
3312 3312
             );
3313
-            $dev_msg  = $user_msg . "\n"
3313
+            $dev_msg = $user_msg."\n"
3314 3314
                         . sprintf(
3315 3315
                             esc_html__('The $route argument is required for the %s->%s method.', 'event_espresso'),
3316 3316
                             __FUNCTION__,
3317 3317
                             __CLASS__
3318 3318
                         );
3319
-            EE_Error::add_error($user_msg . '||' . $dev_msg, __FILE__, __FUNCTION__, __LINE__);
3319
+            EE_Error::add_error($user_msg.'||'.$dev_msg, __FILE__, __FUNCTION__, __LINE__);
3320 3320
         }
3321 3321
         // open form
3322 3322
         $action = $this->_admin_base_url;
@@ -3324,9 +3324,9 @@  discard block
 block discarded – undo
3324 3324
             <form name='form' method='post' action='{$action}' id='{$route}_event_form' class='ee-admin-page-form' >
3325 3325
             ";
3326 3326
         // add nonce
3327
-        $nonce                                             =
3328
-            wp_nonce_field($route . '_nonce', $route . '_nonce', false, false);
3329
-        $this->_template_args['before_admin_page_content'] .= "\n\t" . $nonce;
3327
+        $nonce =
3328
+            wp_nonce_field($route.'_nonce', $route.'_nonce', false, false);
3329
+        $this->_template_args['before_admin_page_content'] .= "\n\t".$nonce;
3330 3330
         // add REQUIRED form action
3331 3331
         $hidden_fields = [
3332 3332
             'action' => ['type' => 'hidden', 'value' => $route],
@@ -3339,7 +3339,7 @@  discard block
 block discarded – undo
3339 3339
         $form_fields = $this->_generate_admin_form_fields($hidden_fields, 'array');
3340 3340
         // add fields to form
3341 3341
         foreach ((array) $form_fields as $form_field) {
3342
-            $this->_template_args['before_admin_page_content'] .= "\n\t" . $form_field['field'];
3342
+            $this->_template_args['before_admin_page_content'] .= "\n\t".$form_field['field'];
3343 3343
         }
3344 3344
         // close form
3345 3345
         $this->_template_args['after_admin_page_content'] = '</form>';
@@ -3422,12 +3422,12 @@  discard block
 block discarded – undo
3422 3422
         bool $override_overwrite = false
3423 3423
     ) {
3424 3424
         do_action('AHEE_log', __FILE__, __FUNCTION__, '');
3425
-        $notices      = EE_Error::get_notices(false);
3425
+        $notices = EE_Error::get_notices(false);
3426 3426
         // overwrite default success messages //BUT ONLY if overwrite not overridden
3427
-        if (! $override_overwrite || ! empty($notices['errors'])) {
3427
+        if ( ! $override_overwrite || ! empty($notices['errors'])) {
3428 3428
             EE_Error::overwrite_success();
3429 3429
         }
3430
-        if (! $override_overwrite && ! empty($what) && ! empty($action_desc) && empty($notices['errors'])) {
3430
+        if ( ! $override_overwrite && ! empty($what) && ! empty($action_desc) && empty($notices['errors'])) {
3431 3431
             // how many records affected ? more than one record ? or just one ?
3432 3432
             EE_Error::add_success(
3433 3433
                 sprintf(
@@ -3448,7 +3448,7 @@  discard block
 block discarded – undo
3448 3448
             );
3449 3449
         }
3450 3450
         // check that $query_args isn't something crazy
3451
-        if (! is_array($query_args)) {
3451
+        if ( ! is_array($query_args)) {
3452 3452
             $query_args = [];
3453 3453
         }
3454 3454
         /**
@@ -3480,7 +3480,7 @@  discard block
 block discarded – undo
3480 3480
             $redirect_url = admin_url('admin.php');
3481 3481
         }
3482 3482
         // merge any default query_args set in _default_route_query_args property
3483
-        if (! empty($this->_default_route_query_args) && ! $this->_is_UI_request) {
3483
+        if ( ! empty($this->_default_route_query_args) && ! $this->_is_UI_request) {
3484 3484
             $args_to_merge = [];
3485 3485
             foreach ($this->_default_route_query_args as $query_param => $query_value) {
3486 3486
                 // is there a wp_referer array in our _default_route_query_args property?
@@ -3492,15 +3492,15 @@  discard block
 block discarded – undo
3492 3492
                         }
3493 3493
                         // finally we will override any arguments in the referer with
3494 3494
                         // what might be set on the _default_route_query_args array.
3495
-                        if (isset($this->_default_route_query_args[ $reference ])) {
3496
-                            $args_to_merge[ $reference ] = urlencode($this->_default_route_query_args[ $reference ]);
3495
+                        if (isset($this->_default_route_query_args[$reference])) {
3496
+                            $args_to_merge[$reference] = urlencode($this->_default_route_query_args[$reference]);
3497 3497
                         } else {
3498
-                            $args_to_merge[ $reference ] = urlencode($value);
3498
+                            $args_to_merge[$reference] = urlencode($value);
3499 3499
                         }
3500 3500
                     }
3501 3501
                     continue;
3502 3502
                 }
3503
-                $args_to_merge[ $query_param ] = $query_value;
3503
+                $args_to_merge[$query_param] = $query_value;
3504 3504
             }
3505 3505
             // now let's merge these arguments but override with what was specifically sent in to the
3506 3506
             // redirect.
@@ -3512,19 +3512,19 @@  discard block
 block discarded – undo
3512 3512
         if (isset($query_args['action'])) {
3513 3513
             // manually generate wp_nonce and merge that with the query vars
3514 3514
             // becuz the wp_nonce_url function wrecks havoc on some vars
3515
-            $query_args['_wpnonce'] = wp_create_nonce($query_args['action'] . '_nonce');
3515
+            $query_args['_wpnonce'] = wp_create_nonce($query_args['action'].'_nonce');
3516 3516
         }
3517 3517
         // we're adding some hooks and filters in here for processing any things just before redirects
3518 3518
         // (example: an admin page has done an insert or update and we want to run something after that).
3519
-        do_action('AHEE_redirect_' . $this->class_name . $this->_req_action, $query_args);
3519
+        do_action('AHEE_redirect_'.$this->class_name.$this->_req_action, $query_args);
3520 3520
         $redirect_url = apply_filters(
3521
-            'FHEE_redirect_' . $this->class_name . $this->_req_action,
3521
+            'FHEE_redirect_'.$this->class_name.$this->_req_action,
3522 3522
             EE_Admin_Page::add_query_args_and_nonce($query_args, $redirect_url),
3523 3523
             $query_args
3524 3524
         );
3525 3525
         // check if we're doing ajax.  If we are then lets just return the results and js can handle how it wants.
3526 3526
         if ($this->request->isAjax()) {
3527
-            $default_data                    = [
3527
+            $default_data = [
3528 3528
                 'close'        => true,
3529 3529
                 'redirect_url' => $redirect_url,
3530 3530
                 'where'        => 'main',
@@ -3574,7 +3574,7 @@  discard block
 block discarded – undo
3574 3574
         }
3575 3575
         $this->_template_args['notices'] = EE_Error::get_notices();
3576 3576
         // IF this isn't ajax we need to create a transient for the notices using the route (however, overridden if $sticky_notices == true)
3577
-        if (! $this->request->isAjax() || $sticky_notices) {
3577
+        if ( ! $this->request->isAjax() || $sticky_notices) {
3578 3578
             $route = isset($query_args['action']) ? $query_args['action'] : 'default';
3579 3579
             $this->_add_transient(
3580 3580
                 $route,
@@ -3614,7 +3614,7 @@  discard block
 block discarded – undo
3614 3614
         $exclude_nonce = false
3615 3615
     ) {
3616 3616
         // first let's validate the action (if $base_url is FALSE otherwise validation will happen further along)
3617
-        if (empty($base_url) && ! isset($this->_page_routes[ $action ])) {
3617
+        if (empty($base_url) && ! isset($this->_page_routes[$action])) {
3618 3618
             throw new EE_Error(
3619 3619
                 sprintf(
3620 3620
                     esc_html__(
@@ -3625,7 +3625,7 @@  discard block
 block discarded – undo
3625 3625
                 )
3626 3626
             );
3627 3627
         }
3628
-        if (! isset($this->_labels['buttons'][ $type ])) {
3628
+        if ( ! isset($this->_labels['buttons'][$type])) {
3629 3629
             throw new EE_Error(
3630 3630
                 sprintf(
3631 3631
                     esc_html__(
@@ -3638,7 +3638,7 @@  discard block
 block discarded – undo
3638 3638
         }
3639 3639
         // finally check user access for this button.
3640 3640
         $has_access = $this->check_user_access($action, true);
3641
-        if (! $has_access) {
3641
+        if ( ! $has_access) {
3642 3642
             return '';
3643 3643
         }
3644 3644
         $_base_url  = ! $base_url ? $this->_admin_base_url : $base_url;
@@ -3646,11 +3646,11 @@  discard block
 block discarded – undo
3646 3646
             'action' => $action,
3647 3647
         ];
3648 3648
         // merge extra_request args but make sure our original action takes precedence and doesn't get overwritten.
3649
-        if (! empty($extra_request)) {
3649
+        if ( ! empty($extra_request)) {
3650 3650
             $query_args = array_merge($extra_request, $query_args);
3651 3651
         }
3652 3652
         $url = EE_Admin_Page::add_query_args_and_nonce($query_args, $_base_url, false, $exclude_nonce);
3653
-        return EEH_Template::get_button_or_link($url, $this->_labels['buttons'][ $type ], $class);
3653
+        return EEH_Template::get_button_or_link($url, $this->_labels['buttons'][$type], $class);
3654 3654
     }
3655 3655
 
3656 3656
 
@@ -3676,7 +3676,7 @@  discard block
 block discarded – undo
3676 3676
                 'FHEE__EE_Admin_Page___per_page_screen_options__default',
3677 3677
                 20
3678 3678
             ),
3679
-            'option'  => $this->_current_page . '_' . $this->_current_view . '_per_page',
3679
+            'option'  => $this->_current_page.'_'.$this->_current_view.'_per_page',
3680 3680
         ];
3681 3681
         // ONLY add the screen option if the user has access to it.
3682 3682
         if ($this->check_user_access($this->_current_view, true)) {
@@ -3697,18 +3697,18 @@  discard block
 block discarded – undo
3697 3697
     {
3698 3698
         if ($this->request->requestParamIsSet('wp_screen_options')) {
3699 3699
             check_admin_referer('screen-options-nonce', 'screenoptionnonce');
3700
-            if (! $user = wp_get_current_user()) {
3700
+            if ( ! $user = wp_get_current_user()) {
3701 3701
                 return;
3702 3702
             }
3703 3703
             $option = $this->request->getRequestParam('wp_screen_options[option]', '', 'key');
3704
-            if (! $option) {
3704
+            if ( ! $option) {
3705 3705
                 return;
3706 3706
             }
3707
-            $value  = $this->request->getRequestParam('wp_screen_options[value]', 0, 'int');
3707
+            $value = $this->request->getRequestParam('wp_screen_options[value]', 0, 'int');
3708 3708
             $map_option = $option;
3709 3709
             $option     = str_replace('-', '_', $option);
3710 3710
             switch ($map_option) {
3711
-                case $this->_current_page . '_' . $this->_current_view . '_per_page':
3711
+                case $this->_current_page.'_'.$this->_current_view.'_per_page':
3712 3712
                     $max_value = apply_filters(
3713 3713
                         'FHEE__EE_Admin_Page___set_per_page_screen_options__max_value',
3714 3714
                         999,
@@ -3765,13 +3765,13 @@  discard block
 block discarded – undo
3765 3765
     protected function _add_transient($route, $data, $notices = false, $skip_route_verify = false)
3766 3766
     {
3767 3767
         $user_id = get_current_user_id();
3768
-        if (! $skip_route_verify) {
3768
+        if ( ! $skip_route_verify) {
3769 3769
             $this->_verify_route($route);
3770 3770
         }
3771 3771
         // now let's set the string for what kind of transient we're setting
3772 3772
         $transient = $notices
3773
-            ? 'ee_rte_n_tx_' . $route . '_' . $user_id
3774
-            : 'rte_tx_' . $route . '_' . $user_id;
3773
+            ? 'ee_rte_n_tx_'.$route.'_'.$user_id
3774
+            : 'rte_tx_'.$route.'_'.$user_id;
3775 3775
         $data      = $notices ? ['notices' => $data] : $data;
3776 3776
         // is there already a transient for this route?  If there is then let's ADD to that transient
3777 3777
         $existing = is_multisite() && is_network_admin()
@@ -3800,8 +3800,8 @@  discard block
 block discarded – undo
3800 3800
         $user_id   = get_current_user_id();
3801 3801
         $route     = ! $route ? $this->_req_action : $route;
3802 3802
         $transient = $notices
3803
-            ? 'ee_rte_n_tx_' . $route . '_' . $user_id
3804
-            : 'rte_tx_' . $route . '_' . $user_id;
3803
+            ? 'ee_rte_n_tx_'.$route.'_'.$user_id
3804
+            : 'rte_tx_'.$route.'_'.$user_id;
3805 3805
         $data      = is_multisite() && is_network_admin()
3806 3806
             ? get_site_transient($transient)
3807 3807
             : get_transient($transient);
@@ -4037,7 +4037,7 @@  discard block
 block discarded – undo
4037 4037
      */
4038 4038
     protected function _next_link($url, $class = 'dashicons dashicons-arrow-right')
4039 4039
     {
4040
-        return '<a class="' . $class . '" href="' . $url . '"></a>';
4040
+        return '<a class="'.$class.'" href="'.$url.'"></a>';
4041 4041
     }
4042 4042
 
4043 4043
 
@@ -4050,7 +4050,7 @@  discard block
 block discarded – undo
4050 4050
      */
4051 4051
     protected function _previous_link($url, $class = 'dashicons dashicons-arrow-left')
4052 4052
     {
4053
-        return '<a class="' . $class . '" href="' . $url . '"></a>';
4053
+        return '<a class="'.$class.'" href="'.$url.'"></a>';
4054 4054
     }
4055 4055
 
4056 4056
 
@@ -4198,13 +4198,13 @@  discard block
 block discarded – undo
4198 4198
         ?callable $callback = null
4199 4199
     ): bool {
4200 4200
         $entity_ID = absint($entity_ID);
4201
-        if (! $entity_ID) {
4201
+        if ( ! $entity_ID) {
4202 4202
             $this->trashRestoreDeleteError($action, $entity_model);
4203 4203
         }
4204 4204
         $result = 0;
4205 4205
         try {
4206 4206
             $entity = $entity_model->get_one_by_ID($entity_ID);
4207
-            if (! $entity instanceof EE_Base_Class) {
4207
+            if ( ! $entity instanceof EE_Base_Class) {
4208 4208
                 throw new DomainException(
4209 4209
                     sprintf(
4210 4210
                         esc_html__(
@@ -4255,7 +4255,7 @@  discard block
 block discarded – undo
4255 4255
                 )
4256 4256
             );
4257 4257
         }
4258
-        if (! $entity_model->has_field($delete_column)) {
4258
+        if ( ! $entity_model->has_field($delete_column)) {
4259 4259
             throw new DomainException(
4260 4260
                 sprintf(
4261 4261
                     esc_html__(
Please login to merge, or discard this patch.
caffeinated/admin/new/pricing/espresso_events_Pricing_Hooks.class.php 1 patch
Indentation   +2175 added lines, -2175 removed lines patch added patch discarded remove patch
@@ -15,2193 +15,2193 @@
 block discarded – undo
15 15
  */
16 16
 class espresso_events_Pricing_Hooks extends EE_Admin_Hooks
17 17
 {
18
-    /**
19
-     * This property is just used to hold the status of whether an event is currently being
20
-     * created (true) or edited (false)
21
-     *
22
-     * @access protected
23
-     * @var bool
24
-     */
25
-    protected $_is_creating_event;
26
-
27
-    /**
28
-     * Used to contain the format strings for date and time that will be used for php date and
29
-     * time.
30
-     * Is set in the _set_hooks_properties() method.
31
-     *
32
-     * @var array
33
-     */
34
-    protected $_date_format_strings;
35
-
36
-    /**
37
-     * @var string $_date_time_format
38
-     */
39
-    protected $_date_time_format;
40
-
41
-
42
-    /**
43
-     * @throws InvalidArgumentException
44
-     * @throws InvalidInterfaceException
45
-     * @throws InvalidDataTypeException
46
-     */
47
-    protected function _set_hooks_properties()
48
-    {
49
-        $this->_name = 'pricing';
50
-        // capability check
51
-        if (
52
-            $this->_adminpage_obj->adminConfig()->useAdvancedEditor()
53
-            || ! EE_Registry::instance()->CAP->current_user_can(
54
-                'ee_read_default_prices',
55
-                'advanced_ticket_datetime_metabox'
56
-            )
57
-        ) {
58
-            $this->_metaboxes      = [];
59
-            $this->_scripts_styles = [];
60
-            return;
61
-        }
62
-        $this->_setup_metaboxes();
63
-        $this->_set_date_time_formats();
64
-        $this->_validate_format_strings();
65
-        $this->_set_scripts_styles();
66
-        add_filter(
67
-            'FHEE__Events_Admin_Page___insert_update_cpt_item__event_update_callbacks',
68
-            [$this, 'caf_updates']
69
-        );
70
-    }
71
-
72
-
73
-    /**
74
-     * @return void
75
-     */
76
-    protected function _setup_metaboxes()
77
-    {
78
-        // if we were going to add our own metaboxes we'd use the below.
79
-        $this->_metaboxes        = [
80
-            0 => [
81
-                'page_route' => ['edit', 'create_new'],
82
-                'func'       => [$this, 'pricing_metabox'],
83
-                'label'      => esc_html__('Event Tickets & Datetimes', 'event_espresso'),
84
-                'priority'   => 'high',
85
-                'context'    => 'normal',
86
-            ],
87
-        ];
88
-        $this->_remove_metaboxes = [
89
-            0 => [
90
-                'page_route' => ['edit', 'create_new'],
91
-                'id'         => 'espresso_event_editor_tickets',
92
-                'context'    => 'normal',
93
-            ],
94
-        ];
95
-    }
96
-
97
-
98
-    /**
99
-     * @return void
100
-     */
101
-    protected function _set_date_time_formats()
102
-    {
103
-        /**
104
-         * Format strings for date and time.  Defaults are existing behaviour from 4.1.
105
-         * Note, that if you return null as the value for 'date', and 'time' in the array, then
106
-         * EE will automatically use the set wp_options, 'date_format', and 'time_format'.
107
-         *
108
-         * @since 4.6.7
109
-         * @var array  Expected an array returned with 'date' and 'time' keys.
110
-         */
111
-        $this->_date_format_strings = apply_filters(
112
-            'FHEE__espresso_events_Pricing_Hooks___set_hooks_properties__date_format_strings',
113
-            [
114
-                'date' => 'Y-m-d',
115
-                'time' => 'h:i a',
116
-            ]
117
-        );
118
-        // validate
119
-        $this->_date_format_strings['date'] = $this->_date_format_strings['date'] ?? '';
120
-        $this->_date_format_strings['time'] = $this->_date_format_strings['time'] ?? '';
121
-
122
-        $this->_date_time_format = $this->_date_format_strings['date'] . ' ' . $this->_date_format_strings['time'];
123
-    }
124
-
125
-
126
-    /**
127
-     * @return void
128
-     */
129
-    protected function _validate_format_strings()
130
-    {
131
-        // validate format strings
132
-        $format_validation = EEH_DTT_Helper::validate_format_string(
133
-            $this->_date_time_format
134
-        );
135
-        if (is_array($format_validation)) {
136
-            $msg = '<p>';
137
-            $msg .= sprintf(
138
-                esc_html__(
139
-                    'The format "%s" was likely added via a filter and is invalid for the following reasons:',
140
-                    'event_espresso'
141
-                ),
142
-                $this->_date_time_format
143
-            );
144
-            $msg .= '</p><ul>';
145
-            foreach ($format_validation as $error) {
146
-                $msg .= '<li>' . $error . '</li>';
147
-            }
148
-            $msg .= '</ul><p>';
149
-            $msg .= sprintf(
150
-                esc_html__(
151
-                    '%sPlease note that your date and time formats have been reset to "Y-m-d" and "h:i a" respectively.%s',
152
-                    'event_espresso'
153
-                ),
154
-                '<span style="color:#D54E21;">',
155
-                '</span>'
156
-            );
157
-            $msg .= '</p>';
158
-            EE_Error::add_attention($msg, __FILE__, __FUNCTION__, __LINE__);
159
-            $this->_date_format_strings = [
160
-                'date' => 'Y-m-d',
161
-                'time' => 'h:i a',
162
-            ];
163
-        }
164
-    }
165
-
166
-
167
-    /**
168
-     * @return void
169
-     */
170
-    protected function _set_scripts_styles()
171
-    {
172
-        $this->_scripts_styles = [
173
-            'registers'   => [
174
-                'ee-tickets-datetimes-css' => [
175
-                    'url'  => PRICING_ASSETS_URL . 'event-tickets-datetimes.css',
176
-                    'type' => 'css',
177
-                ],
178
-                'ee-dtt-ticket-metabox'    => [
179
-                    'url'     => PRICING_ASSETS_URL . 'ee-datetime-ticket-metabox.js',
180
-                    'depends' => ['ee-datepicker', 'ee-dialog', 'underscore'],
181
-                ],
182
-            ],
183
-            'deregisters' => [
184
-                'event-editor-css'       => ['type' => 'css'],
185
-                'event-datetime-metabox' => ['type' => 'js'],
186
-            ],
187
-            'enqueues'    => [
188
-                'ee-tickets-datetimes-css' => ['edit', 'create_new'],
189
-                'ee-dtt-ticket-metabox'    => ['edit', 'create_new'],
190
-            ],
191
-            'localize'    => [
192
-                'ee-dtt-ticket-metabox' => [
193
-                    'DTT_TRASH_BLOCK'       => [
194
-                        'main_warning'            => esc_html__(
195
-                            'The Datetime you are attempting to trash is the only datetime selected for the following ticket(s):',
196
-                            'event_espresso'
197
-                        ),
198
-                        'after_warning'           => esc_html__(
199
-                            'In order to trash this datetime you must first make sure the above ticket(s) are assigned to other datetimes.',
200
-                            'event_espresso'
201
-                        ),
202
-                        'cancel_button'           => '
18
+	/**
19
+	 * This property is just used to hold the status of whether an event is currently being
20
+	 * created (true) or edited (false)
21
+	 *
22
+	 * @access protected
23
+	 * @var bool
24
+	 */
25
+	protected $_is_creating_event;
26
+
27
+	/**
28
+	 * Used to contain the format strings for date and time that will be used for php date and
29
+	 * time.
30
+	 * Is set in the _set_hooks_properties() method.
31
+	 *
32
+	 * @var array
33
+	 */
34
+	protected $_date_format_strings;
35
+
36
+	/**
37
+	 * @var string $_date_time_format
38
+	 */
39
+	protected $_date_time_format;
40
+
41
+
42
+	/**
43
+	 * @throws InvalidArgumentException
44
+	 * @throws InvalidInterfaceException
45
+	 * @throws InvalidDataTypeException
46
+	 */
47
+	protected function _set_hooks_properties()
48
+	{
49
+		$this->_name = 'pricing';
50
+		// capability check
51
+		if (
52
+			$this->_adminpage_obj->adminConfig()->useAdvancedEditor()
53
+			|| ! EE_Registry::instance()->CAP->current_user_can(
54
+				'ee_read_default_prices',
55
+				'advanced_ticket_datetime_metabox'
56
+			)
57
+		) {
58
+			$this->_metaboxes      = [];
59
+			$this->_scripts_styles = [];
60
+			return;
61
+		}
62
+		$this->_setup_metaboxes();
63
+		$this->_set_date_time_formats();
64
+		$this->_validate_format_strings();
65
+		$this->_set_scripts_styles();
66
+		add_filter(
67
+			'FHEE__Events_Admin_Page___insert_update_cpt_item__event_update_callbacks',
68
+			[$this, 'caf_updates']
69
+		);
70
+	}
71
+
72
+
73
+	/**
74
+	 * @return void
75
+	 */
76
+	protected function _setup_metaboxes()
77
+	{
78
+		// if we were going to add our own metaboxes we'd use the below.
79
+		$this->_metaboxes        = [
80
+			0 => [
81
+				'page_route' => ['edit', 'create_new'],
82
+				'func'       => [$this, 'pricing_metabox'],
83
+				'label'      => esc_html__('Event Tickets & Datetimes', 'event_espresso'),
84
+				'priority'   => 'high',
85
+				'context'    => 'normal',
86
+			],
87
+		];
88
+		$this->_remove_metaboxes = [
89
+			0 => [
90
+				'page_route' => ['edit', 'create_new'],
91
+				'id'         => 'espresso_event_editor_tickets',
92
+				'context'    => 'normal',
93
+			],
94
+		];
95
+	}
96
+
97
+
98
+	/**
99
+	 * @return void
100
+	 */
101
+	protected function _set_date_time_formats()
102
+	{
103
+		/**
104
+		 * Format strings for date and time.  Defaults are existing behaviour from 4.1.
105
+		 * Note, that if you return null as the value for 'date', and 'time' in the array, then
106
+		 * EE will automatically use the set wp_options, 'date_format', and 'time_format'.
107
+		 *
108
+		 * @since 4.6.7
109
+		 * @var array  Expected an array returned with 'date' and 'time' keys.
110
+		 */
111
+		$this->_date_format_strings = apply_filters(
112
+			'FHEE__espresso_events_Pricing_Hooks___set_hooks_properties__date_format_strings',
113
+			[
114
+				'date' => 'Y-m-d',
115
+				'time' => 'h:i a',
116
+			]
117
+		);
118
+		// validate
119
+		$this->_date_format_strings['date'] = $this->_date_format_strings['date'] ?? '';
120
+		$this->_date_format_strings['time'] = $this->_date_format_strings['time'] ?? '';
121
+
122
+		$this->_date_time_format = $this->_date_format_strings['date'] . ' ' . $this->_date_format_strings['time'];
123
+	}
124
+
125
+
126
+	/**
127
+	 * @return void
128
+	 */
129
+	protected function _validate_format_strings()
130
+	{
131
+		// validate format strings
132
+		$format_validation = EEH_DTT_Helper::validate_format_string(
133
+			$this->_date_time_format
134
+		);
135
+		if (is_array($format_validation)) {
136
+			$msg = '<p>';
137
+			$msg .= sprintf(
138
+				esc_html__(
139
+					'The format "%s" was likely added via a filter and is invalid for the following reasons:',
140
+					'event_espresso'
141
+				),
142
+				$this->_date_time_format
143
+			);
144
+			$msg .= '</p><ul>';
145
+			foreach ($format_validation as $error) {
146
+				$msg .= '<li>' . $error . '</li>';
147
+			}
148
+			$msg .= '</ul><p>';
149
+			$msg .= sprintf(
150
+				esc_html__(
151
+					'%sPlease note that your date and time formats have been reset to "Y-m-d" and "h:i a" respectively.%s',
152
+					'event_espresso'
153
+				),
154
+				'<span style="color:#D54E21;">',
155
+				'</span>'
156
+			);
157
+			$msg .= '</p>';
158
+			EE_Error::add_attention($msg, __FILE__, __FUNCTION__, __LINE__);
159
+			$this->_date_format_strings = [
160
+				'date' => 'Y-m-d',
161
+				'time' => 'h:i a',
162
+			];
163
+		}
164
+	}
165
+
166
+
167
+	/**
168
+	 * @return void
169
+	 */
170
+	protected function _set_scripts_styles()
171
+	{
172
+		$this->_scripts_styles = [
173
+			'registers'   => [
174
+				'ee-tickets-datetimes-css' => [
175
+					'url'  => PRICING_ASSETS_URL . 'event-tickets-datetimes.css',
176
+					'type' => 'css',
177
+				],
178
+				'ee-dtt-ticket-metabox'    => [
179
+					'url'     => PRICING_ASSETS_URL . 'ee-datetime-ticket-metabox.js',
180
+					'depends' => ['ee-datepicker', 'ee-dialog', 'underscore'],
181
+				],
182
+			],
183
+			'deregisters' => [
184
+				'event-editor-css'       => ['type' => 'css'],
185
+				'event-datetime-metabox' => ['type' => 'js'],
186
+			],
187
+			'enqueues'    => [
188
+				'ee-tickets-datetimes-css' => ['edit', 'create_new'],
189
+				'ee-dtt-ticket-metabox'    => ['edit', 'create_new'],
190
+			],
191
+			'localize'    => [
192
+				'ee-dtt-ticket-metabox' => [
193
+					'DTT_TRASH_BLOCK'       => [
194
+						'main_warning'            => esc_html__(
195
+							'The Datetime you are attempting to trash is the only datetime selected for the following ticket(s):',
196
+							'event_espresso'
197
+						),
198
+						'after_warning'           => esc_html__(
199
+							'In order to trash this datetime you must first make sure the above ticket(s) are assigned to other datetimes.',
200
+							'event_espresso'
201
+						),
202
+						'cancel_button'           => '
203 203
                             <button class="button--secondary ee-modal-cancel">
204 204
                                 ' . esc_html__('Cancel', 'event_espresso') . '
205 205
                             </button>',
206
-                        'close_button'            => '
206
+						'close_button'            => '
207 207
                             <button class="button--secondary ee-modal-cancel">
208 208
                                 ' . esc_html__('Close', 'event_espresso') . '
209 209
                             </button>',
210
-                        'single_warning_from_tkt' => esc_html__(
211
-                            'The Datetime you are attempting to unassign from this ticket is the only remaining datetime for this ticket. Tickets must always have at least one datetime assigned to them.',
212
-                            'event_espresso'
213
-                        ),
214
-                        'single_warning_from_dtt' => esc_html__(
215
-                            'The ticket you are attempting to unassign from this datetime cannot be unassigned because the datetime is the only remaining datetime for the ticket.  Tickets must always have at least one datetime assigned to them.',
216
-                            'event_espresso'
217
-                        ),
218
-                        'dismiss_button'          => '
210
+						'single_warning_from_tkt' => esc_html__(
211
+							'The Datetime you are attempting to unassign from this ticket is the only remaining datetime for this ticket. Tickets must always have at least one datetime assigned to them.',
212
+							'event_espresso'
213
+						),
214
+						'single_warning_from_dtt' => esc_html__(
215
+							'The ticket you are attempting to unassign from this datetime cannot be unassigned because the datetime is the only remaining datetime for the ticket.  Tickets must always have at least one datetime assigned to them.',
216
+							'event_espresso'
217
+						),
218
+						'dismiss_button'          => '
219 219
                             <button class="button--secondary ee-modal-cancel">
220 220
                                 ' . esc_html__('Dismiss', 'event_espresso') . '
221 221
                             </button>',
222
-                    ],
223
-                    'DTT_ERROR_MSG'         => [
224
-                        'no_ticket_name' => esc_html__('General Admission', 'event_espresso'),
225
-                        'dismiss_button' => '
222
+					],
223
+					'DTT_ERROR_MSG'         => [
224
+						'no_ticket_name' => esc_html__('General Admission', 'event_espresso'),
225
+						'dismiss_button' => '
226 226
                             <div class="save-cancel-button-container">
227 227
                                 <button class="button--secondary ee-modal-cancel">
228 228
                                     ' . esc_html__('Dismiss', 'event_espresso') . '
229 229
                                 </button>
230 230
                             </div>',
231
-                    ],
232
-                    'DTT_OVERSELL_WARNING'  => [
233
-                        'datetime_ticket' => esc_html__(
234
-                            'You cannot add this ticket to this datetime because it has a sold amount that is greater than the amount of spots remaining for this datetime.',
235
-                            'event_espresso'
236
-                        ),
237
-                        'ticket_datetime' => esc_html__(
238
-                            'You cannot add this datetime to this ticket because the ticket has a sold amount that is greater than the amount of spots remaining on the datetime.',
239
-                            'event_espresso'
240
-                        ),
241
-                    ],
242
-                    'DTT_CONVERTED_FORMATS' => EEH_DTT_Helper::convert_php_to_js_and_moment_date_formats(
243
-                        $this->_date_format_strings['date'],
244
-                        $this->_date_format_strings['time']
245
-                    ),
246
-                    'DTT_START_OF_WEEK'     => ['dayValue' => (int) get_option('start_of_week')],
247
-                ],
248
-            ],
249
-        ];
250
-    }
251
-
252
-
253
-    /**
254
-     * @param array $update_callbacks
255
-     * @return array
256
-     */
257
-    public function caf_updates(array $update_callbacks): array
258
-    {
259
-        unset($update_callbacks['_default_tickets_update']);
260
-        $update_callbacks['datetime_and_tickets_caf_update'] = [$this, 'datetime_and_tickets_caf_update'];
261
-        return $update_callbacks;
262
-    }
263
-
264
-
265
-    /**
266
-     * Handles saving everything related to Tickets (datetimes, tickets, prices)
267
-     *
268
-     * @param EE_Event $event The Event object we're attaching data to
269
-     * @param array    $data  The request data from the form
270
-     * @throws ReflectionException
271
-     * @throws Exception
272
-     * @throws InvalidInterfaceException
273
-     * @throws InvalidDataTypeException
274
-     * @throws EE_Error
275
-     * @throws InvalidArgumentException
276
-     */
277
-    public function datetime_and_tickets_caf_update(EE_Event $event, array $data)
278
-    {
279
-        // first we need to start with datetimes cause they are the "root" items attached to events.
280
-        $saved_datetimes = $this->_update_datetimes($event, $data);
281
-        // next tackle the tickets (and prices?)
282
-        $this->_update_tickets($event, $saved_datetimes, $data);
283
-    }
284
-
285
-
286
-    /**
287
-     * update event_datetimes
288
-     *
289
-     * @param EE_Event $event Event being updated
290
-     * @param array    $data  the request data from the form
291
-     * @return EE_Datetime[]
292
-     * @throws Exception
293
-     * @throws ReflectionException
294
-     * @throws InvalidInterfaceException
295
-     * @throws InvalidDataTypeException
296
-     * @throws InvalidArgumentException
297
-     * @throws EE_Error
298
-     */
299
-    protected function _update_datetimes(EE_Event $event, array $data): array
300
-    {
301
-        $saved_datetime_ids  = [];
302
-        $saved_datetime_objs = [];
303
-        $timezone       = $data['timezone_string'] ?? null;
304
-        $datetime_model = EEM_Datetime::instance($timezone);
305
-
306
-        if (empty($data['edit_event_datetimes']) || ! is_array($data['edit_event_datetimes'])) {
307
-            throw new InvalidArgumentException(
308
-                esc_html__(
309
-                    'The "edit_event_datetimes" array is invalid therefore the event can not be updated.',
310
-                    'event_espresso'
311
-                )
312
-            );
313
-        }
314
-        foreach ($data['edit_event_datetimes'] as $row => $datetime_data) {
315
-            // trim all values to ensure any excess whitespace is removed.
316
-            $datetime_data = array_map(
317
-                function ($datetime_data) {
318
-                    return is_array($datetime_data) ? $datetime_data : trim($datetime_data);
319
-                },
320
-                $datetime_data
321
-            );
322
-
323
-            $datetime_data['DTT_EVT_end'] = isset($datetime_data['DTT_EVT_end'])
324
-                                            && ! empty($datetime_data['DTT_EVT_end'])
325
-                ? $datetime_data['DTT_EVT_end']
326
-                : $datetime_data['DTT_EVT_start'];
327
-            $datetime_values              = [
328
-                'DTT_ID'          => ! empty($datetime_data['DTT_ID'])
329
-                    ? $datetime_data['DTT_ID']
330
-                    : null,
331
-                'DTT_name'        => ! empty($datetime_data['DTT_name'])
332
-                    ? $datetime_data['DTT_name']
333
-                    : '',
334
-                'DTT_description' => ! empty($datetime_data['DTT_description'])
335
-                    ? $datetime_data['DTT_description']
336
-                    : '',
337
-                'DTT_EVT_start'   => $datetime_data['DTT_EVT_start'],
338
-                'DTT_EVT_end'     => $datetime_data['DTT_EVT_end'],
339
-                'DTT_reg_limit'   => empty($datetime_data['DTT_reg_limit'])
340
-                    ? EE_INF
341
-                    : $datetime_data['DTT_reg_limit'],
342
-                'DTT_order'       => ! isset($datetime_data['DTT_order'])
343
-                    ? $row
344
-                    : $datetime_data['DTT_order'],
345
-            ];
346
-
347
-            // if we have an id then let's get existing object first and then set the new values.
348
-            // Otherwise we instantiate a new object for save.
349
-            if (! empty($datetime_data['DTT_ID'])) {
350
-                $datetime = EE_Registry::instance()
351
-                                       ->load_model('Datetime', [$timezone])
352
-                                       ->get_one_by_ID($datetime_data['DTT_ID']);
353
-                // set date and time format according to what is set in this class.
354
-                $datetime->set_date_format($this->_date_format_strings['date']);
355
-                $datetime->set_time_format($this->_date_format_strings['time']);
356
-                foreach ($datetime_values as $field => $value) {
357
-                    $datetime->set($field, $value);
358
-                }
359
-
360
-                // make sure the $datetime_id here is saved just in case
361
-                // after the add_relation_to() the autosave replaces it.
362
-                // We need to do this so we dont' TRASH the parent DTT.
363
-                // (save the ID for both key and value to avoid duplications)
364
-                $saved_datetime_ids[ $datetime->ID() ] = $datetime->ID();
365
-            } else {
366
-                $datetime = EE_Datetime::new_instance(
367
-                    $datetime_values,
368
-                    $timezone,
369
-                    [$this->_date_format_strings['date'], $this->_date_format_strings['time']]
370
-                );
371
-                foreach ($datetime_values as $field => $value) {
372
-                    $datetime->set($field, $value);
373
-                }
374
-            }
375
-            $datetime->save();
376
-            do_action(
377
-                'AHEE__espresso_events_Pricing_Hooks___update_datetimes_after_save',
378
-                $datetime,
379
-                $row,
380
-                $datetime_data,
381
-                $data
382
-            );
383
-            $datetime = $event->_add_relation_to($datetime, 'Datetime');
384
-            // before going any further make sure our dates are setup correctly
385
-            // so that the end date is always equal or greater than the start date.
386
-            if ($datetime->get_raw('DTT_EVT_start') > $datetime->get_raw('DTT_EVT_end')) {
387
-                $datetime->set('DTT_EVT_end', $datetime->get('DTT_EVT_start'));
388
-                $datetime = EEH_DTT_Helper::date_time_add($datetime, 'DTT_EVT_end', 'days');
389
-                $datetime->save();
390
-            }
391
-            // now we have to make sure we add the new DTT_ID to the $saved_datetime_ids array
392
-            // because it is possible there was a new one created for the autosave.
393
-            // (save the ID for both key and value to avoid duplications)
394
-            $DTT_ID                        = $datetime->ID();
395
-            $saved_datetime_ids[ $DTT_ID ] = $DTT_ID;
396
-            $saved_datetime_objs[ $row ]   = $datetime;
397
-            // @todo if ANY of these updates fail then we want the appropriate global error message.
398
-        }
399
-        $event->save();
400
-        // now we need to REMOVE any datetimes that got deleted.
401
-        // Keep in mind that this process will only kick in for datetimes that don't have any DTT_sold on them.
402
-        // So its safe to permanently delete at this point.
403
-        $old_datetimes = explode(',', $data['datetime_IDs']);
404
-        $old_datetimes = $old_datetimes[0] === '' ? [] : $old_datetimes;
405
-        if (is_array($old_datetimes)) {
406
-            $datetimes_to_delete = array_diff($old_datetimes, $saved_datetime_ids);
407
-            foreach ($datetimes_to_delete as $id) {
408
-                $id = absint($id);
409
-                if (empty($id)) {
410
-                    continue;
411
-                }
412
-                $dtt_to_remove = $datetime_model->get_one_by_ID($id);
413
-                // remove tkt relationships.
414
-                $related_tickets = $dtt_to_remove->get_many_related('Ticket');
415
-                foreach ($related_tickets as $ticket) {
416
-                    $dtt_to_remove->_remove_relation_to($ticket, 'Ticket');
417
-                }
418
-                $event->_remove_relation_to($id, 'Datetime');
419
-                $dtt_to_remove->refresh_cache_of_related_objects();
420
-            }
421
-        }
422
-        return $saved_datetime_objs;
423
-    }
424
-
425
-
426
-    /**
427
-     * update tickets
428
-     *
429
-     * @param EE_Event      $event           Event object being updated
430
-     * @param EE_Datetime[] $saved_datetimes an array of datetime ids being updated
431
-     * @param array         $data            incoming request data
432
-     * @return EE_Ticket[]
433
-     * @throws Exception
434
-     * @throws ReflectionException
435
-     * @throws InvalidInterfaceException
436
-     * @throws InvalidDataTypeException
437
-     * @throws InvalidArgumentException
438
-     * @throws EE_Error
439
-     */
440
-    protected function _update_tickets(EE_Event $event, array $saved_datetimes, array $data): array
441
-    {
442
-        $new_ticket = null;
443
-        // stripslashes because WP filtered the $_POST ($data) array to add slashes
444
-        $data          = stripslashes_deep($data);
445
-        $timezone      = $data['timezone_string'] ?? null;
446
-        $ticket_model = EEM_Ticket::instance($timezone);
447
-
448
-        $saved_tickets = [];
449
-        $old_tickets   = isset($data['ticket_IDs']) ? explode(',', $data['ticket_IDs']) : [];
450
-        if (empty($data['edit_tickets']) || ! is_array($data['edit_tickets'])) {
451
-            throw new InvalidArgumentException(
452
-                esc_html__(
453
-                    'The "edit_tickets" array is invalid therefore the event can not be updated.',
454
-                    'event_espresso'
455
-                )
456
-            );
457
-        }
458
-        foreach ($data['edit_tickets'] as $row => $ticket_data) {
459
-            $update_prices = $create_new_TKT = false;
460
-            // figure out what datetimes were added to the ticket
461
-            // and what datetimes were removed from the ticket in the session.
462
-            $starting_ticket_datetime_rows = explode(',', $data['starting_ticket_datetime_rows'][ $row ]);
463
-            $ticket_datetime_rows          = explode(',', $data['ticket_datetime_rows'][ $row ]);
464
-            $datetimes_added               = array_diff($ticket_datetime_rows, $starting_ticket_datetime_rows);
465
-            $datetimes_removed             = array_diff($starting_ticket_datetime_rows, $ticket_datetime_rows);
466
-            // trim inputs to ensure any excess whitespace is removed.
467
-            $ticket_data = array_map(
468
-                function ($ticket_data) {
469
-                    return is_array($ticket_data) ? $ticket_data : trim($ticket_data);
470
-                },
471
-                $ticket_data
472
-            );
473
-            // note we are doing conversions to floats here instead of allowing EE_Money_Field to handle
474
-            // because we're doing calculations prior to using the models.
475
-            // note incoming ['TKT_price'] value is already in standard notation (via js).
476
-            $ticket_price = isset($ticket_data['TKT_price'])
477
-                ? round((float) $ticket_data['TKT_price'], 3)
478
-                : 0;
479
-            // note incoming base price needs converted from localized value.
480
-            $base_price = isset($ticket_data['TKT_base_price'])
481
-                ? EEH_Money::convert_to_float_from_localized_money($ticket_data['TKT_base_price'])
482
-                : 0;
483
-            // if ticket price == 0 and $base_price != 0 then ticket price == base_price
484
-            $ticket_price  = $ticket_price === 0 && $base_price !== 0
485
-                ? $base_price
486
-                : $ticket_price;
487
-            $base_price_id = $ticket_data['TKT_base_price_ID'] ?? 0;
488
-            $price_rows    = is_array($data['edit_prices']) && isset($data['edit_prices'][ $row ])
489
-                ? $data['edit_prices'][ $row ]
490
-                : [];
491
-            $now           = null;
492
-            if (empty($ticket_data['TKT_start_date'])) {
493
-                // lets' use now in the set timezone.
494
-                $now                           = new DateTime('now', new DateTimeZone($event->get_timezone()));
495
-                $ticket_data['TKT_start_date'] = $now->format($this->_date_time_format);
496
-            }
497
-            if (empty($ticket_data['TKT_end_date'])) {
498
-                /**
499
-                 * set the TKT_end_date to the first datetime attached to the ticket.
500
-                 */
501
-                $first_datetime              = $saved_datetimes[ reset($ticket_datetime_rows) ];
502
-                $ticket_data['TKT_end_date'] = $first_datetime->start_date_and_time($this->_date_time_format);
503
-            }
504
-            $TKT_values = [
505
-                'TKT_ID'          => ! empty($ticket_data['TKT_ID']) ? $ticket_data['TKT_ID'] : null,
506
-                'TTM_ID'          => ! empty($ticket_data['TTM_ID']) ? $ticket_data['TTM_ID'] : 0,
507
-                'TKT_name'        => ! empty($ticket_data['TKT_name']) ? $ticket_data['TKT_name'] : '',
508
-                'TKT_description' => ! empty($ticket_data['TKT_description'])
509
-                                     && $ticket_data['TKT_description'] !== esc_html__(
510
-                                         'You can modify this description',
511
-                                         'event_espresso'
512
-                                     )
513
-                    ? $ticket_data['TKT_description']
514
-                    : '',
515
-                'TKT_start_date'  => $ticket_data['TKT_start_date'],
516
-                'TKT_end_date'    => $ticket_data['TKT_end_date'],
517
-                'TKT_qty'         => ! isset($ticket_data['TKT_qty']) || $ticket_data['TKT_qty'] === ''
518
-                    ? EE_INF
519
-                    : $ticket_data['TKT_qty'],
520
-                'TKT_uses'        => ! isset($ticket_data['TKT_uses']) || $ticket_data['TKT_uses'] === ''
521
-                    ? EE_INF
522
-                    : $ticket_data['TKT_uses'],
523
-                'TKT_min'         => empty($ticket_data['TKT_min']) ? 0 : $ticket_data['TKT_min'],
524
-                'TKT_max'         => empty($ticket_data['TKT_max']) ? EE_INF : $ticket_data['TKT_max'],
525
-                'TKT_row'         => $row,
526
-                'TKT_order'       => $ticket_data['TKT_order'] ?? 0,
527
-                'TKT_taxable'     => ! empty($ticket_data['TKT_taxable']) ? 1 : 0,
528
-                'TKT_required'    => ! empty($ticket_data['TKT_required']) ? 1 : 0,
529
-                'TKT_price'       => $ticket_price,
530
-            ];
531
-            // if this is a default TKT, then we need to set the TKT_ID to 0 and update accordingly,
532
-            // which means in turn that the prices will become new prices as well.
533
-            if (isset($ticket_data['TKT_is_default']) && $ticket_data['TKT_is_default']) {
534
-                $TKT_values['TKT_ID']         = 0;
535
-                $TKT_values['TKT_is_default'] = 0;
536
-                $update_prices                = true;
537
-            }
538
-            // if we have a TKT_ID then we need to get that existing TKT_obj and update it
539
-            // we actually do our saves ahead of doing any add_relations to
540
-            // because its entirely possible that this ticket wasn't removed or added to any datetime in the session
541
-            // but DID have it's items modified.
542
-            // keep in mind that if the TKT has been sold (and we have changed pricing information),
543
-            // then we won't be updating the ticket but instead a new ticket will be created and the old one archived.
544
-            if (absint($TKT_values['TKT_ID'])) {
545
-                $ticket = EE_Registry::instance()
546
-                                     ->load_model('Ticket', [$timezone])
547
-                                     ->get_one_by_ID($TKT_values['TKT_ID']);
548
-                if ($ticket instanceof EE_Ticket) {
549
-                    $ticket = $this->_update_ticket_datetimes(
550
-                        $ticket,
551
-                        $saved_datetimes,
552
-                        $datetimes_added,
553
-                        $datetimes_removed
554
-                    );
555
-                    // are there any registrations using this ticket ?
556
-                    $tickets_sold = $ticket->count_related(
557
-                        'Registration',
558
-                        [
559
-                            [
560
-                                'STS_ID' => ['NOT IN', [EEM_Registration::status_id_incomplete]],
561
-                            ],
562
-                        ]
563
-                    );
564
-                    // set ticket formats
565
-                    $ticket->set_date_format($this->_date_format_strings['date']);
566
-                    $ticket->set_time_format($this->_date_format_strings['time']);
567
-                    // let's just check the total price for the existing ticket
568
-                    // and determine if it matches the new total price.
569
-                    // if they are different then we create a new ticket (if tickets sold)
570
-                    // if they aren't different then we go ahead and modify existing ticket.
571
-                    $create_new_TKT = $tickets_sold > 0 && $ticket_price !== $ticket->price() && ! $ticket->deleted();
572
-                    // set new values
573
-                    foreach ($TKT_values as $field => $value) {
574
-                        if ($field === 'TKT_qty') {
575
-                            $ticket->set_qty($value);
576
-                        } else {
577
-                            $ticket->set($field, $value);
578
-                        }
579
-                    }
580
-                    // if $create_new_TKT is false then we can safely update the existing ticket.
581
-                    // Otherwise we have to create a new ticket.
582
-                    if ($create_new_TKT) {
583
-                        $new_ticket = $this->_duplicate_ticket(
584
-                            $ticket,
585
-                            $price_rows,
586
-                            $ticket_price,
587
-                            $base_price,
588
-                            $base_price_id
589
-                        );
590
-                    }
591
-                }
592
-            } else {
593
-                // no TKT_id so a new TKT
594
-                $ticket = EE_Ticket::new_instance(
595
-                    $TKT_values,
596
-                    $timezone,
597
-                    [$this->_date_format_strings['date'], $this->_date_format_strings['time']]
598
-                );
599
-                if ($ticket instanceof EE_Ticket) {
600
-                    // make sure ticket has an ID of setting relations won't work
601
-                    $ticket->save();
602
-                    $ticket        = $this->_update_ticket_datetimes(
603
-                        $ticket,
604
-                        $saved_datetimes,
605
-                        $datetimes_added,
606
-                        $datetimes_removed
607
-                    );
608
-                    $update_prices = true;
609
-                }
610
-            }
611
-            // make sure any current values have been saved.
612
-            // $ticket->save();
613
-            // before going any further make sure our dates are setup correctly
614
-            // so that the end date is always equal or greater than the start date.
615
-            if ($ticket->get_raw('TKT_start_date') > $ticket->get_raw('TKT_end_date')) {
616
-                $ticket->set('TKT_end_date', $ticket->get('TKT_start_date'));
617
-                $ticket = EEH_DTT_Helper::date_time_add($ticket, 'TKT_end_date', 'days');
618
-            }
619
-            // let's make sure the base price is handled
620
-            $ticket = ! $create_new_TKT
621
-                ? $this->_add_prices_to_ticket(
622
-                    [],
623
-                    $ticket,
624
-                    $update_prices,
625
-                    $base_price,
626
-                    $base_price_id
627
-                )
628
-                : $ticket;
629
-            // add/update price_modifiers
630
-            $ticket = ! $create_new_TKT
631
-                ? $this->_add_prices_to_ticket($price_rows, $ticket, $update_prices)
632
-                : $ticket;
633
-            // need to make sue that the TKT_price is accurate after saving the prices.
634
-            $ticket->ensure_TKT_Price_correct();
635
-            // handle CREATING a default ticket from the incoming ticket but ONLY if this isn't an autosave.
636
-            if (! defined('DOING_AUTOSAVE') && ! empty($ticket_data['TKT_is_default_selector'])) {
637
-                $new_default = clone $ticket;
638
-                $new_default->set('TKT_ID', 0);
639
-                $new_default->set('TKT_is_default', 1);
640
-                $new_default->set('TKT_row', 1);
641
-                $new_default->set('TKT_price', $ticket_price);
642
-                // remove any datetime relations cause we DON'T want datetime relations attached
643
-                // (note this is just removing the cached relations in the object)
644
-                $new_default->_remove_relations('Datetime');
645
-                // @todo we need to add the current attached prices as new prices to the new default ticket.
646
-                $new_default = $this->_add_prices_to_ticket(
647
-                    $price_rows,
648
-                    $new_default,
649
-                    true
650
-                );
651
-                // don't forget the base price!
652
-                $new_default = $this->_add_prices_to_ticket(
653
-                    [],
654
-                    $new_default,
655
-                    true,
656
-                    $base_price,
657
-                    $base_price_id
658
-                );
659
-                $new_default->save();
660
-                do_action(
661
-                    'AHEE__espresso_events_Pricing_Hooks___update_tkts_new_default_ticket',
662
-                    $new_default,
663
-                    $row,
664
-                    $ticket,
665
-                    $data
666
-                );
667
-            }
668
-            // DO ALL datetime relationships for both current tickets and any archived tickets
669
-            // for the given datetime that are related to the current ticket.
670
-            // TODO... not sure exactly how we're going to do this considering we don't know
671
-            // what current ticket the archived tickets are related to
672
-            // (and TKT_parent is used for autosaves so that's not a field we can reliably use).
673
-            // let's assign any tickets that have been setup to the saved_tickets tracker
674
-            // save existing TKT
675
-            $ticket->save();
676
-            if ($create_new_TKT && $new_ticket instanceof EE_Ticket) {
677
-                // save new TKT
678
-                $new_ticket->save();
679
-                // add new ticket to array
680
-                $saved_tickets[ $new_ticket->ID() ] = $new_ticket;
681
-                do_action(
682
-                    'AHEE__espresso_events_Pricing_Hooks___update_tkts_new_ticket',
683
-                    $new_ticket,
684
-                    $row,
685
-                    $ticket_data,
686
-                    $data
687
-                );
688
-            } else {
689
-                // add ticket to saved tickets
690
-                $saved_tickets[ $ticket->ID() ] = $ticket;
691
-                do_action(
692
-                    'AHEE__espresso_events_Pricing_Hooks___update_tkts_update_ticket',
693
-                    $ticket,
694
-                    $row,
695
-                    $ticket_data,
696
-                    $data
697
-                );
698
-            }
699
-        }
700
-        // now we need to handle tickets actually "deleted permanently".
701
-        // There are cases where we'd want this to happen
702
-        // (i.e. autosaves are happening and then in between autosaves the user trashes a ticket).
703
-        // Or a draft event was saved and in the process of editing a ticket is trashed.
704
-        // No sense in keeping all the related data in the db!
705
-        $old_tickets     = isset($old_tickets[0]) && $old_tickets[0] === '' ? [] : $old_tickets;
706
-        $tickets_removed = array_diff($old_tickets, array_keys($saved_tickets));
707
-        foreach ($tickets_removed as $id) {
708
-            $id = absint($id);
709
-            // get the ticket for this id
710
-            $ticket_to_remove = $ticket_model->get_one_by_ID($id);
711
-            // if this tkt is a default tkt we leave it alone cause it won't be attached to the datetime
712
-            if ($ticket_to_remove->get('TKT_is_default')) {
713
-                continue;
714
-            }
715
-            // if this ticket has any registrations attached so then we just ARCHIVE
716
-            // because we don't actually permanently delete these tickets.
717
-            if ($ticket_to_remove->count_related('Registration') > 0) {
718
-                $ticket_to_remove->delete();
719
-                continue;
720
-            }
721
-            // need to get all the related datetimes on this ticket and remove from every single one of them
722
-            // (remember this process can ONLY kick off if there are NO tickets_sold)
723
-            $datetimes = $ticket_to_remove->get_many_related('Datetime');
724
-            foreach ($datetimes as $datetime) {
725
-                $ticket_to_remove->_remove_relation_to($datetime, 'Datetime');
726
-            }
727
-            // need to do the same for prices (except these prices can also be deleted because again,
728
-            // tickets can only be trashed if they don't have any TKTs sold (otherwise they are just archived))
729
-            $ticket_to_remove->delete_related('Price');
730
-            do_action('AHEE__espresso_events_Pricing_Hooks___update_tkts_delete_ticket', $ticket_to_remove);
731
-            // finally let's delete this ticket
732
-            // (which should not be blocked at this point b/c we've removed all our relationships)
733
-            $ticket_to_remove->delete_or_restore();
734
-        }
735
-        return $saved_tickets;
736
-    }
737
-
738
-
739
-    /**
740
-     * @access  protected
741
-     * @param EE_Ticket     $ticket
742
-     * @param EE_Datetime[] $saved_datetimes
743
-     * @param int[]         $added_datetimes
744
-     * @param int[]         $removed_datetimes
745
-     * @return EE_Ticket
746
-     * @throws EE_Error
747
-     * @throws ReflectionException
748
-     */
749
-    protected function _update_ticket_datetimes(
750
-        EE_Ticket $ticket,
751
-        array $saved_datetimes = [],
752
-        array $added_datetimes = [],
753
-        array $removed_datetimes = []
754
-    ): EE_Ticket {
755
-        // to start we have to add the ticket to all the datetimes its supposed to be with,
756
-        // and removing the ticket from datetimes it got removed from.
757
-        // first let's add datetimes
758
-        if (! empty($added_datetimes) && is_array($added_datetimes)) {
759
-            foreach ($added_datetimes as $row_id) {
760
-                if (isset($saved_datetimes[ $row_id ]) && $saved_datetimes[ $row_id ] instanceof EE_Datetime) {
761
-                    $ticket->_add_relation_to($saved_datetimes[ $row_id ], 'Datetime');
762
-                    // Is this an existing ticket (has an ID) and does it have any sold?
763
-                    // If so, then we need to add that to the DTT sold because this DTT is getting added.
764
-                    if ($ticket->ID() && $ticket->sold() > 0) {
765
-                        $saved_datetimes[ $row_id ]->increaseSold($ticket->sold(), false);
766
-                    }
767
-                }
768
-            }
769
-        }
770
-        // then remove datetimes
771
-        if (! empty($removed_datetimes) && is_array($removed_datetimes)) {
772
-            foreach ($removed_datetimes as $row_id) {
773
-                // its entirely possible that a datetime got deleted (instead of just removed from relationship.
774
-                // So make sure we skip over this if the datetime isn't in the $saved_datetimes array)
775
-                if (isset($saved_datetimes[ $row_id ]) && $saved_datetimes[ $row_id ] instanceof EE_Datetime) {
776
-                    $ticket->_remove_relation_to($saved_datetimes[ $row_id ], 'Datetime');
777
-                }
778
-            }
779
-        }
780
-        // cap ticket qty by datetime reg limits
781
-        $ticket->set_qty(min($ticket->qty(), $ticket->qty('reg_limit')));
782
-        return $ticket;
783
-    }
784
-
785
-
786
-    /**
787
-     * @access  protected
788
-     * @param EE_Ticket $ticket
789
-     * @param array     $price_rows
790
-     * @param int|float $ticket_price
791
-     * @param int|float $base_price
792
-     * @param int       $base_price_id
793
-     * @return EE_Ticket
794
-     * @throws ReflectionException
795
-     * @throws InvalidArgumentException
796
-     * @throws InvalidInterfaceException
797
-     * @throws InvalidDataTypeException
798
-     * @throws EE_Error
799
-     */
800
-    protected function _duplicate_ticket(
801
-        EE_Ticket $ticket,
802
-        array $price_rows = [],
803
-        $ticket_price = 0,
804
-        $base_price = 0,
805
-        int $base_price_id = 0
806
-    ): EE_Ticket {
807
-        // create new ticket that's a copy of the existing
808
-        // except a new id of course (and not archived)
809
-        // AND has the new TKT_price associated with it.
810
-        $new_ticket = clone $ticket;
811
-        $new_ticket->set('TKT_ID', 0);
812
-        $new_ticket->set_deleted(0);
813
-        $new_ticket->set_price($ticket_price);
814
-        $new_ticket->set_sold(0);
815
-        // let's get a new ID for this ticket
816
-        $new_ticket->save();
817
-        // we also need to make sure this new ticket gets the same datetime attachments as the archived ticket
818
-        $datetimes_on_existing = $ticket->datetimes();
819
-        $new_ticket            = $this->_update_ticket_datetimes(
820
-            $new_ticket,
821
-            $datetimes_on_existing,
822
-            array_keys($datetimes_on_existing)
823
-        );
824
-        // $ticket will get archived later b/c we are NOT adding it to the saved_tickets array.
825
-        // if existing $ticket has sold amount, then we need to adjust the qty for the new TKT to = the remaining
826
-        // available.
827
-        if ($ticket->sold() > 0) {
828
-            $new_qty = $ticket->qty() - $ticket->sold();
829
-            $new_ticket->set_qty($new_qty);
830
-        }
831
-        // now we update the prices just for this ticket
832
-        $new_ticket = $this->_add_prices_to_ticket($price_rows, $new_ticket, true);
833
-        // and we update the base price
834
-        return $this->_add_prices_to_ticket(
835
-            [],
836
-            $new_ticket,
837
-            true,
838
-            $base_price,
839
-            $base_price_id
840
-        );
841
-    }
842
-
843
-
844
-    /**
845
-     * This attaches a list of given prices to a ticket.
846
-     * Note we dont' have to worry about ever removing relationships (or archiving prices) because if there is a change
847
-     * in price information on a ticket, a new ticket is created anyways so the archived ticket will retain the old
848
-     * price info and prices are automatically "archived" via the ticket.
849
-     *
850
-     * @access  private
851
-     * @param array     $prices        Array of prices from the form.
852
-     * @param EE_Ticket $ticket        EE_Ticket object that prices are being attached to.
853
-     * @param bool      $new_prices    Whether attach existing incoming prices or create new ones.
854
-     * @param int|bool  $base_price    if FALSE then NOT doing a base price add.
855
-     * @param int|bool  $base_price_id if present then this is the base_price_id being updated.
856
-     * @return EE_Ticket
857
-     * @throws ReflectionException
858
-     * @throws InvalidArgumentException
859
-     * @throws InvalidInterfaceException
860
-     * @throws InvalidDataTypeException
861
-     * @throws EE_Error
862
-     */
863
-    protected function _add_prices_to_ticket(
864
-        array $prices,
865
-        EE_Ticket $ticket,
866
-        bool $new_prices = false,
867
-        $base_price = false,
868
-        $base_price_id = false
869
-    ): EE_Ticket {
870
-        $price_model = EEM_Price::instance();
871
-        // let's just get any current prices that may exist on the given ticket
872
-        // so we can remove any prices that got trashed in this session.
873
-        $current_prices_on_ticket = $base_price !== false
874
-            ? $ticket->base_price(true)
875
-            : $ticket->price_modifiers();
876
-        $updated_prices           = [];
877
-        // if $base_price ! FALSE then updating a base price.
878
-        if ($base_price !== false) {
879
-            $prices[1] = [
880
-                'PRC_ID'     => $new_prices || $base_price_id === 1 ? null : $base_price_id,
881
-                'PRT_ID'     => 1,
882
-                'PRC_amount' => $base_price,
883
-                'PRC_name'   => $ticket->get('TKT_name'),
884
-                'PRC_desc'   => $ticket->get('TKT_description'),
885
-            ];
886
-        }
887
-        // possibly need to save ticket
888
-        if (! $ticket->ID()) {
889
-            $ticket->save();
890
-        }
891
-        foreach ($prices as $row => $prc) {
892
-            $prt_id = ! empty($prc['PRT_ID']) ? $prc['PRT_ID'] : null;
893
-            if (empty($prt_id)) {
894
-                continue;
895
-            } //prices MUST have a price type id.
896
-            $PRC_values = [
897
-                'PRC_ID'         => ! empty($prc['PRC_ID']) ? $prc['PRC_ID'] : null,
898
-                'PRT_ID'         => $prt_id,
899
-                'PRC_amount'     => ! empty($prc['PRC_amount']) ? $prc['PRC_amount'] : 0,
900
-                'PRC_name'       => ! empty($prc['PRC_name']) ? $prc['PRC_name'] : '',
901
-                'PRC_desc'       => ! empty($prc['PRC_desc']) ? $prc['PRC_desc'] : '',
902
-                'PRC_is_default' => false,
903
-                // make sure we set PRC_is_default to false for all ticket saves from event_editor
904
-                'PRC_order'      => $row,
905
-            ];
906
-            if ($new_prices || empty($PRC_values['PRC_ID'])) {
907
-                $PRC_values['PRC_ID'] = 0;
908
-                $price                = EE_Registry::instance()->load_class(
909
-                    'Price',
910
-                    [$PRC_values],
911
-                    false,
912
-                    false
913
-                );
914
-            } else {
915
-                $price = $price_model->get_one_by_ID($prc['PRC_ID']);
916
-                // update this price with new values
917
-                foreach ($PRC_values as $field => $value) {
918
-                    $price->set($field, $value);
919
-                }
920
-            }
921
-            $price->save();
922
-            $updated_prices[ $price->ID() ] = $price;
923
-            $ticket->_add_relation_to($price, 'Price');
924
-        }
925
-        // now let's remove any prices that got removed from the ticket
926
-        if (! empty($current_prices_on_ticket)) {
927
-            $current          = array_keys($current_prices_on_ticket);
928
-            $updated          = array_keys($updated_prices);
929
-            $prices_to_remove = array_diff($current, $updated);
930
-            if (! empty($prices_to_remove)) {
931
-                foreach ($prices_to_remove as $prc_id) {
932
-                    $p = $current_prices_on_ticket[ $prc_id ];
933
-                    $ticket->_remove_relation_to($p, 'Price');
934
-                    // delete permanently the price
935
-                    $p->delete_or_restore();
936
-                }
937
-            }
938
-        }
939
-        return $ticket;
940
-    }
941
-
942
-
943
-    /**
944
-     * @throws ReflectionException
945
-     * @throws InvalidArgumentException
946
-     * @throws InvalidInterfaceException
947
-     * @throws InvalidDataTypeException
948
-     * @throws DomainException
949
-     * @throws EE_Error
950
-     */
951
-    public function pricing_metabox()
952
-    {
953
-        $event                 = $this->_adminpage_obj->get_cpt_model_obj();
954
-        $timezone = $event instanceof EE_Event ? $event->timezone_string() : null;
955
-        $price_model = EEM_Price::instance($timezone);
956
-        $ticket_model = EEM_Ticket::instance($timezone);
957
-        $datetime_model = EEM_Datetime::instance($timezone);
958
-
959
-        // set is_creating_event property.
960
-        $EVT_ID                   = $event->ID();
961
-        $this->_is_creating_event = empty($this->_req_data['post']);
962
-        $existing_datetime_ids = $existing_ticket_ids = $datetime_tickets = $ticket_datetimes = [];
963
-
964
-        // default main template args
965
-        $main_template_args = [
966
-            'event_datetime_help_link' => EEH_Template::get_help_tab_link(
967
-                'event_editor_event_datetimes_help_tab',
968
-                $this->_adminpage_obj->page_slug,
969
-                $this->_adminpage_obj->get_req_action()
970
-            ),
971
-
972
-            // todo need to add a filter to the template for the help text
973
-            // in the Events_Admin_Page core file so we can add further help
974
-            'add_new_dtt_help_link'    => EEH_Template::get_help_tab_link(
975
-                'add_new_dtt_info',
976
-                $this->_adminpage_obj->page_slug,
977
-                $this->_adminpage_obj->get_req_action()
978
-            ),
979
-            // todo need to add this help info id to the Events_Admin_Page core file so we can access it here.
980
-            'datetime_rows'            => '',
981
-            'show_tickets_container'   => '',
982
-            'ticket_rows'              => '',
983
-            'ee_collapsible_status'    => ' ee-collapsible-open'
984
-            // $this->_adminpage_obj->get_cpt_model_obj()->ID() > 0 ? ' ee-collapsible-closed' : ' ee-collapsible-open'
985
-        ];
986
-        do_action('AHEE_log', __FILE__, __FUNCTION__, '');
987
-
988
-        /**
989
-         * 1. Start with retrieving Datetimes
990
-         * 2. For each datetime get related tickets
991
-         * 3. For each ticket get related prices
992
-         */
993
-        $datetimes                            = $datetime_model->get_all_event_dates($EVT_ID);
994
-        $main_template_args['total_dtt_rows'] = count($datetimes);
995
-
996
-        /**
997
-         * @see https://events.codebasehq.com/projects/event-espresso/tickets/9486
998
-         * for why we are counting $datetime_row and then setting that on the Datetime object
999
-         */
1000
-        $datetime_row = 1;
1001
-        foreach ($datetimes as $datetime) {
1002
-            $DTT_ID = $datetime->get('DTT_ID');
1003
-            $datetime->set('DTT_order', $datetime_row);
1004
-            $existing_datetime_ids[] = $DTT_ID;
1005
-            // tickets attached
1006
-            $related_tickets = $datetime->ID() > 0
1007
-                ? $datetime->get_many_related(
1008
-                    'Ticket',
1009
-                    [
1010
-                        [
1011
-                            'OR' => ['TKT_deleted' => 1, 'TKT_deleted*' => 0],
1012
-                        ],
1013
-                        'default_where_conditions' => 'none',
1014
-                        'order_by'                 => ['TKT_order' => 'ASC'],
1015
-                    ]
1016
-                )
1017
-                : [];
1018
-            // if there are no related tickets this is likely a new event OR auto-draft
1019
-            // event so we need to generate the default tickets because datetimes
1020
-            // ALWAYS have at least one related ticket!!.  EXCEPT, we dont' do this if there is already more than one
1021
-            // datetime on the event.
1022
-            if (empty($related_tickets) && count($datetimes) < 2) {
1023
-                $related_tickets = $ticket_model->get_all_default_tickets();
1024
-                // this should be ordered by TKT_ID, so let's grab the first default ticket
1025
-                // (which will be the main default) and ensure it has any default prices added to it (but do NOT save).
1026
-                $default_prices      = $price_model->get_all_default_prices();
1027
-                $main_default_ticket = reset($related_tickets);
1028
-                if ($main_default_ticket instanceof EE_Ticket) {
1029
-                    foreach ($default_prices as $default_price) {
1030
-                        if ($default_price instanceof EE_Price && $default_price->is_base_price()) {
1031
-                            continue;
1032
-                        }
1033
-                        $main_default_ticket->cache('Price', $default_price);
1034
-                    }
1035
-                }
1036
-            }
1037
-            // we can't actually setup rows in this loop yet cause we don't know all
1038
-            // the unique tickets for this event yet (tickets are linked through all datetimes).
1039
-            // So we're going to temporarily cache some of that information.
1040
-            // loop through and setup the ticket rows and make sure the order is set.
1041
-            foreach ($related_tickets as $ticket) {
1042
-                $TKT_ID     = $ticket->get('TKT_ID');
1043
-                $ticket_row = $ticket->get('TKT_row');
1044
-                // we only want unique tickets in our final display!!
1045
-                if (! in_array($TKT_ID, $existing_ticket_ids, true)) {
1046
-                    $existing_ticket_ids[] = $TKT_ID;
1047
-                    $all_tickets[]         = $ticket;
1048
-                }
1049
-                // temporary cache of this ticket info for this datetime for later processing of datetime rows.
1050
-                $datetime_tickets[ $DTT_ID ][] = $ticket_row;
1051
-                // temporary cache of this datetime info for this ticket for later processing of ticket rows.
1052
-                if (
1053
-                    ! isset($ticket_datetimes[ $TKT_ID ])
1054
-                    || ! in_array($datetime_row, $ticket_datetimes[ $TKT_ID ], true)
1055
-                ) {
1056
-                    $ticket_datetimes[ $TKT_ID ][] = $datetime_row;
1057
-                }
1058
-            }
1059
-            $datetime_row++;
1060
-        }
1061
-        $main_template_args['total_ticket_rows']     = count($existing_ticket_ids);
1062
-        $main_template_args['existing_ticket_ids']   = implode(',', $existing_ticket_ids);
1063
-        $main_template_args['existing_datetime_ids'] = implode(',', $existing_datetime_ids);
1064
-        // sort $all_tickets by order
1065
-        usort(
1066
-            $all_tickets,
1067
-            function (EE_Ticket $a, EE_Ticket $b) {
1068
-                $a_order = (int) $a->get('TKT_order');
1069
-                $b_order = (int) $b->get('TKT_order');
1070
-                if ($a_order === $b_order) {
1071
-                    return 0;
1072
-                }
1073
-                return ($a_order < $b_order) ? -1 : 1;
1074
-            }
1075
-        );
1076
-        // k NOW we have all the data we need for setting up the datetime rows
1077
-        // and ticket rows so we start our datetime loop again.
1078
-        $datetime_row = 1;
1079
-        foreach ($datetimes as $datetime) {
1080
-            $main_template_args['datetime_rows'] .= $this->_get_datetime_row(
1081
-                $datetime_row,
1082
-                $datetime,
1083
-                $datetime_tickets,
1084
-                $all_tickets,
1085
-                false,
1086
-                $datetimes
1087
-            );
1088
-            $datetime_row++;
1089
-        }
1090
-        // then loop through all tickets for the ticket rows.
1091
-        $ticket_row = 1;
1092
-        foreach ($all_tickets as $ticket) {
1093
-            $main_template_args['ticket_rows'] .= $this->_get_ticket_row(
1094
-                $ticket_row,
1095
-                $ticket,
1096
-                $ticket_datetimes,
1097
-                $datetimes,
1098
-                false,
1099
-                $all_tickets
1100
-            );
1101
-            $ticket_row++;
1102
-        }
1103
-        $main_template_args['ticket_js_structure'] = $this->_get_ticket_js_structure($datetimes, $all_tickets);
1104
-
1105
-        $status_change_notice = LoaderFactory::getLoader()->getShared(
1106
-            'EventEspresso\core\domain\services\admin\notices\status_change\StatusChangeNotice'
1107
-        );
1108
-
1109
-        $main_template_args['status_change_notice'] = $status_change_notice->display(
1110
-            '__event-editor',
1111
-            'espresso-events'
1112
-        );
1113
-
1114
-        EEH_Template::display_template(
1115
-            PRICING_TEMPLATE_PATH . 'event_tickets_metabox_main.template.php',
1116
-            $main_template_args
1117
-        );
1118
-    }
1119
-
1120
-
1121
-    /**
1122
-     * @param int|string  $datetime_row
1123
-     * @param EE_Datetime $datetime
1124
-     * @param array       $datetime_tickets
1125
-     * @param array       $all_tickets
1126
-     * @param bool        $default
1127
-     * @param array       $all_datetimes
1128
-     * @return string
1129
-     * @throws DomainException
1130
-     * @throws EE_Error
1131
-     * @throws ReflectionException
1132
-     */
1133
-    protected function _get_datetime_row(
1134
-        $datetime_row,
1135
-        EE_Datetime $datetime,
1136
-        array $datetime_tickets = [],
1137
-        array $all_tickets = [],
1138
-        bool $default = false,
1139
-        array $all_datetimes = []
1140
-    ): string {
1141
-        return EEH_Template::display_template(
1142
-            PRICING_TEMPLATE_PATH . 'event_tickets_datetime_row_wrapper.template.php',
1143
-            [
1144
-                'dtt_edit_row'             => $this->_get_dtt_edit_row(
1145
-                    $datetime_row,
1146
-                    $datetime,
1147
-                    $default,
1148
-                    $all_datetimes
1149
-                ),
1150
-                'dtt_attached_tickets_row' => $this->_get_dtt_attached_tickets_row(
1151
-                    $datetime_row,
1152
-                    $datetime,
1153
-                    $datetime_tickets,
1154
-                    $all_tickets,
1155
-                    $default
1156
-                ),
1157
-                'dtt_row'                  => $default ? 'DTTNUM' : $datetime_row,
1158
-            ],
1159
-            true
1160
-        );
1161
-    }
1162
-
1163
-
1164
-    /**
1165
-     * This method is used to generate a datetime fields  edit row.
1166
-     * The same row is used to generate a row with valid DTT objects
1167
-     * and the default row that is used as the skeleton by the js.
1168
-     *
1169
-     * @param int|string       $datetime_row  The row number for the row being generated.
1170
-     * @param EE_Datetime|null $datetime
1171
-     * @param bool             $default       Whether a default row is being generated or not.
1172
-     * @param EE_Datetime[]    $all_datetimes This is the array of all datetimes used in the editor.
1173
-     * @return string
1174
-     * @throws EE_Error
1175
-     * @throws ReflectionException
1176
-     */
1177
-    protected function _get_dtt_edit_row(
1178
-        $datetime_row,
1179
-        ?EE_Datetime $datetime,
1180
-        bool $default,
1181
-        array $all_datetimes
1182
-    ): string {
1183
-        // if the incoming $datetime object is NOT an instance of EE_Datetime then force default to true.
1184
-        $default                     = ! $datetime instanceof EE_Datetime ? true : $default;
1185
-        $template_args               = [
1186
-            'dtt_row'              => $default ? 'DTTNUM' : $datetime_row,
1187
-            'event_datetimes_name' => $default ? 'DTTNAMEATTR' : 'edit_event_datetimes',
1188
-            'edit_dtt_expanded'    => '',
1189
-            'DTT_ID'               => $default ? '' : $datetime->ID(),
1190
-            'DTT_name'             => $default ? '' : $datetime->get_f('DTT_name'),
1191
-            'DTT_description'      => $default ? '' : $datetime->get_f('DTT_description'),
1192
-            'DTT_EVT_start'        => $default ? '' : $datetime->start_date($this->_date_time_format),
1193
-            'DTT_EVT_end'          => $default ? '' : $datetime->end_date($this->_date_time_format),
1194
-            'DTT_reg_limit'        => $default
1195
-                ? ''
1196
-                : $datetime->get_pretty(
1197
-                    'DTT_reg_limit',
1198
-                    'input'
1199
-                ),
1200
-            'DTT_order'            => $default ? 'DTTNUM' : $datetime_row,
1201
-            'dtt_sold'             => $default ? '0' : $datetime->get('DTT_sold'),
1202
-            'dtt_reserved'         => $default ? '0' : $datetime->reserved(),
1203
-            'clone_icon'           => ! empty($datetime) && $datetime->get('DTT_sold') > 0
1204
-                ? ''
1205
-                : 'clone-icon ee-icon ee-icon-clone clickable',
1206
-            'trash_icon'           => ! empty($datetime) && $datetime->get('DTT_sold') > 0
1207
-                ? 'dashicons dashicons-lock'
1208
-                : 'trash-icon dashicons dashicons-post-trash clickable',
1209
-            'reg_list_url'         => $default || ! $datetime->event() instanceof EE_Event
1210
-                ? ''
1211
-                : EE_Admin_Page::add_query_args_and_nonce(
1212
-                    [
1213
-                        'event_id'    => $datetime->event()->ID(),
1214
-                        'datetime_id' => $datetime->ID(),
1215
-                        'use_filters' => true,
1216
-                    ],
1217
-                    REG_ADMIN_URL
1218
-                ),
1219
-        ];
1220
-        $template_args['show_trash'] = count($all_datetimes) === 1
1221
-                                       && $template_args['trash_icon'] !== 'dashicons dashicons-lock'
1222
-            ? 'display:none'
1223
-            : '';
1224
-        // allow filtering of template args at this point.
1225
-        $template_args = apply_filters(
1226
-            'FHEE__espresso_events_Pricing_Hooks___get_dtt_edit_row__template_args',
1227
-            $template_args,
1228
-            $datetime_row,
1229
-            $datetime,
1230
-            $default,
1231
-            $all_datetimes,
1232
-            $this->_is_creating_event
1233
-        );
1234
-        return EEH_Template::display_template(
1235
-            PRICING_TEMPLATE_PATH . 'event_tickets_datetime_edit_row.template.php',
1236
-            $template_args,
1237
-            true
1238
-        );
1239
-    }
1240
-
1241
-
1242
-    /**
1243
-     * @param int|string       $datetime_row
1244
-     * @param EE_Datetime|null $datetime
1245
-     * @param array            $datetime_tickets
1246
-     * @param array            $all_tickets
1247
-     * @param bool             $default
1248
-     * @return string
1249
-     * @throws DomainException
1250
-     * @throws EE_Error
1251
-     * @throws ReflectionException
1252
-     */
1253
-    protected function _get_dtt_attached_tickets_row(
1254
-        $datetime_row,
1255
-        ?EE_Datetime $datetime,
1256
-        array $datetime_tickets = [],
1257
-        array $all_tickets = [],
1258
-        bool $default = false
1259
-    ): string {
1260
-        $template_args = [
1261
-            'dtt_row'                           => $default ? 'DTTNUM' : $datetime_row,
1262
-            'event_datetimes_name'              => $default ? 'DTTNAMEATTR' : 'edit_event_datetimes',
1263
-            'DTT_description'                   => $default ? '' : $datetime->get_f('DTT_description'),
1264
-            'datetime_tickets_list'             => $default ? '<li class="hidden"></li>' : '',
1265
-            'show_tickets_row'                  => 'display:none;',
1266
-            'add_new_datetime_ticket_help_link' => EEH_Template::get_help_tab_link(
1267
-                'add_new_ticket_via_datetime',
1268
-                $this->_adminpage_obj->page_slug,
1269
-                $this->_adminpage_obj->get_req_action()
1270
-            ),
1271
-            // todo need to add this help info id to the Events_Admin_Page core file so we can access it here.
1272
-            'DTT_ID'                            => $default ? '' : $datetime->ID(),
1273
-        ];
1274
-        // need to setup the list items (but only if this isn't a default skeleton setup)
1275
-        if (! $default) {
1276
-            $ticket_row = 1;
1277
-            foreach ($all_tickets as $ticket) {
1278
-                $template_args['datetime_tickets_list'] .= $this->_get_datetime_tickets_list_item(
1279
-                    $datetime_row,
1280
-                    $ticket_row,
1281
-                    $datetime,
1282
-                    $ticket,
1283
-                    $datetime_tickets
1284
-                );
1285
-                $ticket_row++;
1286
-            }
1287
-        }
1288
-        // filter template args at this point
1289
-        $template_args = apply_filters(
1290
-            'FHEE__espresso_events_Pricing_Hooks___get_dtt_attached_ticket_row__template_args',
1291
-            $template_args,
1292
-            $datetime_row,
1293
-            $datetime,
1294
-            $datetime_tickets,
1295
-            $all_tickets,
1296
-            $default,
1297
-            $this->_is_creating_event
1298
-        );
1299
-        return EEH_Template::display_template(
1300
-            PRICING_TEMPLATE_PATH . 'event_tickets_datetime_attached_tickets_row.template.php',
1301
-            $template_args,
1302
-            true
1303
-        );
1304
-    }
1305
-
1306
-
1307
-    /**
1308
-     * @param int|string       $datetime_row
1309
-     * @param int|string       $ticket_row
1310
-     * @param EE_Datetime|null $datetime
1311
-     * @param EE_Ticket|null   $ticket
1312
-     * @param array            $datetime_tickets
1313
-     * @param bool             $default
1314
-     * @return string
1315
-     * @throws EE_Error
1316
-     * @throws ReflectionException
1317
-     */
1318
-    protected function _get_datetime_tickets_list_item(
1319
-        $datetime_row,
1320
-        $ticket_row,
1321
-        ?EE_Datetime $datetime,
1322
-        ?EE_Ticket $ticket,
1323
-        array $datetime_tickets = [],
1324
-        bool $default = false
1325
-    ): string {
1326
-        $datetime_tickets = $datetime instanceof EE_Datetime && isset($datetime_tickets[ $datetime->ID() ])
1327
-            ? $datetime_tickets[ $datetime->ID() ]
1328
-            : [];
1329
-        $display_row      = $ticket instanceof EE_Ticket ? $ticket->get('TKT_row') : 0;
1330
-        $no_ticket        = $default && empty($ticket);
1331
-        $template_args    = [
1332
-            'dtt_row'                 => $default
1333
-                ? 'DTTNUM'
1334
-                : $datetime_row,
1335
-            'tkt_row'                 => $no_ticket
1336
-                ? 'TICKETNUM'
1337
-                : $ticket_row,
1338
-            'datetime_ticket_checked' => in_array($display_row, $datetime_tickets, true)
1339
-                ? ' checked'
1340
-                : '',
1341
-            'ticket_selected'         => in_array($display_row, $datetime_tickets, true)
1342
-                ? ' ticket-selected'
1343
-                : '',
1344
-            'TKT_name'                => $no_ticket
1345
-                ? 'TKTNAME'
1346
-                : $ticket->get('TKT_name'),
1347
-            'tkt_status_class'        => $no_ticket || $this->_is_creating_event
1348
-                ? ' tkt-status-' . EE_Ticket::onsale
1349
-                : ' tkt-status-' . $ticket->ticket_status(),
1350
-        ];
1351
-        // filter template args
1352
-        $template_args = apply_filters(
1353
-            'FHEE__espresso_events_Pricing_Hooks___get_datetime_tickets_list_item__template_args',
1354
-            $template_args,
1355
-            $datetime_row,
1356
-            $ticket_row,
1357
-            $datetime,
1358
-            $ticket,
1359
-            $datetime_tickets,
1360
-            $default,
1361
-            $this->_is_creating_event
1362
-        );
1363
-        return EEH_Template::display_template(
1364
-            PRICING_TEMPLATE_PATH . 'event_tickets_datetime_dtt_tickets_list.template.php',
1365
-            $template_args,
1366
-            true
1367
-        );
1368
-    }
1369
-
1370
-
1371
-    /**
1372
-     * This generates the ticket row for tickets.
1373
-     * This same method is used to generate both the actual rows and the js skeleton row
1374
-     * (when default === true)
1375
-     *
1376
-     * @param int|string     $ticket_row       Represents the row number being generated.
1377
-     * @param EE_Ticket|null $ticket
1378
-     * @param EE_Datetime[]  $ticket_datetimes Either an array of all datetimes on all tickets indexed by each ticket
1379
-     *                                         or empty for default
1380
-     * @param EE_Datetime[]  $all_datetimes    All Datetimes on the event or empty for default.
1381
-     * @param bool           $default          Whether default row being generated or not.
1382
-     * @param EE_Ticket[]    $all_tickets      This is an array of all tickets attached to the event
1383
-     *                                         (or empty in the case of defaults)
1384
-     * @return string
1385
-     * @throws InvalidArgumentException
1386
-     * @throws InvalidInterfaceException
1387
-     * @throws InvalidDataTypeException
1388
-     * @throws DomainException
1389
-     * @throws EE_Error
1390
-     * @throws ReflectionException
1391
-     */
1392
-    protected function _get_ticket_row(
1393
-        $ticket_row,
1394
-        ?EE_Ticket $ticket,
1395
-        array $ticket_datetimes,
1396
-        array $all_datetimes,
1397
-        bool $default = false,
1398
-        array $all_tickets = []
1399
-    ): string {
1400
-        // if $ticket is not an instance of EE_Ticket then force default to true.
1401
-        $default = ! $ticket instanceof EE_Ticket ? true : $default;
1402
-        $prices  = ! empty($ticket) && ! $default
1403
-            ? $ticket->get_many_related(
1404
-                'Price',
1405
-                ['default_where_conditions' => 'none', 'order_by' => ['PRC_order' => 'ASC']]
1406
-            )
1407
-            : [];
1408
-        // if there is only one price (which would be the base price)
1409
-        // or NO prices and this ticket is a default ticket,
1410
-        // let's just make sure there are no cached default prices on the object.
1411
-        // This is done by not including any query_params.
1412
-        if ($ticket instanceof EE_Ticket && $ticket->is_default() && (count($prices) === 1 || empty($prices))) {
1413
-            $prices = $ticket->prices();
1414
-        }
1415
-        // check if we're dealing with a default ticket in which case
1416
-        // we don't want any starting_ticket_datetime_row values set
1417
-        // (otherwise there won't be any new relationships created for tickets based off of the default ticket).
1418
-        // This will future proof in case there is ever any behaviour change between what the primary_key defaults to.
1419
-        $default_datetime = $default || ($ticket instanceof EE_Ticket && $ticket->is_default());
1420
-        $ticket_datetimes = $ticket instanceof EE_Ticket && isset($ticket_datetimes[ $ticket->ID() ])
1421
-            ? $ticket_datetimes[ $ticket->ID() ]
1422
-            : [];
1423
-        $ticket_subtotal  = $default ? 0 : $ticket->get_ticket_subtotal();
1424
-        $base_price       = $default ? null : $ticket->base_price();
1425
-        $count_price_mods = EEM_Price::instance()->get_all_default_prices(true);
1426
-        // breaking out complicated condition for ticket_status
1427
-        if ($default) {
1428
-            $ticket_status_class = ' tkt-status-' . EE_Ticket::onsale;
1429
-        } else {
1430
-            $ticket_status_class = $ticket->is_default()
1431
-                ? ' tkt-status-' . EE_Ticket::onsale
1432
-                : ' tkt-status-' . $ticket->ticket_status();
1433
-        }
1434
-        // breaking out complicated condition for TKT_taxable
1435
-        if ($default) {
1436
-            $TKT_taxable = '';
1437
-        } else {
1438
-            $TKT_taxable = $ticket->taxable()
1439
-                ? 'checked'
1440
-                : '';
1441
-        }
1442
-        if ($default) {
1443
-            $TKT_status = EEH_Template::pretty_status(EE_Ticket::onsale, false, 'sentence');
1444
-        } elseif ($ticket->is_default()) {
1445
-            $TKT_status = EEH_Template::pretty_status(EE_Ticket::onsale, false, 'sentence');
1446
-        } else {
1447
-            $TKT_status = $ticket->ticket_status(true);
1448
-        }
1449
-        if ($default) {
1450
-            $TKT_min = '';
1451
-        } else {
1452
-            $TKT_min = $ticket->min();
1453
-            if ($TKT_min === -1 || $TKT_min === 0) {
1454
-                $TKT_min = '';
1455
-            }
1456
-        }
1457
-        $template_args                 = [
1458
-            'tkt_row'                       => $default ? 'TICKETNUM' : $ticket_row,
1459
-            'TKT_order'                     => $default ? 'TICKETNUM' : $ticket_row,
1460
-            // on initial page load this will always be the correct order.
1461
-            'tkt_status_class'              => $ticket_status_class,
1462
-            'display_edit_tkt_row'          => 'display:none;',
1463
-            'edit_tkt_expanded'             => '',
1464
-            'edit_tickets_name'             => $default ? 'TICKETNAMEATTR' : 'edit_tickets',
1465
-            'TKT_name'                      => $default ? '' : $ticket->get_f('TKT_name'),
1466
-            'TKT_start_date'                => $default
1467
-                ? ''
1468
-                : $ticket->get_date('TKT_start_date', $this->_date_time_format),
1469
-            'TKT_end_date'                  => $default
1470
-                ? ''
1471
-                : $ticket->get_date('TKT_end_date', $this->_date_time_format),
1472
-            'TKT_status'                    => $TKT_status,
1473
-            'TKT_price'                     => $default
1474
-                ? ''
1475
-                : EEH_Template::format_currency(
1476
-                    $ticket->get_ticket_total_with_taxes(),
1477
-                    false,
1478
-                    false
1479
-                ),
1480
-            'TKT_price_code'                => EE_Registry::instance()->CFG->currency->code,
1481
-            'TKT_price_amount'              => $default ? 0 : $ticket_subtotal,
1482
-            'TKT_qty'                       => $default
1483
-                ? ''
1484
-                : $ticket->get_pretty('TKT_qty', 'symbol'),
1485
-            'TKT_qty_for_input'             => $default
1486
-                ? ''
1487
-                : $ticket->get_pretty('TKT_qty', 'input'),
1488
-            'TKT_uses'                      => $default
1489
-                ? ''
1490
-                : $ticket->get_pretty('TKT_uses', 'input'),
1491
-            'TKT_min'                       => $TKT_min,
1492
-            'TKT_max'                       => $default
1493
-                ? ''
1494
-                : $ticket->get_pretty('TKT_max', 'input'),
1495
-            'TKT_sold'                      => $default ? 0 : $ticket->tickets_sold(),
1496
-            'TKT_reserved'                  => $default ? 0 : $ticket->reserved(),
1497
-            'TKT_registrations'             => $default
1498
-                ? 0
1499
-                : $ticket->count_registrations(
1500
-                    [
1501
-                        [
1502
-                            'STS_ID' => [
1503
-                                '!=',
1504
-                                EEM_Registration::status_id_incomplete,
1505
-                            ],
1506
-                        ],
1507
-                    ]
1508
-                ),
1509
-            'TKT_ID'                        => $default ? 0 : $ticket->ID(),
1510
-            'TKT_description'               => $default ? '' : $ticket->get_raw('TKT_description'),
1511
-            'TKT_is_default'                => $default ? 0 : $ticket->is_default(),
1512
-            'TKT_required'                  => $default ? 0 : $ticket->required(),
1513
-            'TKT_is_default_selector'       => '',
1514
-            'ticket_price_rows'             => '',
1515
-            'TKT_base_price'                => $default || ! $base_price instanceof EE_Price
1516
-                ? ''
1517
-                : $base_price->get_pretty('PRC_amount', 'localized_float'),
1518
-            'TKT_base_price_ID'             => $default || ! $base_price instanceof EE_Price ? 0 : $base_price->ID(),
1519
-            'show_price_modifier'           => count($prices) > 1 || ($default && $count_price_mods > 0)
1520
-                ? ''
1521
-                : 'display:none;',
1522
-            'show_price_mod_button'         => count($prices) > 1
1523
-                                               || ($default && $count_price_mods > 0)
1524
-                                               || (! $default && $ticket->deleted())
1525
-                ? 'display:none;'
1526
-                : '',
1527
-            'total_price_rows'              => count($prices) > 1 ? count($prices) : 1,
1528
-            'ticket_datetimes_list'         => $default ? '<li class="hidden"></li>' : '',
1529
-            'starting_ticket_datetime_rows' => $default || $default_datetime ? '' : implode(',', $ticket_datetimes),
1530
-            'ticket_datetime_rows'          => $default ? '' : implode(',', $ticket_datetimes),
1531
-            'existing_ticket_price_ids'     => $default ? '' : implode(',', array_keys($prices)),
1532
-            'ticket_template_id'            => $default ? 0 : $ticket->get('TTM_ID'),
1533
-            'TKT_taxable'                   => $TKT_taxable,
1534
-            'display_subtotal'              => $ticket instanceof EE_Ticket && $ticket->taxable()
1535
-                ? ''
1536
-                : 'display:none;',
1537
-            'price_currency_symbol'         => EE_Registry::instance()->CFG->currency->sign,
1538
-            'TKT_subtotal_amount_display'   => EEH_Template::format_currency(
1539
-                $ticket_subtotal,
1540
-                false,
1541
-                false
1542
-            ),
1543
-            'TKT_subtotal_amount'           => $ticket_subtotal,
1544
-            'tax_rows'                      => $this->_get_tax_rows($ticket_row, $ticket),
1545
-            'disabled'                      => $ticket instanceof EE_Ticket && $ticket->deleted(),
1546
-            'ticket_archive_class'          => $ticket instanceof EE_Ticket && $ticket->deleted()
1547
-                ? ' ticket-archived'
1548
-                : '',
1549
-            'trash_icon'                    => $ticket instanceof EE_Ticket
1550
-                                               && $ticket->deleted()
1551
-                                               && ! $ticket->is_permanently_deleteable()
1552
-                ? 'dashicons dashicons-lock '
1553
-                : 'trash-icon dashicons dashicons-post-trash clickable',
1554
-            'clone_icon'                    => $ticket instanceof EE_Ticket && $ticket->deleted()
1555
-                ? ''
1556
-                : 'clone-icon ee-icon ee-icon-clone clickable',
1557
-        ];
1558
-        $template_args['trash_hidden'] = count($all_tickets) === 1
1559
-                                         && $template_args['trash_icon'] !== 'dashicons dashicons-lock'
1560
-            ? 'display:none'
1561
-            : '';
1562
-        // handle rows that should NOT be empty
1563
-        if (empty($template_args['TKT_start_date'])) {
1564
-            // if empty then the start date will be now.
1565
-            $template_args['TKT_start_date']   = date(
1566
-                $this->_date_time_format,
1567
-                current_time('timestamp')
1568
-            );
1569
-            $template_args['tkt_status_class'] = ' tkt-status-' . EE_Ticket::onsale;
1570
-        }
1571
-        if (empty($template_args['TKT_end_date'])) {
1572
-            // get the earliest datetime (if present);
1573
-            $earliest_datetime = $this->_adminpage_obj->get_cpt_model_obj()->ID() > 0
1574
-                ? $this->_adminpage_obj->get_cpt_model_obj()->get_first_related(
1575
-                    'Datetime',
1576
-                    ['order_by' => ['DTT_EVT_start' => 'ASC']]
1577
-                )
1578
-                : null;
1579
-            if (! empty($earliest_datetime)) {
1580
-                $template_args['TKT_end_date'] = $earliest_datetime->get_datetime(
1581
-                    'DTT_EVT_start',
1582
-                    $this->_date_time_format
1583
-                );
1584
-            } else {
1585
-                // default so let's just use what's been set for the default date-time which is 30 days from now.
1586
-                $template_args['TKT_end_date'] = date(
1587
-                    $this->_date_time_format,
1588
-                    mktime(
1589
-                        24,
1590
-                        0,
1591
-                        0,
1592
-                        date('m'),
1593
-                        date('d') + 29,
1594
-                        date('Y')
1595
-                    )
1596
-                );
1597
-            }
1598
-            $template_args['tkt_status_class'] = ' tkt-status-' . EE_Ticket::onsale;
1599
-        }
1600
-        // generate ticket_datetime items
1601
-        if (! $default) {
1602
-            $datetime_row = 1;
1603
-            foreach ($all_datetimes as $datetime) {
1604
-                $template_args['ticket_datetimes_list'] .= $this->_get_ticket_datetime_list_item(
1605
-                    $datetime_row,
1606
-                    $ticket_row,
1607
-                    $datetime,
1608
-                    $ticket,
1609
-                    $ticket_datetimes,
1610
-                    $default
1611
-                );
1612
-                $datetime_row++;
1613
-            }
1614
-        }
1615
-        $price_row = 1;
1616
-        foreach ($prices as $price) {
1617
-            if (! $price instanceof EE_Price) {
1618
-                continue;
1619
-            }
1620
-            if ($price->is_base_price()) {
1621
-                $price_row++;
1622
-                continue;
1623
-            }
1624
-
1625
-            $show_trash  = ! ((count($prices) > 1 && $price_row === 1) || count($prices) === 1);
1626
-            $show_create = ! (count($prices) > 1 && count($prices) !== $price_row);
1627
-
1628
-            $template_args['ticket_price_rows'] .= $this->_get_ticket_price_row(
1629
-                $ticket_row,
1630
-                $price_row,
1631
-                $price,
1632
-                $default,
1633
-                $ticket,
1634
-                $show_trash,
1635
-                $show_create
1636
-            );
1637
-            $price_row++;
1638
-        }
1639
-        // filter $template_args
1640
-        $template_args = apply_filters(
1641
-            'FHEE__espresso_events_Pricing_Hooks___get_ticket_row__template_args',
1642
-            $template_args,
1643
-            $ticket_row,
1644
-            $ticket,
1645
-            $ticket_datetimes,
1646
-            $all_datetimes,
1647
-            $default,
1648
-            $all_tickets,
1649
-            $this->_is_creating_event
1650
-        );
1651
-        return EEH_Template::display_template(
1652
-            PRICING_TEMPLATE_PATH . 'event_tickets_datetime_ticket_row.template.php',
1653
-            $template_args,
1654
-            true
1655
-        );
1656
-    }
1657
-
1658
-
1659
-    /**
1660
-     * @param int|string     $ticket_row
1661
-     * @param EE_Ticket|null $ticket
1662
-     * @return string
1663
-     * @throws DomainException
1664
-     * @throws EE_Error
1665
-     * @throws ReflectionException
1666
-     */
1667
-    protected function _get_tax_rows($ticket_row, ?EE_Ticket $ticket): string
1668
-    {
1669
-        $tax_rows = '';
1670
-        /** @var EE_Price[] $taxes */
1671
-        $taxes = empty($ticket) ? EE_Taxes::get_taxes_for_admin() : $ticket->get_ticket_taxes_for_admin();
1672
-        foreach ($taxes as $tax) {
1673
-            $tax_added     = $this->_get_tax_added($tax, $ticket);
1674
-            $template_args = [
1675
-                'display_tax'       => ! empty($ticket) && $ticket->get('TKT_taxable')
1676
-                    ? ''
1677
-                    : 'display:none;',
1678
-                'tax_id'            => $tax->ID(),
1679
-                'tkt_row'           => $ticket_row,
1680
-                'tax_label'         => $tax->get('PRC_name'),
1681
-                'tax_added'         => $tax_added,
1682
-                'tax_added_display' => EEH_Template::format_currency($tax_added, false, false),
1683
-                'tax_amount'        => $tax->get('PRC_amount'),
1684
-            ];
1685
-            $template_args = apply_filters(
1686
-                'FHEE__espresso_events_Pricing_Hooks___get_tax_rows__template_args',
1687
-                $template_args,
1688
-                $ticket_row,
1689
-                $ticket,
1690
-                $this->_is_creating_event
1691
-            );
1692
-            $tax_rows      .= EEH_Template::display_template(
1693
-                PRICING_TEMPLATE_PATH . 'event_tickets_datetime_ticket_tax_row.template.php',
1694
-                $template_args,
1695
-                true
1696
-            );
1697
-        }
1698
-        return $tax_rows;
1699
-    }
1700
-
1701
-
1702
-    /**
1703
-     * @param EE_Price       $tax
1704
-     * @param EE_Ticket|null $ticket
1705
-     * @return float|int
1706
-     * @throws EE_Error
1707
-     * @throws ReflectionException
1708
-     */
1709
-    protected function _get_tax_added(EE_Price $tax, ?EE_Ticket $ticket)
1710
-    {
1711
-        $subtotal = empty($ticket) ? 0 : $ticket->get_ticket_subtotal();
1712
-        return $subtotal * $tax->get('PRC_amount') / 100;
1713
-    }
1714
-
1715
-
1716
-    /**
1717
-     * @param int|string     $ticket_row
1718
-     * @param int|string     $price_row
1719
-     * @param EE_Price|null  $price
1720
-     * @param bool           $default
1721
-     * @param EE_Ticket|null $ticket
1722
-     * @param bool           $show_trash
1723
-     * @param bool           $show_create
1724
-     * @return string
1725
-     * @throws InvalidArgumentException
1726
-     * @throws InvalidInterfaceException
1727
-     * @throws InvalidDataTypeException
1728
-     * @throws DomainException
1729
-     * @throws EE_Error
1730
-     * @throws ReflectionException
1731
-     */
1732
-    protected function _get_ticket_price_row(
1733
-        $ticket_row,
1734
-        $price_row,
1735
-        ?EE_Price $price,
1736
-        bool $default,
1737
-        ?EE_Ticket $ticket,
1738
-        bool $show_trash = true,
1739
-        bool $show_create = true
1740
-    ): string {
1741
-        $send_disabled = ! empty($ticket) && $ticket->get('TKT_deleted');
1742
-        $template_args = [
1743
-            'tkt_row'               => $default && empty($ticket)
1744
-                ? 'TICKETNUM'
1745
-                : $ticket_row,
1746
-            'PRC_order'             => $default && empty($price)
1747
-                ? 'PRICENUM'
1748
-                : $price_row,
1749
-            'edit_prices_name'      => $default && empty($price)
1750
-                ? 'PRICENAMEATTR'
1751
-                : 'edit_prices',
1752
-            'price_type_selector'   => $default && empty($price)
1753
-                ? $this->_get_base_price_template($ticket_row, $price_row, $price, true)
1754
-                : $this->_get_price_type_selector(
1755
-                    $ticket_row,
1756
-                    $price_row,
1757
-                    $price,
1758
-                    $default,
1759
-                    $send_disabled
1760
-                ),
1761
-            'PRC_ID'                => $default && empty($price)
1762
-                ? 0
1763
-                : $price->ID(),
1764
-            'PRC_is_default'        => $default && empty($price)
1765
-                ? 0
1766
-                : $price->get('PRC_is_default'),
1767
-            'PRC_name'              => $default && empty($price)
1768
-                ? ''
1769
-                : $price->get('PRC_name'),
1770
-            'price_currency_symbol' => EE_Registry::instance()->CFG->currency->sign,
1771
-            'show_plus_or_minus'    => $default && empty($price)
1772
-                ? ''
1773
-                : 'display:none;',
1774
-            'show_plus'             => ($default && empty($price)) || ($price->is_discount() || $price->is_base_price())
1775
-                ? 'display:none;'
1776
-                : '',
1777
-            'show_minus'            => ($default && empty($price)) || ! $price->is_discount()
1778
-                ? 'display:none;'
1779
-                : '',
1780
-            'show_currency_symbol'  => ($default && empty($price)) || $price->is_percent()
1781
-                ? 'display:none'
1782
-                : '',
1783
-            'PRC_amount'            => $default && empty($price)
1784
-                ? 0
1785
-                : $price->get_pretty('PRC_amount', 'localized_float'),
1786
-            'show_percentage'       => ($default && empty($price)) || ! $price->is_percent()
1787
-                ? 'display:none;'
1788
-                : '',
1789
-            'show_trash_icon'       => $show_trash
1790
-                ? ''
1791
-                : ' style="display:none;"',
1792
-            'show_create_button'    => $show_create
1793
-                ? ''
1794
-                : ' style="display:none;"',
1795
-            'PRC_desc'              => $default && empty($price)
1796
-                ? ''
1797
-                : $price->get('PRC_desc'),
1798
-            'disabled'              => ! empty($ticket) && $ticket->get('TKT_deleted'),
1799
-        ];
1800
-        $template_args = apply_filters(
1801
-            'FHEE__espresso_events_Pricing_Hooks___get_ticket_price_row__template_args',
1802
-            $template_args,
1803
-            $ticket_row,
1804
-            $price_row,
1805
-            $price,
1806
-            $default,
1807
-            $ticket,
1808
-            $show_trash,
1809
-            $show_create,
1810
-            $this->_is_creating_event
1811
-        );
1812
-        return EEH_Template::display_template(
1813
-            PRICING_TEMPLATE_PATH . 'event_tickets_datetime_ticket_price_row.template.php',
1814
-            $template_args,
1815
-            true
1816
-        );
1817
-    }
1818
-
1819
-
1820
-    /**
1821
-     * @param int|string    $ticket_row
1822
-     * @param int|string    $price_row
1823
-     * @param EE_Price|null $price
1824
-     * @param bool          $default
1825
-     * @param bool          $disabled
1826
-     * @return string
1827
-     * @throws ReflectionException
1828
-     * @throws InvalidArgumentException
1829
-     * @throws InvalidInterfaceException
1830
-     * @throws InvalidDataTypeException
1831
-     * @throws DomainException
1832
-     * @throws EE_Error
1833
-     */
1834
-    protected function _get_price_type_selector(
1835
-        $ticket_row,
1836
-        $price_row,
1837
-        ?EE_Price $price,
1838
-        bool $default,
1839
-        bool $disabled = false
1840
-    ): string {
1841
-        if ($price->is_base_price()) {
1842
-            return $this->_get_base_price_template(
1843
-                $ticket_row,
1844
-                $price_row,
1845
-                $price,
1846
-                $default
1847
-            );
1848
-        }
1849
-        return $this->_get_price_modifier_template(
1850
-            $ticket_row,
1851
-            $price_row,
1852
-            $price,
1853
-            $default,
1854
-            $disabled
1855
-        );
1856
-    }
1857
-
1858
-
1859
-    /**
1860
-     * @param int|string    $ticket_row
1861
-     * @param int|string    $price_row
1862
-     * @param EE_Price|null $price
1863
-     * @param bool          $default
1864
-     * @return string
1865
-     * @throws DomainException
1866
-     * @throws EE_Error
1867
-     * @throws ReflectionException
1868
-     */
1869
-    protected function _get_base_price_template(
1870
-        $ticket_row,
1871
-        $price_row,
1872
-        ?EE_Price $price,
1873
-        bool $default
1874
-    ): string {
1875
-        $template_args = [
1876
-            'tkt_row'                   => $default ? 'TICKETNUM' : $ticket_row,
1877
-            'PRC_order'                 => $default && empty($price) ? 'PRICENUM' : $price_row,
1878
-            'PRT_ID'                    => $default && empty($price) ? 1 : $price->get('PRT_ID'),
1879
-            'PRT_name'                  => esc_html__('Price', 'event_espresso'),
1880
-            'price_selected_operator'   => '+',
1881
-            'price_selected_is_percent' => 0,
1882
-        ];
1883
-        $template_args = apply_filters(
1884
-            'FHEE__espresso_events_Pricing_Hooks___get_base_price_template__template_args',
1885
-            $template_args,
1886
-            $ticket_row,
1887
-            $price_row,
1888
-            $price,
1889
-            $default,
1890
-            $this->_is_creating_event
1891
-        );
1892
-        return EEH_Template::display_template(
1893
-            PRICING_TEMPLATE_PATH . 'event_tickets_datetime_price_type_base.template.php',
1894
-            $template_args,
1895
-            true
1896
-        );
1897
-    }
1898
-
1899
-
1900
-    /**
1901
-     * @param int|string    $ticket_row
1902
-     * @param int|string    $price_row
1903
-     * @param EE_Price|null $price
1904
-     * @param bool          $default
1905
-     * @param bool          $disabled
1906
-     * @return string
1907
-     * @throws ReflectionException
1908
-     * @throws InvalidArgumentException
1909
-     * @throws InvalidInterfaceException
1910
-     * @throws InvalidDataTypeException
1911
-     * @throws DomainException
1912
-     * @throws EE_Error
1913
-     */
1914
-    protected function _get_price_modifier_template(
1915
-        $ticket_row,
1916
-        $price_row,
1917
-        ?EE_Price $price,
1918
-        bool $default,
1919
-        bool $disabled = false
1920
-    ): string {
1921
-        $select_name = $default && ! $price instanceof EE_Price
1922
-            ? 'edit_prices[TICKETNUM][PRICENUM][PRT_ID]'
1923
-            : 'edit_prices[' . esc_attr($ticket_row) . '][' . esc_attr($price_row) . '][PRT_ID]';
1924
-
1925
-        $price_type_model       = EEM_Price_Type::instance();
1926
-        $price_types            = $price_type_model->get_all(
1927
-            [
1928
-                [
1929
-                    'OR' => [
1930
-                        'PBT_ID'  => '2',
1931
-                        'PBT_ID*' => '3',
1932
-                    ],
1933
-                ],
1934
-            ]
1935
-        );
1936
-        $all_price_types        = $default && ! $price instanceof EE_Price
1937
-            ? [esc_html__('Select Modifier', 'event_espresso')]
1938
-            : [];
1939
-        $selected_price_type_id = $default && ! $price instanceof EE_Price ? 0 : $price->type();
1940
-        $price_option_spans     = '';
1941
-        // setup price types for selector
1942
-        foreach ($price_types as $price_type) {
1943
-            if (! $price_type instanceof EE_Price_Type) {
1944
-                continue;
1945
-            }
1946
-            $all_price_types[ $price_type->ID() ] = $price_type->get('PRT_name');
1947
-            // while we're in the loop let's setup the option spans used by js
1948
-            $span_args          = [
1949
-                'PRT_ID'         => $price_type->ID(),
1950
-                'PRT_operator'   => $price_type->is_discount() ? '-' : '+',
1951
-                'PRT_is_percent' => $price_type->get('PRT_is_percent') ? 1 : 0,
1952
-            ];
1953
-            $price_option_spans .= EEH_Template::display_template(
1954
-                PRICING_TEMPLATE_PATH . 'event_tickets_datetime_price_option_span.template.php',
1955
-                $span_args,
1956
-                true
1957
-            );
1958
-        }
1959
-
1960
-        $select_name = $disabled
1961
-            ? 'archive_price[' . $ticket_row . '][' . $price_row . '][PRT_ID]'
1962
-            : $select_name;
1963
-
1964
-        $select_input = new EE_Select_Input(
1965
-            $all_price_types,
1966
-            [
1967
-                'default'               => $selected_price_type_id,
1968
-                'html_name'             => $select_name,
1969
-                'html_class'            => 'edit-price-PRT_ID',
1970
-                'other_html_attributes' => $disabled ? 'style="width:auto;" disabled' : 'style="width:auto;"',
1971
-            ]
1972
-        );
1973
-
1974
-        $price_selected_operator   = $price instanceof EE_Price && $price->is_discount() ? '-' : '+';
1975
-        $price_selected_operator   = $default && ! $price instanceof EE_Price ? '' : $price_selected_operator;
1976
-        $price_selected_is_percent = $price instanceof EE_Price && $price->is_percent() ? 1 : 0;
1977
-        $price_selected_is_percent = $default && ! $price instanceof EE_Price ? '' : $price_selected_is_percent;
1978
-        $template_args             = [
1979
-            'tkt_row'                   => $default ? 'TICKETNUM' : $ticket_row,
1980
-            'PRC_order'                 => $default && ! $price instanceof EE_Price ? 'PRICENUM' : $price_row,
1981
-            'price_modifier_selector'   => $select_input->get_html_for_input(),
1982
-            'main_name'                 => $select_name,
1983
-            'selected_price_type_id'    => $selected_price_type_id,
1984
-            'price_option_spans'        => $price_option_spans,
1985
-            'price_selected_operator'   => $price_selected_operator,
1986
-            'price_selected_is_percent' => $price_selected_is_percent,
1987
-            'disabled'                  => $disabled,
1988
-        ];
1989
-        $template_args             = apply_filters(
1990
-            'FHEE__espresso_events_Pricing_Hooks___get_price_modifier_template__template_args',
1991
-            $template_args,
1992
-            $ticket_row,
1993
-            $price_row,
1994
-            $price,
1995
-            $default,
1996
-            $disabled,
1997
-            $this->_is_creating_event
1998
-        );
1999
-        return EEH_Template::display_template(
2000
-            PRICING_TEMPLATE_PATH . 'event_tickets_datetime_price_modifier_selector.template.php',
2001
-            $template_args,
2002
-            true
2003
-        );
2004
-    }
2005
-
2006
-
2007
-    /**
2008
-     * @param int|string       $datetime_row
2009
-     * @param int|string       $ticket_row
2010
-     * @param EE_Datetime|null $datetime
2011
-     * @param EE_Ticket|null   $ticket
2012
-     * @param array            $ticket_datetimes
2013
-     * @param bool             $default
2014
-     * @return string
2015
-     * @throws DomainException
2016
-     * @throws EE_Error
2017
-     * @throws ReflectionException
2018
-     */
2019
-    protected function _get_ticket_datetime_list_item(
2020
-        $datetime_row,
2021
-        $ticket_row,
2022
-        ?EE_Datetime $datetime,
2023
-        ?EE_Ticket $ticket,
2024
-        array $ticket_datetimes = [],
2025
-        bool $default = false
2026
-    ): string {
2027
-        $ticket_datetimes = $ticket instanceof EE_Ticket && isset($ticket_datetimes[ $ticket->ID() ])
2028
-            ? $ticket_datetimes[ $ticket->ID() ]
2029
-            : [];
2030
-        $template_args    = [
2031
-            'dtt_row'                  => $default && ! $datetime instanceof EE_Datetime
2032
-                ? 'DTTNUM'
2033
-                : $datetime_row,
2034
-            'tkt_row'                  => $default
2035
-                ? 'TICKETNUM'
2036
-                : $ticket_row,
2037
-            'ticket_datetime_selected' => in_array($datetime_row, $ticket_datetimes, true)
2038
-                ? ' ticket-selected'
2039
-                : '',
2040
-            'ticket_datetime_checked'  => in_array($datetime_row, $ticket_datetimes, true)
2041
-                ? ' checked'
2042
-                : '',
2043
-            'DTT_name'                 => $default && empty($datetime)
2044
-                ? 'DTTNAME'
2045
-                : $datetime->get_dtt_display_name(true),
2046
-            'tkt_status_class'         => '',
2047
-        ];
2048
-        $template_args    = apply_filters(
2049
-            'FHEE__espresso_events_Pricing_Hooks___get_ticket_datetime_list_item__template_args',
2050
-            $template_args,
2051
-            $datetime_row,
2052
-            $ticket_row,
2053
-            $datetime,
2054
-            $ticket,
2055
-            $ticket_datetimes,
2056
-            $default,
2057
-            $this->_is_creating_event
2058
-        );
2059
-        return EEH_Template::display_template(
2060
-            PRICING_TEMPLATE_PATH . 'event_tickets_datetime_ticket_datetimes_list_item.template.php',
2061
-            $template_args,
2062
-            true
2063
-        );
2064
-    }
2065
-
2066
-
2067
-    /**
2068
-     * @param array $all_datetimes
2069
-     * @param array $all_tickets
2070
-     * @return string
2071
-     * @throws ReflectionException
2072
-     * @throws InvalidArgumentException
2073
-     * @throws InvalidInterfaceException
2074
-     * @throws InvalidDataTypeException
2075
-     * @throws DomainException
2076
-     * @throws EE_Error
2077
-     */
2078
-    protected function _get_ticket_js_structure(array $all_datetimes = [], array $all_tickets = []): string
2079
-    {
2080
-        $template_args = [
2081
-            'default_datetime_edit_row' => $this->_get_dtt_edit_row(
2082
-                'DTTNUM',
2083
-                null,
2084
-                true,
2085
-                $all_datetimes
2086
-            ),
2087
-            'default_ticket_row'        => $this->_get_ticket_row(
2088
-                'TICKETNUM',
2089
-                null,
2090
-                [],
2091
-                [],
2092
-                true
2093
-            ),
2094
-            'default_price_row'         => $this->_get_ticket_price_row(
2095
-                'TICKETNUM',
2096
-                'PRICENUM',
2097
-                null,
2098
-                true,
2099
-                null
2100
-            ),
2101
-
2102
-            'default_price_rows'                       => '',
2103
-            'default_base_price_amount'                => 0,
2104
-            'default_base_price_name'                  => '',
2105
-            'default_base_price_description'           => '',
2106
-            'default_price_modifier_selector_row'      => $this->_get_price_modifier_template(
2107
-                'TICKETNUM',
2108
-                'PRICENUM',
2109
-                null,
2110
-                true
2111
-            ),
2112
-            'default_available_tickets_for_datetime'   => $this->_get_dtt_attached_tickets_row(
2113
-                'DTTNUM',
2114
-                null,
2115
-                [],
2116
-                [],
2117
-                true
2118
-            ),
2119
-            'existing_available_datetime_tickets_list' => '',
2120
-            'existing_available_ticket_datetimes_list' => '',
2121
-            'new_available_datetime_ticket_list_item'  => $this->_get_datetime_tickets_list_item(
2122
-                'DTTNUM',
2123
-                'TICKETNUM',
2124
-                null,
2125
-                null,
2126
-                [],
2127
-                true
2128
-            ),
2129
-            'new_available_ticket_datetime_list_item'  => $this->_get_ticket_datetime_list_item(
2130
-                'DTTNUM',
2131
-                'TICKETNUM',
2132
-                null,
2133
-                null,
2134
-                [],
2135
-                true
2136
-            ),
2137
-        ];
2138
-        $ticket_row    = 1;
2139
-        foreach ($all_tickets as $ticket) {
2140
-            $template_args['existing_available_datetime_tickets_list'] .= $this->_get_datetime_tickets_list_item(
2141
-                'DTTNUM',
2142
-                $ticket_row,
2143
-                null,
2144
-                $ticket,
2145
-                [],
2146
-                true
2147
-            );
2148
-            $ticket_row++;
2149
-        }
2150
-        $datetime_row = 1;
2151
-        foreach ($all_datetimes as $datetime) {
2152
-            $template_args['existing_available_ticket_datetimes_list'] .= $this->_get_ticket_datetime_list_item(
2153
-                $datetime_row,
2154
-                'TICKETNUM',
2155
-                $datetime,
2156
-                null,
2157
-                [],
2158
-                true
2159
-            );
2160
-            $datetime_row++;
2161
-        }
2162
-        $price_model    = EEM_Price::instance();
2163
-        $default_prices = $price_model->get_all_default_prices();
2164
-        $price_row      = 1;
2165
-        foreach ($default_prices as $price) {
2166
-            if (! $price instanceof EE_Price) {
2167
-                continue;
2168
-            }
2169
-            if ($price->is_base_price()) {
2170
-                $template_args['default_base_price_amount']      = $price->get_pretty(
2171
-                    'PRC_amount',
2172
-                    'localized_float'
2173
-                );
2174
-                $template_args['default_base_price_name']        = $price->get('PRC_name');
2175
-                $template_args['default_base_price_description'] = $price->get('PRC_desc');
2176
-                $price_row++;
2177
-                continue;
2178
-            }
2179
-
2180
-            $show_trash  = ! ((count($default_prices) > 1 && $price_row === 1) || count($default_prices) === 1);
2181
-            $show_create = ! (count($default_prices) > 1 && count($default_prices) !== $price_row);
2182
-
2183
-            $template_args['default_price_rows'] .= $this->_get_ticket_price_row(
2184
-                'TICKETNUM',
2185
-                $price_row,
2186
-                $price,
2187
-                true,
2188
-                null,
2189
-                $show_trash,
2190
-                $show_create
2191
-            );
2192
-            $price_row++;
2193
-        }
2194
-        $template_args = apply_filters(
2195
-            'FHEE__espresso_events_Pricing_Hooks___get_ticket_js_structure__template_args',
2196
-            $template_args,
2197
-            $all_datetimes,
2198
-            $all_tickets,
2199
-            $this->_is_creating_event
2200
-        );
2201
-        return EEH_Template::display_template(
2202
-            PRICING_TEMPLATE_PATH . 'event_tickets_datetime_ticket_js_structure.template.php',
2203
-            $template_args,
2204
-            true
2205
-        );
2206
-    }
231
+					],
232
+					'DTT_OVERSELL_WARNING'  => [
233
+						'datetime_ticket' => esc_html__(
234
+							'You cannot add this ticket to this datetime because it has a sold amount that is greater than the amount of spots remaining for this datetime.',
235
+							'event_espresso'
236
+						),
237
+						'ticket_datetime' => esc_html__(
238
+							'You cannot add this datetime to this ticket because the ticket has a sold amount that is greater than the amount of spots remaining on the datetime.',
239
+							'event_espresso'
240
+						),
241
+					],
242
+					'DTT_CONVERTED_FORMATS' => EEH_DTT_Helper::convert_php_to_js_and_moment_date_formats(
243
+						$this->_date_format_strings['date'],
244
+						$this->_date_format_strings['time']
245
+					),
246
+					'DTT_START_OF_WEEK'     => ['dayValue' => (int) get_option('start_of_week')],
247
+				],
248
+			],
249
+		];
250
+	}
251
+
252
+
253
+	/**
254
+	 * @param array $update_callbacks
255
+	 * @return array
256
+	 */
257
+	public function caf_updates(array $update_callbacks): array
258
+	{
259
+		unset($update_callbacks['_default_tickets_update']);
260
+		$update_callbacks['datetime_and_tickets_caf_update'] = [$this, 'datetime_and_tickets_caf_update'];
261
+		return $update_callbacks;
262
+	}
263
+
264
+
265
+	/**
266
+	 * Handles saving everything related to Tickets (datetimes, tickets, prices)
267
+	 *
268
+	 * @param EE_Event $event The Event object we're attaching data to
269
+	 * @param array    $data  The request data from the form
270
+	 * @throws ReflectionException
271
+	 * @throws Exception
272
+	 * @throws InvalidInterfaceException
273
+	 * @throws InvalidDataTypeException
274
+	 * @throws EE_Error
275
+	 * @throws InvalidArgumentException
276
+	 */
277
+	public function datetime_and_tickets_caf_update(EE_Event $event, array $data)
278
+	{
279
+		// first we need to start with datetimes cause they are the "root" items attached to events.
280
+		$saved_datetimes = $this->_update_datetimes($event, $data);
281
+		// next tackle the tickets (and prices?)
282
+		$this->_update_tickets($event, $saved_datetimes, $data);
283
+	}
284
+
285
+
286
+	/**
287
+	 * update event_datetimes
288
+	 *
289
+	 * @param EE_Event $event Event being updated
290
+	 * @param array    $data  the request data from the form
291
+	 * @return EE_Datetime[]
292
+	 * @throws Exception
293
+	 * @throws ReflectionException
294
+	 * @throws InvalidInterfaceException
295
+	 * @throws InvalidDataTypeException
296
+	 * @throws InvalidArgumentException
297
+	 * @throws EE_Error
298
+	 */
299
+	protected function _update_datetimes(EE_Event $event, array $data): array
300
+	{
301
+		$saved_datetime_ids  = [];
302
+		$saved_datetime_objs = [];
303
+		$timezone       = $data['timezone_string'] ?? null;
304
+		$datetime_model = EEM_Datetime::instance($timezone);
305
+
306
+		if (empty($data['edit_event_datetimes']) || ! is_array($data['edit_event_datetimes'])) {
307
+			throw new InvalidArgumentException(
308
+				esc_html__(
309
+					'The "edit_event_datetimes" array is invalid therefore the event can not be updated.',
310
+					'event_espresso'
311
+				)
312
+			);
313
+		}
314
+		foreach ($data['edit_event_datetimes'] as $row => $datetime_data) {
315
+			// trim all values to ensure any excess whitespace is removed.
316
+			$datetime_data = array_map(
317
+				function ($datetime_data) {
318
+					return is_array($datetime_data) ? $datetime_data : trim($datetime_data);
319
+				},
320
+				$datetime_data
321
+			);
322
+
323
+			$datetime_data['DTT_EVT_end'] = isset($datetime_data['DTT_EVT_end'])
324
+											&& ! empty($datetime_data['DTT_EVT_end'])
325
+				? $datetime_data['DTT_EVT_end']
326
+				: $datetime_data['DTT_EVT_start'];
327
+			$datetime_values              = [
328
+				'DTT_ID'          => ! empty($datetime_data['DTT_ID'])
329
+					? $datetime_data['DTT_ID']
330
+					: null,
331
+				'DTT_name'        => ! empty($datetime_data['DTT_name'])
332
+					? $datetime_data['DTT_name']
333
+					: '',
334
+				'DTT_description' => ! empty($datetime_data['DTT_description'])
335
+					? $datetime_data['DTT_description']
336
+					: '',
337
+				'DTT_EVT_start'   => $datetime_data['DTT_EVT_start'],
338
+				'DTT_EVT_end'     => $datetime_data['DTT_EVT_end'],
339
+				'DTT_reg_limit'   => empty($datetime_data['DTT_reg_limit'])
340
+					? EE_INF
341
+					: $datetime_data['DTT_reg_limit'],
342
+				'DTT_order'       => ! isset($datetime_data['DTT_order'])
343
+					? $row
344
+					: $datetime_data['DTT_order'],
345
+			];
346
+
347
+			// if we have an id then let's get existing object first and then set the new values.
348
+			// Otherwise we instantiate a new object for save.
349
+			if (! empty($datetime_data['DTT_ID'])) {
350
+				$datetime = EE_Registry::instance()
351
+									   ->load_model('Datetime', [$timezone])
352
+									   ->get_one_by_ID($datetime_data['DTT_ID']);
353
+				// set date and time format according to what is set in this class.
354
+				$datetime->set_date_format($this->_date_format_strings['date']);
355
+				$datetime->set_time_format($this->_date_format_strings['time']);
356
+				foreach ($datetime_values as $field => $value) {
357
+					$datetime->set($field, $value);
358
+				}
359
+
360
+				// make sure the $datetime_id here is saved just in case
361
+				// after the add_relation_to() the autosave replaces it.
362
+				// We need to do this so we dont' TRASH the parent DTT.
363
+				// (save the ID for both key and value to avoid duplications)
364
+				$saved_datetime_ids[ $datetime->ID() ] = $datetime->ID();
365
+			} else {
366
+				$datetime = EE_Datetime::new_instance(
367
+					$datetime_values,
368
+					$timezone,
369
+					[$this->_date_format_strings['date'], $this->_date_format_strings['time']]
370
+				);
371
+				foreach ($datetime_values as $field => $value) {
372
+					$datetime->set($field, $value);
373
+				}
374
+			}
375
+			$datetime->save();
376
+			do_action(
377
+				'AHEE__espresso_events_Pricing_Hooks___update_datetimes_after_save',
378
+				$datetime,
379
+				$row,
380
+				$datetime_data,
381
+				$data
382
+			);
383
+			$datetime = $event->_add_relation_to($datetime, 'Datetime');
384
+			// before going any further make sure our dates are setup correctly
385
+			// so that the end date is always equal or greater than the start date.
386
+			if ($datetime->get_raw('DTT_EVT_start') > $datetime->get_raw('DTT_EVT_end')) {
387
+				$datetime->set('DTT_EVT_end', $datetime->get('DTT_EVT_start'));
388
+				$datetime = EEH_DTT_Helper::date_time_add($datetime, 'DTT_EVT_end', 'days');
389
+				$datetime->save();
390
+			}
391
+			// now we have to make sure we add the new DTT_ID to the $saved_datetime_ids array
392
+			// because it is possible there was a new one created for the autosave.
393
+			// (save the ID for both key and value to avoid duplications)
394
+			$DTT_ID                        = $datetime->ID();
395
+			$saved_datetime_ids[ $DTT_ID ] = $DTT_ID;
396
+			$saved_datetime_objs[ $row ]   = $datetime;
397
+			// @todo if ANY of these updates fail then we want the appropriate global error message.
398
+		}
399
+		$event->save();
400
+		// now we need to REMOVE any datetimes that got deleted.
401
+		// Keep in mind that this process will only kick in for datetimes that don't have any DTT_sold on them.
402
+		// So its safe to permanently delete at this point.
403
+		$old_datetimes = explode(',', $data['datetime_IDs']);
404
+		$old_datetimes = $old_datetimes[0] === '' ? [] : $old_datetimes;
405
+		if (is_array($old_datetimes)) {
406
+			$datetimes_to_delete = array_diff($old_datetimes, $saved_datetime_ids);
407
+			foreach ($datetimes_to_delete as $id) {
408
+				$id = absint($id);
409
+				if (empty($id)) {
410
+					continue;
411
+				}
412
+				$dtt_to_remove = $datetime_model->get_one_by_ID($id);
413
+				// remove tkt relationships.
414
+				$related_tickets = $dtt_to_remove->get_many_related('Ticket');
415
+				foreach ($related_tickets as $ticket) {
416
+					$dtt_to_remove->_remove_relation_to($ticket, 'Ticket');
417
+				}
418
+				$event->_remove_relation_to($id, 'Datetime');
419
+				$dtt_to_remove->refresh_cache_of_related_objects();
420
+			}
421
+		}
422
+		return $saved_datetime_objs;
423
+	}
424
+
425
+
426
+	/**
427
+	 * update tickets
428
+	 *
429
+	 * @param EE_Event      $event           Event object being updated
430
+	 * @param EE_Datetime[] $saved_datetimes an array of datetime ids being updated
431
+	 * @param array         $data            incoming request data
432
+	 * @return EE_Ticket[]
433
+	 * @throws Exception
434
+	 * @throws ReflectionException
435
+	 * @throws InvalidInterfaceException
436
+	 * @throws InvalidDataTypeException
437
+	 * @throws InvalidArgumentException
438
+	 * @throws EE_Error
439
+	 */
440
+	protected function _update_tickets(EE_Event $event, array $saved_datetimes, array $data): array
441
+	{
442
+		$new_ticket = null;
443
+		// stripslashes because WP filtered the $_POST ($data) array to add slashes
444
+		$data          = stripslashes_deep($data);
445
+		$timezone      = $data['timezone_string'] ?? null;
446
+		$ticket_model = EEM_Ticket::instance($timezone);
447
+
448
+		$saved_tickets = [];
449
+		$old_tickets   = isset($data['ticket_IDs']) ? explode(',', $data['ticket_IDs']) : [];
450
+		if (empty($data['edit_tickets']) || ! is_array($data['edit_tickets'])) {
451
+			throw new InvalidArgumentException(
452
+				esc_html__(
453
+					'The "edit_tickets" array is invalid therefore the event can not be updated.',
454
+					'event_espresso'
455
+				)
456
+			);
457
+		}
458
+		foreach ($data['edit_tickets'] as $row => $ticket_data) {
459
+			$update_prices = $create_new_TKT = false;
460
+			// figure out what datetimes were added to the ticket
461
+			// and what datetimes were removed from the ticket in the session.
462
+			$starting_ticket_datetime_rows = explode(',', $data['starting_ticket_datetime_rows'][ $row ]);
463
+			$ticket_datetime_rows          = explode(',', $data['ticket_datetime_rows'][ $row ]);
464
+			$datetimes_added               = array_diff($ticket_datetime_rows, $starting_ticket_datetime_rows);
465
+			$datetimes_removed             = array_diff($starting_ticket_datetime_rows, $ticket_datetime_rows);
466
+			// trim inputs to ensure any excess whitespace is removed.
467
+			$ticket_data = array_map(
468
+				function ($ticket_data) {
469
+					return is_array($ticket_data) ? $ticket_data : trim($ticket_data);
470
+				},
471
+				$ticket_data
472
+			);
473
+			// note we are doing conversions to floats here instead of allowing EE_Money_Field to handle
474
+			// because we're doing calculations prior to using the models.
475
+			// note incoming ['TKT_price'] value is already in standard notation (via js).
476
+			$ticket_price = isset($ticket_data['TKT_price'])
477
+				? round((float) $ticket_data['TKT_price'], 3)
478
+				: 0;
479
+			// note incoming base price needs converted from localized value.
480
+			$base_price = isset($ticket_data['TKT_base_price'])
481
+				? EEH_Money::convert_to_float_from_localized_money($ticket_data['TKT_base_price'])
482
+				: 0;
483
+			// if ticket price == 0 and $base_price != 0 then ticket price == base_price
484
+			$ticket_price  = $ticket_price === 0 && $base_price !== 0
485
+				? $base_price
486
+				: $ticket_price;
487
+			$base_price_id = $ticket_data['TKT_base_price_ID'] ?? 0;
488
+			$price_rows    = is_array($data['edit_prices']) && isset($data['edit_prices'][ $row ])
489
+				? $data['edit_prices'][ $row ]
490
+				: [];
491
+			$now           = null;
492
+			if (empty($ticket_data['TKT_start_date'])) {
493
+				// lets' use now in the set timezone.
494
+				$now                           = new DateTime('now', new DateTimeZone($event->get_timezone()));
495
+				$ticket_data['TKT_start_date'] = $now->format($this->_date_time_format);
496
+			}
497
+			if (empty($ticket_data['TKT_end_date'])) {
498
+				/**
499
+				 * set the TKT_end_date to the first datetime attached to the ticket.
500
+				 */
501
+				$first_datetime              = $saved_datetimes[ reset($ticket_datetime_rows) ];
502
+				$ticket_data['TKT_end_date'] = $first_datetime->start_date_and_time($this->_date_time_format);
503
+			}
504
+			$TKT_values = [
505
+				'TKT_ID'          => ! empty($ticket_data['TKT_ID']) ? $ticket_data['TKT_ID'] : null,
506
+				'TTM_ID'          => ! empty($ticket_data['TTM_ID']) ? $ticket_data['TTM_ID'] : 0,
507
+				'TKT_name'        => ! empty($ticket_data['TKT_name']) ? $ticket_data['TKT_name'] : '',
508
+				'TKT_description' => ! empty($ticket_data['TKT_description'])
509
+									 && $ticket_data['TKT_description'] !== esc_html__(
510
+										 'You can modify this description',
511
+										 'event_espresso'
512
+									 )
513
+					? $ticket_data['TKT_description']
514
+					: '',
515
+				'TKT_start_date'  => $ticket_data['TKT_start_date'],
516
+				'TKT_end_date'    => $ticket_data['TKT_end_date'],
517
+				'TKT_qty'         => ! isset($ticket_data['TKT_qty']) || $ticket_data['TKT_qty'] === ''
518
+					? EE_INF
519
+					: $ticket_data['TKT_qty'],
520
+				'TKT_uses'        => ! isset($ticket_data['TKT_uses']) || $ticket_data['TKT_uses'] === ''
521
+					? EE_INF
522
+					: $ticket_data['TKT_uses'],
523
+				'TKT_min'         => empty($ticket_data['TKT_min']) ? 0 : $ticket_data['TKT_min'],
524
+				'TKT_max'         => empty($ticket_data['TKT_max']) ? EE_INF : $ticket_data['TKT_max'],
525
+				'TKT_row'         => $row,
526
+				'TKT_order'       => $ticket_data['TKT_order'] ?? 0,
527
+				'TKT_taxable'     => ! empty($ticket_data['TKT_taxable']) ? 1 : 0,
528
+				'TKT_required'    => ! empty($ticket_data['TKT_required']) ? 1 : 0,
529
+				'TKT_price'       => $ticket_price,
530
+			];
531
+			// if this is a default TKT, then we need to set the TKT_ID to 0 and update accordingly,
532
+			// which means in turn that the prices will become new prices as well.
533
+			if (isset($ticket_data['TKT_is_default']) && $ticket_data['TKT_is_default']) {
534
+				$TKT_values['TKT_ID']         = 0;
535
+				$TKT_values['TKT_is_default'] = 0;
536
+				$update_prices                = true;
537
+			}
538
+			// if we have a TKT_ID then we need to get that existing TKT_obj and update it
539
+			// we actually do our saves ahead of doing any add_relations to
540
+			// because its entirely possible that this ticket wasn't removed or added to any datetime in the session
541
+			// but DID have it's items modified.
542
+			// keep in mind that if the TKT has been sold (and we have changed pricing information),
543
+			// then we won't be updating the ticket but instead a new ticket will be created and the old one archived.
544
+			if (absint($TKT_values['TKT_ID'])) {
545
+				$ticket = EE_Registry::instance()
546
+									 ->load_model('Ticket', [$timezone])
547
+									 ->get_one_by_ID($TKT_values['TKT_ID']);
548
+				if ($ticket instanceof EE_Ticket) {
549
+					$ticket = $this->_update_ticket_datetimes(
550
+						$ticket,
551
+						$saved_datetimes,
552
+						$datetimes_added,
553
+						$datetimes_removed
554
+					);
555
+					// are there any registrations using this ticket ?
556
+					$tickets_sold = $ticket->count_related(
557
+						'Registration',
558
+						[
559
+							[
560
+								'STS_ID' => ['NOT IN', [EEM_Registration::status_id_incomplete]],
561
+							],
562
+						]
563
+					);
564
+					// set ticket formats
565
+					$ticket->set_date_format($this->_date_format_strings['date']);
566
+					$ticket->set_time_format($this->_date_format_strings['time']);
567
+					// let's just check the total price for the existing ticket
568
+					// and determine if it matches the new total price.
569
+					// if they are different then we create a new ticket (if tickets sold)
570
+					// if they aren't different then we go ahead and modify existing ticket.
571
+					$create_new_TKT = $tickets_sold > 0 && $ticket_price !== $ticket->price() && ! $ticket->deleted();
572
+					// set new values
573
+					foreach ($TKT_values as $field => $value) {
574
+						if ($field === 'TKT_qty') {
575
+							$ticket->set_qty($value);
576
+						} else {
577
+							$ticket->set($field, $value);
578
+						}
579
+					}
580
+					// if $create_new_TKT is false then we can safely update the existing ticket.
581
+					// Otherwise we have to create a new ticket.
582
+					if ($create_new_TKT) {
583
+						$new_ticket = $this->_duplicate_ticket(
584
+							$ticket,
585
+							$price_rows,
586
+							$ticket_price,
587
+							$base_price,
588
+							$base_price_id
589
+						);
590
+					}
591
+				}
592
+			} else {
593
+				// no TKT_id so a new TKT
594
+				$ticket = EE_Ticket::new_instance(
595
+					$TKT_values,
596
+					$timezone,
597
+					[$this->_date_format_strings['date'], $this->_date_format_strings['time']]
598
+				);
599
+				if ($ticket instanceof EE_Ticket) {
600
+					// make sure ticket has an ID of setting relations won't work
601
+					$ticket->save();
602
+					$ticket        = $this->_update_ticket_datetimes(
603
+						$ticket,
604
+						$saved_datetimes,
605
+						$datetimes_added,
606
+						$datetimes_removed
607
+					);
608
+					$update_prices = true;
609
+				}
610
+			}
611
+			// make sure any current values have been saved.
612
+			// $ticket->save();
613
+			// before going any further make sure our dates are setup correctly
614
+			// so that the end date is always equal or greater than the start date.
615
+			if ($ticket->get_raw('TKT_start_date') > $ticket->get_raw('TKT_end_date')) {
616
+				$ticket->set('TKT_end_date', $ticket->get('TKT_start_date'));
617
+				$ticket = EEH_DTT_Helper::date_time_add($ticket, 'TKT_end_date', 'days');
618
+			}
619
+			// let's make sure the base price is handled
620
+			$ticket = ! $create_new_TKT
621
+				? $this->_add_prices_to_ticket(
622
+					[],
623
+					$ticket,
624
+					$update_prices,
625
+					$base_price,
626
+					$base_price_id
627
+				)
628
+				: $ticket;
629
+			// add/update price_modifiers
630
+			$ticket = ! $create_new_TKT
631
+				? $this->_add_prices_to_ticket($price_rows, $ticket, $update_prices)
632
+				: $ticket;
633
+			// need to make sue that the TKT_price is accurate after saving the prices.
634
+			$ticket->ensure_TKT_Price_correct();
635
+			// handle CREATING a default ticket from the incoming ticket but ONLY if this isn't an autosave.
636
+			if (! defined('DOING_AUTOSAVE') && ! empty($ticket_data['TKT_is_default_selector'])) {
637
+				$new_default = clone $ticket;
638
+				$new_default->set('TKT_ID', 0);
639
+				$new_default->set('TKT_is_default', 1);
640
+				$new_default->set('TKT_row', 1);
641
+				$new_default->set('TKT_price', $ticket_price);
642
+				// remove any datetime relations cause we DON'T want datetime relations attached
643
+				// (note this is just removing the cached relations in the object)
644
+				$new_default->_remove_relations('Datetime');
645
+				// @todo we need to add the current attached prices as new prices to the new default ticket.
646
+				$new_default = $this->_add_prices_to_ticket(
647
+					$price_rows,
648
+					$new_default,
649
+					true
650
+				);
651
+				// don't forget the base price!
652
+				$new_default = $this->_add_prices_to_ticket(
653
+					[],
654
+					$new_default,
655
+					true,
656
+					$base_price,
657
+					$base_price_id
658
+				);
659
+				$new_default->save();
660
+				do_action(
661
+					'AHEE__espresso_events_Pricing_Hooks___update_tkts_new_default_ticket',
662
+					$new_default,
663
+					$row,
664
+					$ticket,
665
+					$data
666
+				);
667
+			}
668
+			// DO ALL datetime relationships for both current tickets and any archived tickets
669
+			// for the given datetime that are related to the current ticket.
670
+			// TODO... not sure exactly how we're going to do this considering we don't know
671
+			// what current ticket the archived tickets are related to
672
+			// (and TKT_parent is used for autosaves so that's not a field we can reliably use).
673
+			// let's assign any tickets that have been setup to the saved_tickets tracker
674
+			// save existing TKT
675
+			$ticket->save();
676
+			if ($create_new_TKT && $new_ticket instanceof EE_Ticket) {
677
+				// save new TKT
678
+				$new_ticket->save();
679
+				// add new ticket to array
680
+				$saved_tickets[ $new_ticket->ID() ] = $new_ticket;
681
+				do_action(
682
+					'AHEE__espresso_events_Pricing_Hooks___update_tkts_new_ticket',
683
+					$new_ticket,
684
+					$row,
685
+					$ticket_data,
686
+					$data
687
+				);
688
+			} else {
689
+				// add ticket to saved tickets
690
+				$saved_tickets[ $ticket->ID() ] = $ticket;
691
+				do_action(
692
+					'AHEE__espresso_events_Pricing_Hooks___update_tkts_update_ticket',
693
+					$ticket,
694
+					$row,
695
+					$ticket_data,
696
+					$data
697
+				);
698
+			}
699
+		}
700
+		// now we need to handle tickets actually "deleted permanently".
701
+		// There are cases where we'd want this to happen
702
+		// (i.e. autosaves are happening and then in between autosaves the user trashes a ticket).
703
+		// Or a draft event was saved and in the process of editing a ticket is trashed.
704
+		// No sense in keeping all the related data in the db!
705
+		$old_tickets     = isset($old_tickets[0]) && $old_tickets[0] === '' ? [] : $old_tickets;
706
+		$tickets_removed = array_diff($old_tickets, array_keys($saved_tickets));
707
+		foreach ($tickets_removed as $id) {
708
+			$id = absint($id);
709
+			// get the ticket for this id
710
+			$ticket_to_remove = $ticket_model->get_one_by_ID($id);
711
+			// if this tkt is a default tkt we leave it alone cause it won't be attached to the datetime
712
+			if ($ticket_to_remove->get('TKT_is_default')) {
713
+				continue;
714
+			}
715
+			// if this ticket has any registrations attached so then we just ARCHIVE
716
+			// because we don't actually permanently delete these tickets.
717
+			if ($ticket_to_remove->count_related('Registration') > 0) {
718
+				$ticket_to_remove->delete();
719
+				continue;
720
+			}
721
+			// need to get all the related datetimes on this ticket and remove from every single one of them
722
+			// (remember this process can ONLY kick off if there are NO tickets_sold)
723
+			$datetimes = $ticket_to_remove->get_many_related('Datetime');
724
+			foreach ($datetimes as $datetime) {
725
+				$ticket_to_remove->_remove_relation_to($datetime, 'Datetime');
726
+			}
727
+			// need to do the same for prices (except these prices can also be deleted because again,
728
+			// tickets can only be trashed if they don't have any TKTs sold (otherwise they are just archived))
729
+			$ticket_to_remove->delete_related('Price');
730
+			do_action('AHEE__espresso_events_Pricing_Hooks___update_tkts_delete_ticket', $ticket_to_remove);
731
+			// finally let's delete this ticket
732
+			// (which should not be blocked at this point b/c we've removed all our relationships)
733
+			$ticket_to_remove->delete_or_restore();
734
+		}
735
+		return $saved_tickets;
736
+	}
737
+
738
+
739
+	/**
740
+	 * @access  protected
741
+	 * @param EE_Ticket     $ticket
742
+	 * @param EE_Datetime[] $saved_datetimes
743
+	 * @param int[]         $added_datetimes
744
+	 * @param int[]         $removed_datetimes
745
+	 * @return EE_Ticket
746
+	 * @throws EE_Error
747
+	 * @throws ReflectionException
748
+	 */
749
+	protected function _update_ticket_datetimes(
750
+		EE_Ticket $ticket,
751
+		array $saved_datetimes = [],
752
+		array $added_datetimes = [],
753
+		array $removed_datetimes = []
754
+	): EE_Ticket {
755
+		// to start we have to add the ticket to all the datetimes its supposed to be with,
756
+		// and removing the ticket from datetimes it got removed from.
757
+		// first let's add datetimes
758
+		if (! empty($added_datetimes) && is_array($added_datetimes)) {
759
+			foreach ($added_datetimes as $row_id) {
760
+				if (isset($saved_datetimes[ $row_id ]) && $saved_datetimes[ $row_id ] instanceof EE_Datetime) {
761
+					$ticket->_add_relation_to($saved_datetimes[ $row_id ], 'Datetime');
762
+					// Is this an existing ticket (has an ID) and does it have any sold?
763
+					// If so, then we need to add that to the DTT sold because this DTT is getting added.
764
+					if ($ticket->ID() && $ticket->sold() > 0) {
765
+						$saved_datetimes[ $row_id ]->increaseSold($ticket->sold(), false);
766
+					}
767
+				}
768
+			}
769
+		}
770
+		// then remove datetimes
771
+		if (! empty($removed_datetimes) && is_array($removed_datetimes)) {
772
+			foreach ($removed_datetimes as $row_id) {
773
+				// its entirely possible that a datetime got deleted (instead of just removed from relationship.
774
+				// So make sure we skip over this if the datetime isn't in the $saved_datetimes array)
775
+				if (isset($saved_datetimes[ $row_id ]) && $saved_datetimes[ $row_id ] instanceof EE_Datetime) {
776
+					$ticket->_remove_relation_to($saved_datetimes[ $row_id ], 'Datetime');
777
+				}
778
+			}
779
+		}
780
+		// cap ticket qty by datetime reg limits
781
+		$ticket->set_qty(min($ticket->qty(), $ticket->qty('reg_limit')));
782
+		return $ticket;
783
+	}
784
+
785
+
786
+	/**
787
+	 * @access  protected
788
+	 * @param EE_Ticket $ticket
789
+	 * @param array     $price_rows
790
+	 * @param int|float $ticket_price
791
+	 * @param int|float $base_price
792
+	 * @param int       $base_price_id
793
+	 * @return EE_Ticket
794
+	 * @throws ReflectionException
795
+	 * @throws InvalidArgumentException
796
+	 * @throws InvalidInterfaceException
797
+	 * @throws InvalidDataTypeException
798
+	 * @throws EE_Error
799
+	 */
800
+	protected function _duplicate_ticket(
801
+		EE_Ticket $ticket,
802
+		array $price_rows = [],
803
+		$ticket_price = 0,
804
+		$base_price = 0,
805
+		int $base_price_id = 0
806
+	): EE_Ticket {
807
+		// create new ticket that's a copy of the existing
808
+		// except a new id of course (and not archived)
809
+		// AND has the new TKT_price associated with it.
810
+		$new_ticket = clone $ticket;
811
+		$new_ticket->set('TKT_ID', 0);
812
+		$new_ticket->set_deleted(0);
813
+		$new_ticket->set_price($ticket_price);
814
+		$new_ticket->set_sold(0);
815
+		// let's get a new ID for this ticket
816
+		$new_ticket->save();
817
+		// we also need to make sure this new ticket gets the same datetime attachments as the archived ticket
818
+		$datetimes_on_existing = $ticket->datetimes();
819
+		$new_ticket            = $this->_update_ticket_datetimes(
820
+			$new_ticket,
821
+			$datetimes_on_existing,
822
+			array_keys($datetimes_on_existing)
823
+		);
824
+		// $ticket will get archived later b/c we are NOT adding it to the saved_tickets array.
825
+		// if existing $ticket has sold amount, then we need to adjust the qty for the new TKT to = the remaining
826
+		// available.
827
+		if ($ticket->sold() > 0) {
828
+			$new_qty = $ticket->qty() - $ticket->sold();
829
+			$new_ticket->set_qty($new_qty);
830
+		}
831
+		// now we update the prices just for this ticket
832
+		$new_ticket = $this->_add_prices_to_ticket($price_rows, $new_ticket, true);
833
+		// and we update the base price
834
+		return $this->_add_prices_to_ticket(
835
+			[],
836
+			$new_ticket,
837
+			true,
838
+			$base_price,
839
+			$base_price_id
840
+		);
841
+	}
842
+
843
+
844
+	/**
845
+	 * This attaches a list of given prices to a ticket.
846
+	 * Note we dont' have to worry about ever removing relationships (or archiving prices) because if there is a change
847
+	 * in price information on a ticket, a new ticket is created anyways so the archived ticket will retain the old
848
+	 * price info and prices are automatically "archived" via the ticket.
849
+	 *
850
+	 * @access  private
851
+	 * @param array     $prices        Array of prices from the form.
852
+	 * @param EE_Ticket $ticket        EE_Ticket object that prices are being attached to.
853
+	 * @param bool      $new_prices    Whether attach existing incoming prices or create new ones.
854
+	 * @param int|bool  $base_price    if FALSE then NOT doing a base price add.
855
+	 * @param int|bool  $base_price_id if present then this is the base_price_id being updated.
856
+	 * @return EE_Ticket
857
+	 * @throws ReflectionException
858
+	 * @throws InvalidArgumentException
859
+	 * @throws InvalidInterfaceException
860
+	 * @throws InvalidDataTypeException
861
+	 * @throws EE_Error
862
+	 */
863
+	protected function _add_prices_to_ticket(
864
+		array $prices,
865
+		EE_Ticket $ticket,
866
+		bool $new_prices = false,
867
+		$base_price = false,
868
+		$base_price_id = false
869
+	): EE_Ticket {
870
+		$price_model = EEM_Price::instance();
871
+		// let's just get any current prices that may exist on the given ticket
872
+		// so we can remove any prices that got trashed in this session.
873
+		$current_prices_on_ticket = $base_price !== false
874
+			? $ticket->base_price(true)
875
+			: $ticket->price_modifiers();
876
+		$updated_prices           = [];
877
+		// if $base_price ! FALSE then updating a base price.
878
+		if ($base_price !== false) {
879
+			$prices[1] = [
880
+				'PRC_ID'     => $new_prices || $base_price_id === 1 ? null : $base_price_id,
881
+				'PRT_ID'     => 1,
882
+				'PRC_amount' => $base_price,
883
+				'PRC_name'   => $ticket->get('TKT_name'),
884
+				'PRC_desc'   => $ticket->get('TKT_description'),
885
+			];
886
+		}
887
+		// possibly need to save ticket
888
+		if (! $ticket->ID()) {
889
+			$ticket->save();
890
+		}
891
+		foreach ($prices as $row => $prc) {
892
+			$prt_id = ! empty($prc['PRT_ID']) ? $prc['PRT_ID'] : null;
893
+			if (empty($prt_id)) {
894
+				continue;
895
+			} //prices MUST have a price type id.
896
+			$PRC_values = [
897
+				'PRC_ID'         => ! empty($prc['PRC_ID']) ? $prc['PRC_ID'] : null,
898
+				'PRT_ID'         => $prt_id,
899
+				'PRC_amount'     => ! empty($prc['PRC_amount']) ? $prc['PRC_amount'] : 0,
900
+				'PRC_name'       => ! empty($prc['PRC_name']) ? $prc['PRC_name'] : '',
901
+				'PRC_desc'       => ! empty($prc['PRC_desc']) ? $prc['PRC_desc'] : '',
902
+				'PRC_is_default' => false,
903
+				// make sure we set PRC_is_default to false for all ticket saves from event_editor
904
+				'PRC_order'      => $row,
905
+			];
906
+			if ($new_prices || empty($PRC_values['PRC_ID'])) {
907
+				$PRC_values['PRC_ID'] = 0;
908
+				$price                = EE_Registry::instance()->load_class(
909
+					'Price',
910
+					[$PRC_values],
911
+					false,
912
+					false
913
+				);
914
+			} else {
915
+				$price = $price_model->get_one_by_ID($prc['PRC_ID']);
916
+				// update this price with new values
917
+				foreach ($PRC_values as $field => $value) {
918
+					$price->set($field, $value);
919
+				}
920
+			}
921
+			$price->save();
922
+			$updated_prices[ $price->ID() ] = $price;
923
+			$ticket->_add_relation_to($price, 'Price');
924
+		}
925
+		// now let's remove any prices that got removed from the ticket
926
+		if (! empty($current_prices_on_ticket)) {
927
+			$current          = array_keys($current_prices_on_ticket);
928
+			$updated          = array_keys($updated_prices);
929
+			$prices_to_remove = array_diff($current, $updated);
930
+			if (! empty($prices_to_remove)) {
931
+				foreach ($prices_to_remove as $prc_id) {
932
+					$p = $current_prices_on_ticket[ $prc_id ];
933
+					$ticket->_remove_relation_to($p, 'Price');
934
+					// delete permanently the price
935
+					$p->delete_or_restore();
936
+				}
937
+			}
938
+		}
939
+		return $ticket;
940
+	}
941
+
942
+
943
+	/**
944
+	 * @throws ReflectionException
945
+	 * @throws InvalidArgumentException
946
+	 * @throws InvalidInterfaceException
947
+	 * @throws InvalidDataTypeException
948
+	 * @throws DomainException
949
+	 * @throws EE_Error
950
+	 */
951
+	public function pricing_metabox()
952
+	{
953
+		$event                 = $this->_adminpage_obj->get_cpt_model_obj();
954
+		$timezone = $event instanceof EE_Event ? $event->timezone_string() : null;
955
+		$price_model = EEM_Price::instance($timezone);
956
+		$ticket_model = EEM_Ticket::instance($timezone);
957
+		$datetime_model = EEM_Datetime::instance($timezone);
958
+
959
+		// set is_creating_event property.
960
+		$EVT_ID                   = $event->ID();
961
+		$this->_is_creating_event = empty($this->_req_data['post']);
962
+		$existing_datetime_ids = $existing_ticket_ids = $datetime_tickets = $ticket_datetimes = [];
963
+
964
+		// default main template args
965
+		$main_template_args = [
966
+			'event_datetime_help_link' => EEH_Template::get_help_tab_link(
967
+				'event_editor_event_datetimes_help_tab',
968
+				$this->_adminpage_obj->page_slug,
969
+				$this->_adminpage_obj->get_req_action()
970
+			),
971
+
972
+			// todo need to add a filter to the template for the help text
973
+			// in the Events_Admin_Page core file so we can add further help
974
+			'add_new_dtt_help_link'    => EEH_Template::get_help_tab_link(
975
+				'add_new_dtt_info',
976
+				$this->_adminpage_obj->page_slug,
977
+				$this->_adminpage_obj->get_req_action()
978
+			),
979
+			// todo need to add this help info id to the Events_Admin_Page core file so we can access it here.
980
+			'datetime_rows'            => '',
981
+			'show_tickets_container'   => '',
982
+			'ticket_rows'              => '',
983
+			'ee_collapsible_status'    => ' ee-collapsible-open'
984
+			// $this->_adminpage_obj->get_cpt_model_obj()->ID() > 0 ? ' ee-collapsible-closed' : ' ee-collapsible-open'
985
+		];
986
+		do_action('AHEE_log', __FILE__, __FUNCTION__, '');
987
+
988
+		/**
989
+		 * 1. Start with retrieving Datetimes
990
+		 * 2. For each datetime get related tickets
991
+		 * 3. For each ticket get related prices
992
+		 */
993
+		$datetimes                            = $datetime_model->get_all_event_dates($EVT_ID);
994
+		$main_template_args['total_dtt_rows'] = count($datetimes);
995
+
996
+		/**
997
+		 * @see https://events.codebasehq.com/projects/event-espresso/tickets/9486
998
+		 * for why we are counting $datetime_row and then setting that on the Datetime object
999
+		 */
1000
+		$datetime_row = 1;
1001
+		foreach ($datetimes as $datetime) {
1002
+			$DTT_ID = $datetime->get('DTT_ID');
1003
+			$datetime->set('DTT_order', $datetime_row);
1004
+			$existing_datetime_ids[] = $DTT_ID;
1005
+			// tickets attached
1006
+			$related_tickets = $datetime->ID() > 0
1007
+				? $datetime->get_many_related(
1008
+					'Ticket',
1009
+					[
1010
+						[
1011
+							'OR' => ['TKT_deleted' => 1, 'TKT_deleted*' => 0],
1012
+						],
1013
+						'default_where_conditions' => 'none',
1014
+						'order_by'                 => ['TKT_order' => 'ASC'],
1015
+					]
1016
+				)
1017
+				: [];
1018
+			// if there are no related tickets this is likely a new event OR auto-draft
1019
+			// event so we need to generate the default tickets because datetimes
1020
+			// ALWAYS have at least one related ticket!!.  EXCEPT, we dont' do this if there is already more than one
1021
+			// datetime on the event.
1022
+			if (empty($related_tickets) && count($datetimes) < 2) {
1023
+				$related_tickets = $ticket_model->get_all_default_tickets();
1024
+				// this should be ordered by TKT_ID, so let's grab the first default ticket
1025
+				// (which will be the main default) and ensure it has any default prices added to it (but do NOT save).
1026
+				$default_prices      = $price_model->get_all_default_prices();
1027
+				$main_default_ticket = reset($related_tickets);
1028
+				if ($main_default_ticket instanceof EE_Ticket) {
1029
+					foreach ($default_prices as $default_price) {
1030
+						if ($default_price instanceof EE_Price && $default_price->is_base_price()) {
1031
+							continue;
1032
+						}
1033
+						$main_default_ticket->cache('Price', $default_price);
1034
+					}
1035
+				}
1036
+			}
1037
+			// we can't actually setup rows in this loop yet cause we don't know all
1038
+			// the unique tickets for this event yet (tickets are linked through all datetimes).
1039
+			// So we're going to temporarily cache some of that information.
1040
+			// loop through and setup the ticket rows and make sure the order is set.
1041
+			foreach ($related_tickets as $ticket) {
1042
+				$TKT_ID     = $ticket->get('TKT_ID');
1043
+				$ticket_row = $ticket->get('TKT_row');
1044
+				// we only want unique tickets in our final display!!
1045
+				if (! in_array($TKT_ID, $existing_ticket_ids, true)) {
1046
+					$existing_ticket_ids[] = $TKT_ID;
1047
+					$all_tickets[]         = $ticket;
1048
+				}
1049
+				// temporary cache of this ticket info for this datetime for later processing of datetime rows.
1050
+				$datetime_tickets[ $DTT_ID ][] = $ticket_row;
1051
+				// temporary cache of this datetime info for this ticket for later processing of ticket rows.
1052
+				if (
1053
+					! isset($ticket_datetimes[ $TKT_ID ])
1054
+					|| ! in_array($datetime_row, $ticket_datetimes[ $TKT_ID ], true)
1055
+				) {
1056
+					$ticket_datetimes[ $TKT_ID ][] = $datetime_row;
1057
+				}
1058
+			}
1059
+			$datetime_row++;
1060
+		}
1061
+		$main_template_args['total_ticket_rows']     = count($existing_ticket_ids);
1062
+		$main_template_args['existing_ticket_ids']   = implode(',', $existing_ticket_ids);
1063
+		$main_template_args['existing_datetime_ids'] = implode(',', $existing_datetime_ids);
1064
+		// sort $all_tickets by order
1065
+		usort(
1066
+			$all_tickets,
1067
+			function (EE_Ticket $a, EE_Ticket $b) {
1068
+				$a_order = (int) $a->get('TKT_order');
1069
+				$b_order = (int) $b->get('TKT_order');
1070
+				if ($a_order === $b_order) {
1071
+					return 0;
1072
+				}
1073
+				return ($a_order < $b_order) ? -1 : 1;
1074
+			}
1075
+		);
1076
+		// k NOW we have all the data we need for setting up the datetime rows
1077
+		// and ticket rows so we start our datetime loop again.
1078
+		$datetime_row = 1;
1079
+		foreach ($datetimes as $datetime) {
1080
+			$main_template_args['datetime_rows'] .= $this->_get_datetime_row(
1081
+				$datetime_row,
1082
+				$datetime,
1083
+				$datetime_tickets,
1084
+				$all_tickets,
1085
+				false,
1086
+				$datetimes
1087
+			);
1088
+			$datetime_row++;
1089
+		}
1090
+		// then loop through all tickets for the ticket rows.
1091
+		$ticket_row = 1;
1092
+		foreach ($all_tickets as $ticket) {
1093
+			$main_template_args['ticket_rows'] .= $this->_get_ticket_row(
1094
+				$ticket_row,
1095
+				$ticket,
1096
+				$ticket_datetimes,
1097
+				$datetimes,
1098
+				false,
1099
+				$all_tickets
1100
+			);
1101
+			$ticket_row++;
1102
+		}
1103
+		$main_template_args['ticket_js_structure'] = $this->_get_ticket_js_structure($datetimes, $all_tickets);
1104
+
1105
+		$status_change_notice = LoaderFactory::getLoader()->getShared(
1106
+			'EventEspresso\core\domain\services\admin\notices\status_change\StatusChangeNotice'
1107
+		);
1108
+
1109
+		$main_template_args['status_change_notice'] = $status_change_notice->display(
1110
+			'__event-editor',
1111
+			'espresso-events'
1112
+		);
1113
+
1114
+		EEH_Template::display_template(
1115
+			PRICING_TEMPLATE_PATH . 'event_tickets_metabox_main.template.php',
1116
+			$main_template_args
1117
+		);
1118
+	}
1119
+
1120
+
1121
+	/**
1122
+	 * @param int|string  $datetime_row
1123
+	 * @param EE_Datetime $datetime
1124
+	 * @param array       $datetime_tickets
1125
+	 * @param array       $all_tickets
1126
+	 * @param bool        $default
1127
+	 * @param array       $all_datetimes
1128
+	 * @return string
1129
+	 * @throws DomainException
1130
+	 * @throws EE_Error
1131
+	 * @throws ReflectionException
1132
+	 */
1133
+	protected function _get_datetime_row(
1134
+		$datetime_row,
1135
+		EE_Datetime $datetime,
1136
+		array $datetime_tickets = [],
1137
+		array $all_tickets = [],
1138
+		bool $default = false,
1139
+		array $all_datetimes = []
1140
+	): string {
1141
+		return EEH_Template::display_template(
1142
+			PRICING_TEMPLATE_PATH . 'event_tickets_datetime_row_wrapper.template.php',
1143
+			[
1144
+				'dtt_edit_row'             => $this->_get_dtt_edit_row(
1145
+					$datetime_row,
1146
+					$datetime,
1147
+					$default,
1148
+					$all_datetimes
1149
+				),
1150
+				'dtt_attached_tickets_row' => $this->_get_dtt_attached_tickets_row(
1151
+					$datetime_row,
1152
+					$datetime,
1153
+					$datetime_tickets,
1154
+					$all_tickets,
1155
+					$default
1156
+				),
1157
+				'dtt_row'                  => $default ? 'DTTNUM' : $datetime_row,
1158
+			],
1159
+			true
1160
+		);
1161
+	}
1162
+
1163
+
1164
+	/**
1165
+	 * This method is used to generate a datetime fields  edit row.
1166
+	 * The same row is used to generate a row with valid DTT objects
1167
+	 * and the default row that is used as the skeleton by the js.
1168
+	 *
1169
+	 * @param int|string       $datetime_row  The row number for the row being generated.
1170
+	 * @param EE_Datetime|null $datetime
1171
+	 * @param bool             $default       Whether a default row is being generated or not.
1172
+	 * @param EE_Datetime[]    $all_datetimes This is the array of all datetimes used in the editor.
1173
+	 * @return string
1174
+	 * @throws EE_Error
1175
+	 * @throws ReflectionException
1176
+	 */
1177
+	protected function _get_dtt_edit_row(
1178
+		$datetime_row,
1179
+		?EE_Datetime $datetime,
1180
+		bool $default,
1181
+		array $all_datetimes
1182
+	): string {
1183
+		// if the incoming $datetime object is NOT an instance of EE_Datetime then force default to true.
1184
+		$default                     = ! $datetime instanceof EE_Datetime ? true : $default;
1185
+		$template_args               = [
1186
+			'dtt_row'              => $default ? 'DTTNUM' : $datetime_row,
1187
+			'event_datetimes_name' => $default ? 'DTTNAMEATTR' : 'edit_event_datetimes',
1188
+			'edit_dtt_expanded'    => '',
1189
+			'DTT_ID'               => $default ? '' : $datetime->ID(),
1190
+			'DTT_name'             => $default ? '' : $datetime->get_f('DTT_name'),
1191
+			'DTT_description'      => $default ? '' : $datetime->get_f('DTT_description'),
1192
+			'DTT_EVT_start'        => $default ? '' : $datetime->start_date($this->_date_time_format),
1193
+			'DTT_EVT_end'          => $default ? '' : $datetime->end_date($this->_date_time_format),
1194
+			'DTT_reg_limit'        => $default
1195
+				? ''
1196
+				: $datetime->get_pretty(
1197
+					'DTT_reg_limit',
1198
+					'input'
1199
+				),
1200
+			'DTT_order'            => $default ? 'DTTNUM' : $datetime_row,
1201
+			'dtt_sold'             => $default ? '0' : $datetime->get('DTT_sold'),
1202
+			'dtt_reserved'         => $default ? '0' : $datetime->reserved(),
1203
+			'clone_icon'           => ! empty($datetime) && $datetime->get('DTT_sold') > 0
1204
+				? ''
1205
+				: 'clone-icon ee-icon ee-icon-clone clickable',
1206
+			'trash_icon'           => ! empty($datetime) && $datetime->get('DTT_sold') > 0
1207
+				? 'dashicons dashicons-lock'
1208
+				: 'trash-icon dashicons dashicons-post-trash clickable',
1209
+			'reg_list_url'         => $default || ! $datetime->event() instanceof EE_Event
1210
+				? ''
1211
+				: EE_Admin_Page::add_query_args_and_nonce(
1212
+					[
1213
+						'event_id'    => $datetime->event()->ID(),
1214
+						'datetime_id' => $datetime->ID(),
1215
+						'use_filters' => true,
1216
+					],
1217
+					REG_ADMIN_URL
1218
+				),
1219
+		];
1220
+		$template_args['show_trash'] = count($all_datetimes) === 1
1221
+									   && $template_args['trash_icon'] !== 'dashicons dashicons-lock'
1222
+			? 'display:none'
1223
+			: '';
1224
+		// allow filtering of template args at this point.
1225
+		$template_args = apply_filters(
1226
+			'FHEE__espresso_events_Pricing_Hooks___get_dtt_edit_row__template_args',
1227
+			$template_args,
1228
+			$datetime_row,
1229
+			$datetime,
1230
+			$default,
1231
+			$all_datetimes,
1232
+			$this->_is_creating_event
1233
+		);
1234
+		return EEH_Template::display_template(
1235
+			PRICING_TEMPLATE_PATH . 'event_tickets_datetime_edit_row.template.php',
1236
+			$template_args,
1237
+			true
1238
+		);
1239
+	}
1240
+
1241
+
1242
+	/**
1243
+	 * @param int|string       $datetime_row
1244
+	 * @param EE_Datetime|null $datetime
1245
+	 * @param array            $datetime_tickets
1246
+	 * @param array            $all_tickets
1247
+	 * @param bool             $default
1248
+	 * @return string
1249
+	 * @throws DomainException
1250
+	 * @throws EE_Error
1251
+	 * @throws ReflectionException
1252
+	 */
1253
+	protected function _get_dtt_attached_tickets_row(
1254
+		$datetime_row,
1255
+		?EE_Datetime $datetime,
1256
+		array $datetime_tickets = [],
1257
+		array $all_tickets = [],
1258
+		bool $default = false
1259
+	): string {
1260
+		$template_args = [
1261
+			'dtt_row'                           => $default ? 'DTTNUM' : $datetime_row,
1262
+			'event_datetimes_name'              => $default ? 'DTTNAMEATTR' : 'edit_event_datetimes',
1263
+			'DTT_description'                   => $default ? '' : $datetime->get_f('DTT_description'),
1264
+			'datetime_tickets_list'             => $default ? '<li class="hidden"></li>' : '',
1265
+			'show_tickets_row'                  => 'display:none;',
1266
+			'add_new_datetime_ticket_help_link' => EEH_Template::get_help_tab_link(
1267
+				'add_new_ticket_via_datetime',
1268
+				$this->_adminpage_obj->page_slug,
1269
+				$this->_adminpage_obj->get_req_action()
1270
+			),
1271
+			// todo need to add this help info id to the Events_Admin_Page core file so we can access it here.
1272
+			'DTT_ID'                            => $default ? '' : $datetime->ID(),
1273
+		];
1274
+		// need to setup the list items (but only if this isn't a default skeleton setup)
1275
+		if (! $default) {
1276
+			$ticket_row = 1;
1277
+			foreach ($all_tickets as $ticket) {
1278
+				$template_args['datetime_tickets_list'] .= $this->_get_datetime_tickets_list_item(
1279
+					$datetime_row,
1280
+					$ticket_row,
1281
+					$datetime,
1282
+					$ticket,
1283
+					$datetime_tickets
1284
+				);
1285
+				$ticket_row++;
1286
+			}
1287
+		}
1288
+		// filter template args at this point
1289
+		$template_args = apply_filters(
1290
+			'FHEE__espresso_events_Pricing_Hooks___get_dtt_attached_ticket_row__template_args',
1291
+			$template_args,
1292
+			$datetime_row,
1293
+			$datetime,
1294
+			$datetime_tickets,
1295
+			$all_tickets,
1296
+			$default,
1297
+			$this->_is_creating_event
1298
+		);
1299
+		return EEH_Template::display_template(
1300
+			PRICING_TEMPLATE_PATH . 'event_tickets_datetime_attached_tickets_row.template.php',
1301
+			$template_args,
1302
+			true
1303
+		);
1304
+	}
1305
+
1306
+
1307
+	/**
1308
+	 * @param int|string       $datetime_row
1309
+	 * @param int|string       $ticket_row
1310
+	 * @param EE_Datetime|null $datetime
1311
+	 * @param EE_Ticket|null   $ticket
1312
+	 * @param array            $datetime_tickets
1313
+	 * @param bool             $default
1314
+	 * @return string
1315
+	 * @throws EE_Error
1316
+	 * @throws ReflectionException
1317
+	 */
1318
+	protected function _get_datetime_tickets_list_item(
1319
+		$datetime_row,
1320
+		$ticket_row,
1321
+		?EE_Datetime $datetime,
1322
+		?EE_Ticket $ticket,
1323
+		array $datetime_tickets = [],
1324
+		bool $default = false
1325
+	): string {
1326
+		$datetime_tickets = $datetime instanceof EE_Datetime && isset($datetime_tickets[ $datetime->ID() ])
1327
+			? $datetime_tickets[ $datetime->ID() ]
1328
+			: [];
1329
+		$display_row      = $ticket instanceof EE_Ticket ? $ticket->get('TKT_row') : 0;
1330
+		$no_ticket        = $default && empty($ticket);
1331
+		$template_args    = [
1332
+			'dtt_row'                 => $default
1333
+				? 'DTTNUM'
1334
+				: $datetime_row,
1335
+			'tkt_row'                 => $no_ticket
1336
+				? 'TICKETNUM'
1337
+				: $ticket_row,
1338
+			'datetime_ticket_checked' => in_array($display_row, $datetime_tickets, true)
1339
+				? ' checked'
1340
+				: '',
1341
+			'ticket_selected'         => in_array($display_row, $datetime_tickets, true)
1342
+				? ' ticket-selected'
1343
+				: '',
1344
+			'TKT_name'                => $no_ticket
1345
+				? 'TKTNAME'
1346
+				: $ticket->get('TKT_name'),
1347
+			'tkt_status_class'        => $no_ticket || $this->_is_creating_event
1348
+				? ' tkt-status-' . EE_Ticket::onsale
1349
+				: ' tkt-status-' . $ticket->ticket_status(),
1350
+		];
1351
+		// filter template args
1352
+		$template_args = apply_filters(
1353
+			'FHEE__espresso_events_Pricing_Hooks___get_datetime_tickets_list_item__template_args',
1354
+			$template_args,
1355
+			$datetime_row,
1356
+			$ticket_row,
1357
+			$datetime,
1358
+			$ticket,
1359
+			$datetime_tickets,
1360
+			$default,
1361
+			$this->_is_creating_event
1362
+		);
1363
+		return EEH_Template::display_template(
1364
+			PRICING_TEMPLATE_PATH . 'event_tickets_datetime_dtt_tickets_list.template.php',
1365
+			$template_args,
1366
+			true
1367
+		);
1368
+	}
1369
+
1370
+
1371
+	/**
1372
+	 * This generates the ticket row for tickets.
1373
+	 * This same method is used to generate both the actual rows and the js skeleton row
1374
+	 * (when default === true)
1375
+	 *
1376
+	 * @param int|string     $ticket_row       Represents the row number being generated.
1377
+	 * @param EE_Ticket|null $ticket
1378
+	 * @param EE_Datetime[]  $ticket_datetimes Either an array of all datetimes on all tickets indexed by each ticket
1379
+	 *                                         or empty for default
1380
+	 * @param EE_Datetime[]  $all_datetimes    All Datetimes on the event or empty for default.
1381
+	 * @param bool           $default          Whether default row being generated or not.
1382
+	 * @param EE_Ticket[]    $all_tickets      This is an array of all tickets attached to the event
1383
+	 *                                         (or empty in the case of defaults)
1384
+	 * @return string
1385
+	 * @throws InvalidArgumentException
1386
+	 * @throws InvalidInterfaceException
1387
+	 * @throws InvalidDataTypeException
1388
+	 * @throws DomainException
1389
+	 * @throws EE_Error
1390
+	 * @throws ReflectionException
1391
+	 */
1392
+	protected function _get_ticket_row(
1393
+		$ticket_row,
1394
+		?EE_Ticket $ticket,
1395
+		array $ticket_datetimes,
1396
+		array $all_datetimes,
1397
+		bool $default = false,
1398
+		array $all_tickets = []
1399
+	): string {
1400
+		// if $ticket is not an instance of EE_Ticket then force default to true.
1401
+		$default = ! $ticket instanceof EE_Ticket ? true : $default;
1402
+		$prices  = ! empty($ticket) && ! $default
1403
+			? $ticket->get_many_related(
1404
+				'Price',
1405
+				['default_where_conditions' => 'none', 'order_by' => ['PRC_order' => 'ASC']]
1406
+			)
1407
+			: [];
1408
+		// if there is only one price (which would be the base price)
1409
+		// or NO prices and this ticket is a default ticket,
1410
+		// let's just make sure there are no cached default prices on the object.
1411
+		// This is done by not including any query_params.
1412
+		if ($ticket instanceof EE_Ticket && $ticket->is_default() && (count($prices) === 1 || empty($prices))) {
1413
+			$prices = $ticket->prices();
1414
+		}
1415
+		// check if we're dealing with a default ticket in which case
1416
+		// we don't want any starting_ticket_datetime_row values set
1417
+		// (otherwise there won't be any new relationships created for tickets based off of the default ticket).
1418
+		// This will future proof in case there is ever any behaviour change between what the primary_key defaults to.
1419
+		$default_datetime = $default || ($ticket instanceof EE_Ticket && $ticket->is_default());
1420
+		$ticket_datetimes = $ticket instanceof EE_Ticket && isset($ticket_datetimes[ $ticket->ID() ])
1421
+			? $ticket_datetimes[ $ticket->ID() ]
1422
+			: [];
1423
+		$ticket_subtotal  = $default ? 0 : $ticket->get_ticket_subtotal();
1424
+		$base_price       = $default ? null : $ticket->base_price();
1425
+		$count_price_mods = EEM_Price::instance()->get_all_default_prices(true);
1426
+		// breaking out complicated condition for ticket_status
1427
+		if ($default) {
1428
+			$ticket_status_class = ' tkt-status-' . EE_Ticket::onsale;
1429
+		} else {
1430
+			$ticket_status_class = $ticket->is_default()
1431
+				? ' tkt-status-' . EE_Ticket::onsale
1432
+				: ' tkt-status-' . $ticket->ticket_status();
1433
+		}
1434
+		// breaking out complicated condition for TKT_taxable
1435
+		if ($default) {
1436
+			$TKT_taxable = '';
1437
+		} else {
1438
+			$TKT_taxable = $ticket->taxable()
1439
+				? 'checked'
1440
+				: '';
1441
+		}
1442
+		if ($default) {
1443
+			$TKT_status = EEH_Template::pretty_status(EE_Ticket::onsale, false, 'sentence');
1444
+		} elseif ($ticket->is_default()) {
1445
+			$TKT_status = EEH_Template::pretty_status(EE_Ticket::onsale, false, 'sentence');
1446
+		} else {
1447
+			$TKT_status = $ticket->ticket_status(true);
1448
+		}
1449
+		if ($default) {
1450
+			$TKT_min = '';
1451
+		} else {
1452
+			$TKT_min = $ticket->min();
1453
+			if ($TKT_min === -1 || $TKT_min === 0) {
1454
+				$TKT_min = '';
1455
+			}
1456
+		}
1457
+		$template_args                 = [
1458
+			'tkt_row'                       => $default ? 'TICKETNUM' : $ticket_row,
1459
+			'TKT_order'                     => $default ? 'TICKETNUM' : $ticket_row,
1460
+			// on initial page load this will always be the correct order.
1461
+			'tkt_status_class'              => $ticket_status_class,
1462
+			'display_edit_tkt_row'          => 'display:none;',
1463
+			'edit_tkt_expanded'             => '',
1464
+			'edit_tickets_name'             => $default ? 'TICKETNAMEATTR' : 'edit_tickets',
1465
+			'TKT_name'                      => $default ? '' : $ticket->get_f('TKT_name'),
1466
+			'TKT_start_date'                => $default
1467
+				? ''
1468
+				: $ticket->get_date('TKT_start_date', $this->_date_time_format),
1469
+			'TKT_end_date'                  => $default
1470
+				? ''
1471
+				: $ticket->get_date('TKT_end_date', $this->_date_time_format),
1472
+			'TKT_status'                    => $TKT_status,
1473
+			'TKT_price'                     => $default
1474
+				? ''
1475
+				: EEH_Template::format_currency(
1476
+					$ticket->get_ticket_total_with_taxes(),
1477
+					false,
1478
+					false
1479
+				),
1480
+			'TKT_price_code'                => EE_Registry::instance()->CFG->currency->code,
1481
+			'TKT_price_amount'              => $default ? 0 : $ticket_subtotal,
1482
+			'TKT_qty'                       => $default
1483
+				? ''
1484
+				: $ticket->get_pretty('TKT_qty', 'symbol'),
1485
+			'TKT_qty_for_input'             => $default
1486
+				? ''
1487
+				: $ticket->get_pretty('TKT_qty', 'input'),
1488
+			'TKT_uses'                      => $default
1489
+				? ''
1490
+				: $ticket->get_pretty('TKT_uses', 'input'),
1491
+			'TKT_min'                       => $TKT_min,
1492
+			'TKT_max'                       => $default
1493
+				? ''
1494
+				: $ticket->get_pretty('TKT_max', 'input'),
1495
+			'TKT_sold'                      => $default ? 0 : $ticket->tickets_sold(),
1496
+			'TKT_reserved'                  => $default ? 0 : $ticket->reserved(),
1497
+			'TKT_registrations'             => $default
1498
+				? 0
1499
+				: $ticket->count_registrations(
1500
+					[
1501
+						[
1502
+							'STS_ID' => [
1503
+								'!=',
1504
+								EEM_Registration::status_id_incomplete,
1505
+							],
1506
+						],
1507
+					]
1508
+				),
1509
+			'TKT_ID'                        => $default ? 0 : $ticket->ID(),
1510
+			'TKT_description'               => $default ? '' : $ticket->get_raw('TKT_description'),
1511
+			'TKT_is_default'                => $default ? 0 : $ticket->is_default(),
1512
+			'TKT_required'                  => $default ? 0 : $ticket->required(),
1513
+			'TKT_is_default_selector'       => '',
1514
+			'ticket_price_rows'             => '',
1515
+			'TKT_base_price'                => $default || ! $base_price instanceof EE_Price
1516
+				? ''
1517
+				: $base_price->get_pretty('PRC_amount', 'localized_float'),
1518
+			'TKT_base_price_ID'             => $default || ! $base_price instanceof EE_Price ? 0 : $base_price->ID(),
1519
+			'show_price_modifier'           => count($prices) > 1 || ($default && $count_price_mods > 0)
1520
+				? ''
1521
+				: 'display:none;',
1522
+			'show_price_mod_button'         => count($prices) > 1
1523
+											   || ($default && $count_price_mods > 0)
1524
+											   || (! $default && $ticket->deleted())
1525
+				? 'display:none;'
1526
+				: '',
1527
+			'total_price_rows'              => count($prices) > 1 ? count($prices) : 1,
1528
+			'ticket_datetimes_list'         => $default ? '<li class="hidden"></li>' : '',
1529
+			'starting_ticket_datetime_rows' => $default || $default_datetime ? '' : implode(',', $ticket_datetimes),
1530
+			'ticket_datetime_rows'          => $default ? '' : implode(',', $ticket_datetimes),
1531
+			'existing_ticket_price_ids'     => $default ? '' : implode(',', array_keys($prices)),
1532
+			'ticket_template_id'            => $default ? 0 : $ticket->get('TTM_ID'),
1533
+			'TKT_taxable'                   => $TKT_taxable,
1534
+			'display_subtotal'              => $ticket instanceof EE_Ticket && $ticket->taxable()
1535
+				? ''
1536
+				: 'display:none;',
1537
+			'price_currency_symbol'         => EE_Registry::instance()->CFG->currency->sign,
1538
+			'TKT_subtotal_amount_display'   => EEH_Template::format_currency(
1539
+				$ticket_subtotal,
1540
+				false,
1541
+				false
1542
+			),
1543
+			'TKT_subtotal_amount'           => $ticket_subtotal,
1544
+			'tax_rows'                      => $this->_get_tax_rows($ticket_row, $ticket),
1545
+			'disabled'                      => $ticket instanceof EE_Ticket && $ticket->deleted(),
1546
+			'ticket_archive_class'          => $ticket instanceof EE_Ticket && $ticket->deleted()
1547
+				? ' ticket-archived'
1548
+				: '',
1549
+			'trash_icon'                    => $ticket instanceof EE_Ticket
1550
+											   && $ticket->deleted()
1551
+											   && ! $ticket->is_permanently_deleteable()
1552
+				? 'dashicons dashicons-lock '
1553
+				: 'trash-icon dashicons dashicons-post-trash clickable',
1554
+			'clone_icon'                    => $ticket instanceof EE_Ticket && $ticket->deleted()
1555
+				? ''
1556
+				: 'clone-icon ee-icon ee-icon-clone clickable',
1557
+		];
1558
+		$template_args['trash_hidden'] = count($all_tickets) === 1
1559
+										 && $template_args['trash_icon'] !== 'dashicons dashicons-lock'
1560
+			? 'display:none'
1561
+			: '';
1562
+		// handle rows that should NOT be empty
1563
+		if (empty($template_args['TKT_start_date'])) {
1564
+			// if empty then the start date will be now.
1565
+			$template_args['TKT_start_date']   = date(
1566
+				$this->_date_time_format,
1567
+				current_time('timestamp')
1568
+			);
1569
+			$template_args['tkt_status_class'] = ' tkt-status-' . EE_Ticket::onsale;
1570
+		}
1571
+		if (empty($template_args['TKT_end_date'])) {
1572
+			// get the earliest datetime (if present);
1573
+			$earliest_datetime = $this->_adminpage_obj->get_cpt_model_obj()->ID() > 0
1574
+				? $this->_adminpage_obj->get_cpt_model_obj()->get_first_related(
1575
+					'Datetime',
1576
+					['order_by' => ['DTT_EVT_start' => 'ASC']]
1577
+				)
1578
+				: null;
1579
+			if (! empty($earliest_datetime)) {
1580
+				$template_args['TKT_end_date'] = $earliest_datetime->get_datetime(
1581
+					'DTT_EVT_start',
1582
+					$this->_date_time_format
1583
+				);
1584
+			} else {
1585
+				// default so let's just use what's been set for the default date-time which is 30 days from now.
1586
+				$template_args['TKT_end_date'] = date(
1587
+					$this->_date_time_format,
1588
+					mktime(
1589
+						24,
1590
+						0,
1591
+						0,
1592
+						date('m'),
1593
+						date('d') + 29,
1594
+						date('Y')
1595
+					)
1596
+				);
1597
+			}
1598
+			$template_args['tkt_status_class'] = ' tkt-status-' . EE_Ticket::onsale;
1599
+		}
1600
+		// generate ticket_datetime items
1601
+		if (! $default) {
1602
+			$datetime_row = 1;
1603
+			foreach ($all_datetimes as $datetime) {
1604
+				$template_args['ticket_datetimes_list'] .= $this->_get_ticket_datetime_list_item(
1605
+					$datetime_row,
1606
+					$ticket_row,
1607
+					$datetime,
1608
+					$ticket,
1609
+					$ticket_datetimes,
1610
+					$default
1611
+				);
1612
+				$datetime_row++;
1613
+			}
1614
+		}
1615
+		$price_row = 1;
1616
+		foreach ($prices as $price) {
1617
+			if (! $price instanceof EE_Price) {
1618
+				continue;
1619
+			}
1620
+			if ($price->is_base_price()) {
1621
+				$price_row++;
1622
+				continue;
1623
+			}
1624
+
1625
+			$show_trash  = ! ((count($prices) > 1 && $price_row === 1) || count($prices) === 1);
1626
+			$show_create = ! (count($prices) > 1 && count($prices) !== $price_row);
1627
+
1628
+			$template_args['ticket_price_rows'] .= $this->_get_ticket_price_row(
1629
+				$ticket_row,
1630
+				$price_row,
1631
+				$price,
1632
+				$default,
1633
+				$ticket,
1634
+				$show_trash,
1635
+				$show_create
1636
+			);
1637
+			$price_row++;
1638
+		}
1639
+		// filter $template_args
1640
+		$template_args = apply_filters(
1641
+			'FHEE__espresso_events_Pricing_Hooks___get_ticket_row__template_args',
1642
+			$template_args,
1643
+			$ticket_row,
1644
+			$ticket,
1645
+			$ticket_datetimes,
1646
+			$all_datetimes,
1647
+			$default,
1648
+			$all_tickets,
1649
+			$this->_is_creating_event
1650
+		);
1651
+		return EEH_Template::display_template(
1652
+			PRICING_TEMPLATE_PATH . 'event_tickets_datetime_ticket_row.template.php',
1653
+			$template_args,
1654
+			true
1655
+		);
1656
+	}
1657
+
1658
+
1659
+	/**
1660
+	 * @param int|string     $ticket_row
1661
+	 * @param EE_Ticket|null $ticket
1662
+	 * @return string
1663
+	 * @throws DomainException
1664
+	 * @throws EE_Error
1665
+	 * @throws ReflectionException
1666
+	 */
1667
+	protected function _get_tax_rows($ticket_row, ?EE_Ticket $ticket): string
1668
+	{
1669
+		$tax_rows = '';
1670
+		/** @var EE_Price[] $taxes */
1671
+		$taxes = empty($ticket) ? EE_Taxes::get_taxes_for_admin() : $ticket->get_ticket_taxes_for_admin();
1672
+		foreach ($taxes as $tax) {
1673
+			$tax_added     = $this->_get_tax_added($tax, $ticket);
1674
+			$template_args = [
1675
+				'display_tax'       => ! empty($ticket) && $ticket->get('TKT_taxable')
1676
+					? ''
1677
+					: 'display:none;',
1678
+				'tax_id'            => $tax->ID(),
1679
+				'tkt_row'           => $ticket_row,
1680
+				'tax_label'         => $tax->get('PRC_name'),
1681
+				'tax_added'         => $tax_added,
1682
+				'tax_added_display' => EEH_Template::format_currency($tax_added, false, false),
1683
+				'tax_amount'        => $tax->get('PRC_amount'),
1684
+			];
1685
+			$template_args = apply_filters(
1686
+				'FHEE__espresso_events_Pricing_Hooks___get_tax_rows__template_args',
1687
+				$template_args,
1688
+				$ticket_row,
1689
+				$ticket,
1690
+				$this->_is_creating_event
1691
+			);
1692
+			$tax_rows      .= EEH_Template::display_template(
1693
+				PRICING_TEMPLATE_PATH . 'event_tickets_datetime_ticket_tax_row.template.php',
1694
+				$template_args,
1695
+				true
1696
+			);
1697
+		}
1698
+		return $tax_rows;
1699
+	}
1700
+
1701
+
1702
+	/**
1703
+	 * @param EE_Price       $tax
1704
+	 * @param EE_Ticket|null $ticket
1705
+	 * @return float|int
1706
+	 * @throws EE_Error
1707
+	 * @throws ReflectionException
1708
+	 */
1709
+	protected function _get_tax_added(EE_Price $tax, ?EE_Ticket $ticket)
1710
+	{
1711
+		$subtotal = empty($ticket) ? 0 : $ticket->get_ticket_subtotal();
1712
+		return $subtotal * $tax->get('PRC_amount') / 100;
1713
+	}
1714
+
1715
+
1716
+	/**
1717
+	 * @param int|string     $ticket_row
1718
+	 * @param int|string     $price_row
1719
+	 * @param EE_Price|null  $price
1720
+	 * @param bool           $default
1721
+	 * @param EE_Ticket|null $ticket
1722
+	 * @param bool           $show_trash
1723
+	 * @param bool           $show_create
1724
+	 * @return string
1725
+	 * @throws InvalidArgumentException
1726
+	 * @throws InvalidInterfaceException
1727
+	 * @throws InvalidDataTypeException
1728
+	 * @throws DomainException
1729
+	 * @throws EE_Error
1730
+	 * @throws ReflectionException
1731
+	 */
1732
+	protected function _get_ticket_price_row(
1733
+		$ticket_row,
1734
+		$price_row,
1735
+		?EE_Price $price,
1736
+		bool $default,
1737
+		?EE_Ticket $ticket,
1738
+		bool $show_trash = true,
1739
+		bool $show_create = true
1740
+	): string {
1741
+		$send_disabled = ! empty($ticket) && $ticket->get('TKT_deleted');
1742
+		$template_args = [
1743
+			'tkt_row'               => $default && empty($ticket)
1744
+				? 'TICKETNUM'
1745
+				: $ticket_row,
1746
+			'PRC_order'             => $default && empty($price)
1747
+				? 'PRICENUM'
1748
+				: $price_row,
1749
+			'edit_prices_name'      => $default && empty($price)
1750
+				? 'PRICENAMEATTR'
1751
+				: 'edit_prices',
1752
+			'price_type_selector'   => $default && empty($price)
1753
+				? $this->_get_base_price_template($ticket_row, $price_row, $price, true)
1754
+				: $this->_get_price_type_selector(
1755
+					$ticket_row,
1756
+					$price_row,
1757
+					$price,
1758
+					$default,
1759
+					$send_disabled
1760
+				),
1761
+			'PRC_ID'                => $default && empty($price)
1762
+				? 0
1763
+				: $price->ID(),
1764
+			'PRC_is_default'        => $default && empty($price)
1765
+				? 0
1766
+				: $price->get('PRC_is_default'),
1767
+			'PRC_name'              => $default && empty($price)
1768
+				? ''
1769
+				: $price->get('PRC_name'),
1770
+			'price_currency_symbol' => EE_Registry::instance()->CFG->currency->sign,
1771
+			'show_plus_or_minus'    => $default && empty($price)
1772
+				? ''
1773
+				: 'display:none;',
1774
+			'show_plus'             => ($default && empty($price)) || ($price->is_discount() || $price->is_base_price())
1775
+				? 'display:none;'
1776
+				: '',
1777
+			'show_minus'            => ($default && empty($price)) || ! $price->is_discount()
1778
+				? 'display:none;'
1779
+				: '',
1780
+			'show_currency_symbol'  => ($default && empty($price)) || $price->is_percent()
1781
+				? 'display:none'
1782
+				: '',
1783
+			'PRC_amount'            => $default && empty($price)
1784
+				? 0
1785
+				: $price->get_pretty('PRC_amount', 'localized_float'),
1786
+			'show_percentage'       => ($default && empty($price)) || ! $price->is_percent()
1787
+				? 'display:none;'
1788
+				: '',
1789
+			'show_trash_icon'       => $show_trash
1790
+				? ''
1791
+				: ' style="display:none;"',
1792
+			'show_create_button'    => $show_create
1793
+				? ''
1794
+				: ' style="display:none;"',
1795
+			'PRC_desc'              => $default && empty($price)
1796
+				? ''
1797
+				: $price->get('PRC_desc'),
1798
+			'disabled'              => ! empty($ticket) && $ticket->get('TKT_deleted'),
1799
+		];
1800
+		$template_args = apply_filters(
1801
+			'FHEE__espresso_events_Pricing_Hooks___get_ticket_price_row__template_args',
1802
+			$template_args,
1803
+			$ticket_row,
1804
+			$price_row,
1805
+			$price,
1806
+			$default,
1807
+			$ticket,
1808
+			$show_trash,
1809
+			$show_create,
1810
+			$this->_is_creating_event
1811
+		);
1812
+		return EEH_Template::display_template(
1813
+			PRICING_TEMPLATE_PATH . 'event_tickets_datetime_ticket_price_row.template.php',
1814
+			$template_args,
1815
+			true
1816
+		);
1817
+	}
1818
+
1819
+
1820
+	/**
1821
+	 * @param int|string    $ticket_row
1822
+	 * @param int|string    $price_row
1823
+	 * @param EE_Price|null $price
1824
+	 * @param bool          $default
1825
+	 * @param bool          $disabled
1826
+	 * @return string
1827
+	 * @throws ReflectionException
1828
+	 * @throws InvalidArgumentException
1829
+	 * @throws InvalidInterfaceException
1830
+	 * @throws InvalidDataTypeException
1831
+	 * @throws DomainException
1832
+	 * @throws EE_Error
1833
+	 */
1834
+	protected function _get_price_type_selector(
1835
+		$ticket_row,
1836
+		$price_row,
1837
+		?EE_Price $price,
1838
+		bool $default,
1839
+		bool $disabled = false
1840
+	): string {
1841
+		if ($price->is_base_price()) {
1842
+			return $this->_get_base_price_template(
1843
+				$ticket_row,
1844
+				$price_row,
1845
+				$price,
1846
+				$default
1847
+			);
1848
+		}
1849
+		return $this->_get_price_modifier_template(
1850
+			$ticket_row,
1851
+			$price_row,
1852
+			$price,
1853
+			$default,
1854
+			$disabled
1855
+		);
1856
+	}
1857
+
1858
+
1859
+	/**
1860
+	 * @param int|string    $ticket_row
1861
+	 * @param int|string    $price_row
1862
+	 * @param EE_Price|null $price
1863
+	 * @param bool          $default
1864
+	 * @return string
1865
+	 * @throws DomainException
1866
+	 * @throws EE_Error
1867
+	 * @throws ReflectionException
1868
+	 */
1869
+	protected function _get_base_price_template(
1870
+		$ticket_row,
1871
+		$price_row,
1872
+		?EE_Price $price,
1873
+		bool $default
1874
+	): string {
1875
+		$template_args = [
1876
+			'tkt_row'                   => $default ? 'TICKETNUM' : $ticket_row,
1877
+			'PRC_order'                 => $default && empty($price) ? 'PRICENUM' : $price_row,
1878
+			'PRT_ID'                    => $default && empty($price) ? 1 : $price->get('PRT_ID'),
1879
+			'PRT_name'                  => esc_html__('Price', 'event_espresso'),
1880
+			'price_selected_operator'   => '+',
1881
+			'price_selected_is_percent' => 0,
1882
+		];
1883
+		$template_args = apply_filters(
1884
+			'FHEE__espresso_events_Pricing_Hooks___get_base_price_template__template_args',
1885
+			$template_args,
1886
+			$ticket_row,
1887
+			$price_row,
1888
+			$price,
1889
+			$default,
1890
+			$this->_is_creating_event
1891
+		);
1892
+		return EEH_Template::display_template(
1893
+			PRICING_TEMPLATE_PATH . 'event_tickets_datetime_price_type_base.template.php',
1894
+			$template_args,
1895
+			true
1896
+		);
1897
+	}
1898
+
1899
+
1900
+	/**
1901
+	 * @param int|string    $ticket_row
1902
+	 * @param int|string    $price_row
1903
+	 * @param EE_Price|null $price
1904
+	 * @param bool          $default
1905
+	 * @param bool          $disabled
1906
+	 * @return string
1907
+	 * @throws ReflectionException
1908
+	 * @throws InvalidArgumentException
1909
+	 * @throws InvalidInterfaceException
1910
+	 * @throws InvalidDataTypeException
1911
+	 * @throws DomainException
1912
+	 * @throws EE_Error
1913
+	 */
1914
+	protected function _get_price_modifier_template(
1915
+		$ticket_row,
1916
+		$price_row,
1917
+		?EE_Price $price,
1918
+		bool $default,
1919
+		bool $disabled = false
1920
+	): string {
1921
+		$select_name = $default && ! $price instanceof EE_Price
1922
+			? 'edit_prices[TICKETNUM][PRICENUM][PRT_ID]'
1923
+			: 'edit_prices[' . esc_attr($ticket_row) . '][' . esc_attr($price_row) . '][PRT_ID]';
1924
+
1925
+		$price_type_model       = EEM_Price_Type::instance();
1926
+		$price_types            = $price_type_model->get_all(
1927
+			[
1928
+				[
1929
+					'OR' => [
1930
+						'PBT_ID'  => '2',
1931
+						'PBT_ID*' => '3',
1932
+					],
1933
+				],
1934
+			]
1935
+		);
1936
+		$all_price_types        = $default && ! $price instanceof EE_Price
1937
+			? [esc_html__('Select Modifier', 'event_espresso')]
1938
+			: [];
1939
+		$selected_price_type_id = $default && ! $price instanceof EE_Price ? 0 : $price->type();
1940
+		$price_option_spans     = '';
1941
+		// setup price types for selector
1942
+		foreach ($price_types as $price_type) {
1943
+			if (! $price_type instanceof EE_Price_Type) {
1944
+				continue;
1945
+			}
1946
+			$all_price_types[ $price_type->ID() ] = $price_type->get('PRT_name');
1947
+			// while we're in the loop let's setup the option spans used by js
1948
+			$span_args          = [
1949
+				'PRT_ID'         => $price_type->ID(),
1950
+				'PRT_operator'   => $price_type->is_discount() ? '-' : '+',
1951
+				'PRT_is_percent' => $price_type->get('PRT_is_percent') ? 1 : 0,
1952
+			];
1953
+			$price_option_spans .= EEH_Template::display_template(
1954
+				PRICING_TEMPLATE_PATH . 'event_tickets_datetime_price_option_span.template.php',
1955
+				$span_args,
1956
+				true
1957
+			);
1958
+		}
1959
+
1960
+		$select_name = $disabled
1961
+			? 'archive_price[' . $ticket_row . '][' . $price_row . '][PRT_ID]'
1962
+			: $select_name;
1963
+
1964
+		$select_input = new EE_Select_Input(
1965
+			$all_price_types,
1966
+			[
1967
+				'default'               => $selected_price_type_id,
1968
+				'html_name'             => $select_name,
1969
+				'html_class'            => 'edit-price-PRT_ID',
1970
+				'other_html_attributes' => $disabled ? 'style="width:auto;" disabled' : 'style="width:auto;"',
1971
+			]
1972
+		);
1973
+
1974
+		$price_selected_operator   = $price instanceof EE_Price && $price->is_discount() ? '-' : '+';
1975
+		$price_selected_operator   = $default && ! $price instanceof EE_Price ? '' : $price_selected_operator;
1976
+		$price_selected_is_percent = $price instanceof EE_Price && $price->is_percent() ? 1 : 0;
1977
+		$price_selected_is_percent = $default && ! $price instanceof EE_Price ? '' : $price_selected_is_percent;
1978
+		$template_args             = [
1979
+			'tkt_row'                   => $default ? 'TICKETNUM' : $ticket_row,
1980
+			'PRC_order'                 => $default && ! $price instanceof EE_Price ? 'PRICENUM' : $price_row,
1981
+			'price_modifier_selector'   => $select_input->get_html_for_input(),
1982
+			'main_name'                 => $select_name,
1983
+			'selected_price_type_id'    => $selected_price_type_id,
1984
+			'price_option_spans'        => $price_option_spans,
1985
+			'price_selected_operator'   => $price_selected_operator,
1986
+			'price_selected_is_percent' => $price_selected_is_percent,
1987
+			'disabled'                  => $disabled,
1988
+		];
1989
+		$template_args             = apply_filters(
1990
+			'FHEE__espresso_events_Pricing_Hooks___get_price_modifier_template__template_args',
1991
+			$template_args,
1992
+			$ticket_row,
1993
+			$price_row,
1994
+			$price,
1995
+			$default,
1996
+			$disabled,
1997
+			$this->_is_creating_event
1998
+		);
1999
+		return EEH_Template::display_template(
2000
+			PRICING_TEMPLATE_PATH . 'event_tickets_datetime_price_modifier_selector.template.php',
2001
+			$template_args,
2002
+			true
2003
+		);
2004
+	}
2005
+
2006
+
2007
+	/**
2008
+	 * @param int|string       $datetime_row
2009
+	 * @param int|string       $ticket_row
2010
+	 * @param EE_Datetime|null $datetime
2011
+	 * @param EE_Ticket|null   $ticket
2012
+	 * @param array            $ticket_datetimes
2013
+	 * @param bool             $default
2014
+	 * @return string
2015
+	 * @throws DomainException
2016
+	 * @throws EE_Error
2017
+	 * @throws ReflectionException
2018
+	 */
2019
+	protected function _get_ticket_datetime_list_item(
2020
+		$datetime_row,
2021
+		$ticket_row,
2022
+		?EE_Datetime $datetime,
2023
+		?EE_Ticket $ticket,
2024
+		array $ticket_datetimes = [],
2025
+		bool $default = false
2026
+	): string {
2027
+		$ticket_datetimes = $ticket instanceof EE_Ticket && isset($ticket_datetimes[ $ticket->ID() ])
2028
+			? $ticket_datetimes[ $ticket->ID() ]
2029
+			: [];
2030
+		$template_args    = [
2031
+			'dtt_row'                  => $default && ! $datetime instanceof EE_Datetime
2032
+				? 'DTTNUM'
2033
+				: $datetime_row,
2034
+			'tkt_row'                  => $default
2035
+				? 'TICKETNUM'
2036
+				: $ticket_row,
2037
+			'ticket_datetime_selected' => in_array($datetime_row, $ticket_datetimes, true)
2038
+				? ' ticket-selected'
2039
+				: '',
2040
+			'ticket_datetime_checked'  => in_array($datetime_row, $ticket_datetimes, true)
2041
+				? ' checked'
2042
+				: '',
2043
+			'DTT_name'                 => $default && empty($datetime)
2044
+				? 'DTTNAME'
2045
+				: $datetime->get_dtt_display_name(true),
2046
+			'tkt_status_class'         => '',
2047
+		];
2048
+		$template_args    = apply_filters(
2049
+			'FHEE__espresso_events_Pricing_Hooks___get_ticket_datetime_list_item__template_args',
2050
+			$template_args,
2051
+			$datetime_row,
2052
+			$ticket_row,
2053
+			$datetime,
2054
+			$ticket,
2055
+			$ticket_datetimes,
2056
+			$default,
2057
+			$this->_is_creating_event
2058
+		);
2059
+		return EEH_Template::display_template(
2060
+			PRICING_TEMPLATE_PATH . 'event_tickets_datetime_ticket_datetimes_list_item.template.php',
2061
+			$template_args,
2062
+			true
2063
+		);
2064
+	}
2065
+
2066
+
2067
+	/**
2068
+	 * @param array $all_datetimes
2069
+	 * @param array $all_tickets
2070
+	 * @return string
2071
+	 * @throws ReflectionException
2072
+	 * @throws InvalidArgumentException
2073
+	 * @throws InvalidInterfaceException
2074
+	 * @throws InvalidDataTypeException
2075
+	 * @throws DomainException
2076
+	 * @throws EE_Error
2077
+	 */
2078
+	protected function _get_ticket_js_structure(array $all_datetimes = [], array $all_tickets = []): string
2079
+	{
2080
+		$template_args = [
2081
+			'default_datetime_edit_row' => $this->_get_dtt_edit_row(
2082
+				'DTTNUM',
2083
+				null,
2084
+				true,
2085
+				$all_datetimes
2086
+			),
2087
+			'default_ticket_row'        => $this->_get_ticket_row(
2088
+				'TICKETNUM',
2089
+				null,
2090
+				[],
2091
+				[],
2092
+				true
2093
+			),
2094
+			'default_price_row'         => $this->_get_ticket_price_row(
2095
+				'TICKETNUM',
2096
+				'PRICENUM',
2097
+				null,
2098
+				true,
2099
+				null
2100
+			),
2101
+
2102
+			'default_price_rows'                       => '',
2103
+			'default_base_price_amount'                => 0,
2104
+			'default_base_price_name'                  => '',
2105
+			'default_base_price_description'           => '',
2106
+			'default_price_modifier_selector_row'      => $this->_get_price_modifier_template(
2107
+				'TICKETNUM',
2108
+				'PRICENUM',
2109
+				null,
2110
+				true
2111
+			),
2112
+			'default_available_tickets_for_datetime'   => $this->_get_dtt_attached_tickets_row(
2113
+				'DTTNUM',
2114
+				null,
2115
+				[],
2116
+				[],
2117
+				true
2118
+			),
2119
+			'existing_available_datetime_tickets_list' => '',
2120
+			'existing_available_ticket_datetimes_list' => '',
2121
+			'new_available_datetime_ticket_list_item'  => $this->_get_datetime_tickets_list_item(
2122
+				'DTTNUM',
2123
+				'TICKETNUM',
2124
+				null,
2125
+				null,
2126
+				[],
2127
+				true
2128
+			),
2129
+			'new_available_ticket_datetime_list_item'  => $this->_get_ticket_datetime_list_item(
2130
+				'DTTNUM',
2131
+				'TICKETNUM',
2132
+				null,
2133
+				null,
2134
+				[],
2135
+				true
2136
+			),
2137
+		];
2138
+		$ticket_row    = 1;
2139
+		foreach ($all_tickets as $ticket) {
2140
+			$template_args['existing_available_datetime_tickets_list'] .= $this->_get_datetime_tickets_list_item(
2141
+				'DTTNUM',
2142
+				$ticket_row,
2143
+				null,
2144
+				$ticket,
2145
+				[],
2146
+				true
2147
+			);
2148
+			$ticket_row++;
2149
+		}
2150
+		$datetime_row = 1;
2151
+		foreach ($all_datetimes as $datetime) {
2152
+			$template_args['existing_available_ticket_datetimes_list'] .= $this->_get_ticket_datetime_list_item(
2153
+				$datetime_row,
2154
+				'TICKETNUM',
2155
+				$datetime,
2156
+				null,
2157
+				[],
2158
+				true
2159
+			);
2160
+			$datetime_row++;
2161
+		}
2162
+		$price_model    = EEM_Price::instance();
2163
+		$default_prices = $price_model->get_all_default_prices();
2164
+		$price_row      = 1;
2165
+		foreach ($default_prices as $price) {
2166
+			if (! $price instanceof EE_Price) {
2167
+				continue;
2168
+			}
2169
+			if ($price->is_base_price()) {
2170
+				$template_args['default_base_price_amount']      = $price->get_pretty(
2171
+					'PRC_amount',
2172
+					'localized_float'
2173
+				);
2174
+				$template_args['default_base_price_name']        = $price->get('PRC_name');
2175
+				$template_args['default_base_price_description'] = $price->get('PRC_desc');
2176
+				$price_row++;
2177
+				continue;
2178
+			}
2179
+
2180
+			$show_trash  = ! ((count($default_prices) > 1 && $price_row === 1) || count($default_prices) === 1);
2181
+			$show_create = ! (count($default_prices) > 1 && count($default_prices) !== $price_row);
2182
+
2183
+			$template_args['default_price_rows'] .= $this->_get_ticket_price_row(
2184
+				'TICKETNUM',
2185
+				$price_row,
2186
+				$price,
2187
+				true,
2188
+				null,
2189
+				$show_trash,
2190
+				$show_create
2191
+			);
2192
+			$price_row++;
2193
+		}
2194
+		$template_args = apply_filters(
2195
+			'FHEE__espresso_events_Pricing_Hooks___get_ticket_js_structure__template_args',
2196
+			$template_args,
2197
+			$all_datetimes,
2198
+			$all_tickets,
2199
+			$this->_is_creating_event
2200
+		);
2201
+		return EEH_Template::display_template(
2202
+			PRICING_TEMPLATE_PATH . 'event_tickets_datetime_ticket_js_structure.template.php',
2203
+			$template_args,
2204
+			true
2205
+		);
2206
+	}
2207 2207
 }
Please login to merge, or discard this patch.
admin_pages/general_settings/General_Settings_Admin_Page.core.php 2 patches
Indentation   +1430 added lines, -1430 removed lines patch added patch discarded remove patch
@@ -19,1447 +19,1447 @@
 block discarded – undo
19 19
  */
20 20
 class General_Settings_Admin_Page extends EE_Admin_Page
21 21
 {
22
-    /**
23
-     * @var EE_Core_Config
24
-     */
25
-    public $core_config;
26
-
27
-
28
-    /**
29
-     * Initialize basic properties.
30
-     */
31
-    protected function _init_page_props()
32
-    {
33
-        $this->page_slug        = GEN_SET_PG_SLUG;
34
-        $this->page_label       = GEN_SET_LABEL;
35
-        $this->_admin_base_url  = GEN_SET_ADMIN_URL;
36
-        $this->_admin_base_path = GEN_SET_ADMIN;
37
-
38
-        $this->core_config = EE_Registry::instance()->CFG->core;
39
-    }
40
-
41
-
42
-    /**
43
-     * Set ajax hooks
44
-     */
45
-    protected function _ajax_hooks()
46
-    {
47
-        add_action('wp_ajax_espresso_display_country_settings', [$this, 'display_country_settings']);
48
-        add_action('wp_ajax_espresso_display_country_states', [$this, 'display_country_states']);
49
-        add_action('wp_ajax_espresso_delete_state', [$this, 'delete_state'], 10, 3);
50
-        add_action('wp_ajax_espresso_add_new_state', [$this, 'add_new_state']);
51
-    }
52
-
53
-
54
-    /**
55
-     * More page properties initialization.
56
-     */
57
-    protected function _define_page_props()
58
-    {
59
-        $this->_admin_page_title = GEN_SET_LABEL;
60
-        $this->_labels           = ['publishbox' => esc_html__('Update Settings', 'event_espresso')];
61
-    }
62
-
63
-
64
-    /**
65
-     * Set page routes property.
66
-     */
67
-    protected function _set_page_routes()
68
-    {
69
-        $this->_page_routes = [
70
-            'critical_pages'                => [
71
-                'func'       => [$this, '_espresso_page_settings'],
72
-                'capability' => 'manage_options',
73
-            ],
74
-            'update_espresso_page_settings' => [
75
-                'func'       => [$this, '_update_espresso_page_settings'],
76
-                'capability' => 'manage_options',
77
-                'noheader'   => true,
78
-            ],
79
-            'default'                       => [
80
-                'func'       => [$this, '_your_organization_settings'],
81
-                'capability' => 'manage_options',
82
-            ],
83
-
84
-            'update_your_organization_settings' => [
85
-                'func'       => [$this, '_update_your_organization_settings'],
86
-                'capability' => 'manage_options',
87
-                'noheader'   => true,
88
-            ],
89
-
90
-            'admin_option_settings' => [
91
-                'func'       => [$this, '_admin_option_settings'],
92
-                'capability' => 'manage_options',
93
-            ],
94
-
95
-            'update_admin_option_settings' => [
96
-                'func'       => [$this, '_update_admin_option_settings'],
97
-                'capability' => 'manage_options',
98
-                'noheader'   => true,
99
-            ],
100
-
101
-            'country_settings' => [
102
-                'func'       => [$this, '_country_settings'],
103
-                'capability' => 'manage_options',
104
-            ],
105
-
106
-            'update_country_settings' => [
107
-                'func'       => [$this, '_update_country_settings'],
108
-                'capability' => 'manage_options',
109
-                'noheader'   => true,
110
-            ],
111
-
112
-            'display_country_settings' => [
113
-                'func'       => [$this, 'display_country_settings'],
114
-                'capability' => 'manage_options',
115
-                'noheader'   => true,
116
-            ],
117
-
118
-            'add_new_state' => [
119
-                'func'       => [$this, 'add_new_state'],
120
-                'capability' => 'manage_options',
121
-                'noheader'   => true,
122
-            ],
123
-
124
-            'delete_state'            => [
125
-                'func'       => [$this, 'delete_state'],
126
-                'capability' => 'manage_options',
127
-                'noheader'   => true,
128
-            ],
129
-            'privacy_settings'        => [
130
-                'func'       => [$this, 'privacySettings'],
131
-                'capability' => 'manage_options',
132
-            ],
133
-            'update_privacy_settings' => [
134
-                'func'               => [$this, 'updatePrivacySettings'],
135
-                'capability'         => 'manage_options',
136
-                'noheader'           => true,
137
-                'headers_sent_route' => 'privacy_settings',
138
-            ],
139
-        ];
140
-    }
141
-
142
-
143
-    /**
144
-     * Set page configuration property
145
-     */
146
-    protected function _set_page_config()
147
-    {
148
-        $this->_page_config = [
149
-            'critical_pages'        => [
150
-                'nav'           => [
151
-                    'label' => esc_html__('Critical Pages', 'event_espresso'),
152
-                    'icon' => 'dashicons-warning',
153
-                    'order' => 50,
154
-                ],
155
-                'metaboxes'     => array_merge($this->_default_espresso_metaboxes, ['_publish_post_box']),
156
-                'help_tabs'     => [
157
-                    'general_settings_critical_pages_help_tab' => [
158
-                        'title'    => esc_html__('Critical Pages', 'event_espresso'),
159
-                        'filename' => 'general_settings_critical_pages',
160
-                    ],
161
-                ],
162
-                'require_nonce' => false,
163
-            ],
164
-            'default'               => [
165
-                'nav'           => [
166
-                    'label' => esc_html__('Your Organization', 'event_espresso'),
167
-                    'icon' => 'dashicons-admin-home',
168
-                    'order' => 20,
169
-                ],
170
-                'help_tabs'     => [
171
-                    'general_settings_your_organization_help_tab' => [
172
-                        'title'    => esc_html__('Your Organization', 'event_espresso'),
173
-                        'filename' => 'general_settings_your_organization',
174
-                    ],
175
-                ],
176
-                'metaboxes'     => array_merge($this->_default_espresso_metaboxes, ['_publish_post_box']),
177
-                'require_nonce' => false,
178
-            ],
179
-            'admin_option_settings' => [
180
-                'nav'           => [
181
-                    'label' => esc_html__('Admin Options', 'event_espresso'),
182
-                    'icon' => 'dashicons-admin-settings',
183
-                    'order' => 60,
184
-                ],
185
-                'metaboxes'     => array_merge($this->_default_espresso_metaboxes, ['_publish_post_box']),
186
-                'help_tabs'     => [
187
-                    'general_settings_admin_options_help_tab' => [
188
-                        'title'    => esc_html__('Admin Options', 'event_espresso'),
189
-                        'filename' => 'general_settings_admin_options',
190
-                    ],
191
-                ],
192
-                'require_nonce' => false,
193
-            ],
194
-            'country_settings'      => [
195
-                'nav'           => [
196
-                    'label' => esc_html__('Countries', 'event_espresso'),
197
-                    'icon' => 'dashicons-admin-site',
198
-                    'order' => 70,
199
-                ],
200
-                'help_tabs'     => [
201
-                    'general_settings_countries_help_tab' => [
202
-                        'title'    => esc_html__('Countries', 'event_espresso'),
203
-                        'filename' => 'general_settings_countries',
204
-                    ],
205
-                ],
206
-                'require_nonce' => false,
207
-            ],
208
-            'privacy_settings'      => [
209
-                'nav'           => [
210
-                    'label' => esc_html__('Privacy', 'event_espresso'),
211
-                    'icon' => 'dashicons-privacy',
212
-                    'order' => 80,
213
-                ],
214
-                'metaboxes'     => array_merge($this->_default_espresso_metaboxes, ['_publish_post_box']),
215
-                'require_nonce' => false,
216
-            ],
217
-        ];
218
-    }
219
-
220
-
221
-    protected function _add_screen_options()
222
-    {
223
-    }
224
-
225
-
226
-    protected function _add_feature_pointers()
227
-    {
228
-    }
229
-
230
-
231
-    /**
232
-     * Enqueue global scripts and styles for all routes in the General Settings Admin Pages.
233
-     */
234
-    public function load_scripts_styles()
235
-    {
236
-        // styles
237
-        wp_enqueue_style('espresso-ui-theme');
238
-        // scripts
239
-        wp_enqueue_script('ee_admin_js');
240
-    }
241
-
242
-
243
-    /**
244
-     * Execute logic running on `admin_init`
245
-     */
246
-    public function admin_init()
247
-    {
248
-        EE_Registry::$i18n_js_strings['invalid_server_response'] = wp_strip_all_tags(
249
-            esc_html__(
250
-                'An error occurred! Your request may have been processed, but a valid response from the server was not received. Please refresh the page and try again.',
251
-                'event_espresso'
252
-            )
253
-        );
254
-        EE_Registry::$i18n_js_strings['error_occurred']          = wp_strip_all_tags(
255
-            esc_html__(
256
-                'An error occurred! Please refresh the page and try again.',
257
-                'event_espresso'
258
-            )
259
-        );
260
-        EE_Registry::$i18n_js_strings['confirm_delete_state']    = wp_strip_all_tags(
261
-            esc_html__(
262
-                'Are you sure you want to delete this State / Province?',
263
-                'event_espresso'
264
-            )
265
-        );
266
-        EE_Registry::$i18n_js_strings['ajax_url']                = admin_url(
267
-            'admin-ajax.php?page=espresso_general_settings',
268
-            is_ssl() ? 'https://' : 'http://'
269
-        );
270
-    }
271
-
272
-
273
-    public function admin_notices()
274
-    {
275
-    }
276
-
277
-
278
-    public function admin_footer_scripts()
279
-    {
280
-    }
281
-
282
-
283
-    /**
284
-     * Enqueue scripts and styles for the default route.
285
-     */
286
-    public function load_scripts_styles_default()
287
-    {
288
-        // styles
289
-        wp_enqueue_style('thickbox');
290
-        // scripts
291
-        wp_enqueue_script('media-upload');
292
-        wp_enqueue_script('thickbox');
293
-        wp_register_script(
294
-            'organization_settings',
295
-            GEN_SET_ASSETS_URL . 'your_organization_settings.js',
296
-            ['jquery', 'media-upload', 'thickbox'],
297
-            EVENT_ESPRESSO_VERSION,
298
-            true
299
-        );
300
-        wp_register_style('organization-css', GEN_SET_ASSETS_URL . 'organization.css', [], EVENT_ESPRESSO_VERSION);
301
-        wp_enqueue_script('organization_settings');
302
-        wp_enqueue_style('organization-css');
303
-        $confirm_image_delete = [
304
-            'text' => wp_strip_all_tags(
305
-                esc_html__(
306
-                    'Do you really want to delete this image? Please remember to save your settings to complete the removal.',
307
-                    'event_espresso'
308
-                )
309
-            ),
310
-        ];
311
-        wp_localize_script('organization_settings', 'confirm_image_delete', $confirm_image_delete);
312
-    }
313
-
314
-
315
-    /**
316
-     * Enqueue scripts and styles for the country settings route.
317
-     */
318
-    public function load_scripts_styles_country_settings()
319
-    {
320
-        // scripts
321
-        wp_register_script(
322
-            'gen_settings_countries',
323
-            GEN_SET_ASSETS_URL . 'gen_settings_countries.js',
324
-            ['ee_admin_js'],
325
-            EVENT_ESPRESSO_VERSION,
326
-            true
327
-        );
328
-        wp_register_style('organization-css', GEN_SET_ASSETS_URL . 'organization.css', [], EVENT_ESPRESSO_VERSION);
329
-        wp_enqueue_script('gen_settings_countries');
330
-        wp_enqueue_style('organization-css');
331
-    }
332
-
333
-
334
-    /*************        Espresso Pages        *************/
335
-    /**
336
-     * _espresso_page_settings
337
-     *
338
-     * @throws EE_Error
339
-     * @throws DomainException
340
-     * @throws DomainException
341
-     * @throws InvalidDataTypeException
342
-     * @throws InvalidArgumentException
343
-     */
344
-    protected function _espresso_page_settings()
345
-    {
346
-        // Check to make sure all of the main pages are set up properly,
347
-        // if not create the default pages and display an admin notice
348
-        EEH_Activation::verify_default_pages_exist();
349
-        $this->_transient_garbage_collection();
350
-
351
-        $this->_template_args['values'] = $this->_yes_no_values;
352
-
353
-        $this->_template_args['reg_page_id']  = $this->core_config->reg_page_id ?? null;
354
-        $this->_template_args['reg_page_obj'] = isset($this->core_config->reg_page_id)
355
-            ? get_post($this->core_config->reg_page_id)
356
-            : false;
357
-
358
-        $this->_template_args['txn_page_id']  = $this->core_config->txn_page_id ?? null;
359
-        $this->_template_args['txn_page_obj'] = isset($this->core_config->txn_page_id)
360
-            ? get_post($this->core_config->txn_page_id)
361
-            : false;
362
-
363
-        $this->_template_args['thank_you_page_id']  = $this->core_config->thank_you_page_id ?? null;
364
-        $this->_template_args['thank_you_page_obj'] = isset($this->core_config->thank_you_page_id)
365
-            ? get_post($this->core_config->thank_you_page_id)
366
-            : false;
367
-
368
-        $this->_template_args['cancel_page_id']  = $this->core_config->cancel_page_id ?? null;
369
-        $this->_template_args['cancel_page_obj'] = isset($this->core_config->cancel_page_id)
370
-            ? get_post($this->core_config->cancel_page_id)
371
-            : false;
372
-
373
-        $this->_set_add_edit_form_tags('update_espresso_page_settings');
374
-        $this->_set_publish_post_box_vars(null, false, false, null, false);
375
-        $this->_template_args['admin_page_content'] = EEH_Template::display_template(
376
-            GEN_SET_TEMPLATE_PATH . 'espresso_page_settings.template.php',
377
-            $this->_template_args,
378
-            true
379
-        );
380
-        $this->display_admin_page_with_sidebar();
381
-    }
382
-
383
-
384
-    /**
385
-     * Handler for updating espresso page settings.
386
-     *
387
-     * @throws EE_Error
388
-     */
389
-    protected function _update_espresso_page_settings()
390
-    {
391
-        $this->core_config = EE_Registry::instance()->CFG->core;
392
-        // capture incoming request data && set page IDs
393
-        $this->core_config->reg_page_id       = $this->request->getRequestParam(
394
-            'reg_page_id',
395
-            $this->core_config->reg_page_id,
396
-            DataType::INT
397
-        );
398
-        $this->core_config->txn_page_id       = $this->request->getRequestParam(
399
-            'txn_page_id',
400
-            $this->core_config->txn_page_id,
401
-            DataType::INT
402
-        );
403
-        $this->core_config->thank_you_page_id = $this->request->getRequestParam(
404
-            'thank_you_page_id',
405
-            $this->core_config->thank_you_page_id,
406
-            DataType::INT
407
-        );
408
-        $this->core_config->cancel_page_id    = $this->request->getRequestParam(
409
-            'cancel_page_id',
410
-            $this->core_config->cancel_page_id,
411
-            DataType::INT
412
-        );
413
-
414
-        $this->core_config = apply_filters(
415
-            'FHEE__General_Settings_Admin_Page___update_espresso_page_settings__CFG_core',
416
-            $this->core_config,
417
-            $this->request->requestParams()
418
-        );
419
-
420
-        $what = esc_html__('Critical Pages & Shortcodes', 'event_espresso');
421
-        $this->_redirect_after_action(
422
-            $this->_update_espresso_configuration(
423
-                $what,
424
-                $this->core_config,
425
-                __FILE__,
426
-                __FUNCTION__,
427
-                __LINE__
428
-            ),
429
-            $what,
430
-            '',
431
-            [
432
-                'action' => 'critical_pages',
433
-            ],
434
-            true
435
-        );
436
-    }
437
-
438
-
439
-    /*************        Your Organization        *************/
440
-
441
-
442
-    /**
443
-     * @throws DomainException
444
-     * @throws EE_Error
445
-     * @throws InvalidArgumentException
446
-     * @throws InvalidDataTypeException
447
-     * @throws InvalidInterfaceException
448
-     */
449
-    protected function _your_organization_settings()
450
-    {
451
-        $this->_template_args['admin_page_content'] = '';
452
-        try {
453
-            /** @var OrganizationSettings $organization_settings_form */
454
-            $organization_settings_form = $this->loader->getShared(OrganizationSettings::class);
455
-
456
-            $this->_template_args['admin_page_content'] = EEH_HTML::div(
457
-                $organization_settings_form->display(),
458
-                '',
459
-                'padding'
460
-            );
461
-        } catch (Exception $e) {
462
-            EE_Error::add_error($e->getMessage(), __FILE__, __FUNCTION__, __LINE__);
463
-        }
464
-        $this->_set_add_edit_form_tags('update_your_organization_settings');
465
-        $this->_set_publish_post_box_vars(null, false, false, null, false);
466
-        $this->display_admin_page_with_sidebar();
467
-    }
468
-
469
-
470
-    /**
471
-     * Handler for updating organization settings.
472
-     *
473
-     * @throws EE_Error
474
-     */
475
-    protected function _update_your_organization_settings()
476
-    {
477
-        try {
478
-            /** @var OrganizationSettings $organization_settings_form */
479
-            $organization_settings_form = $this->loader->getShared(OrganizationSettings::class);
480
-
481
-            $success = $organization_settings_form->process($this->request->requestParams());
482
-
483
-            EE_Registry::instance()->CFG = apply_filters(
484
-                'FHEE__General_Settings_Admin_Page___update_your_organization_settings__CFG',
485
-                EE_Registry::instance()->CFG
486
-            );
487
-        } catch (Exception $e) {
488
-            EE_Error::add_error($e->getMessage(), __FILE__, __FUNCTION__, __LINE__);
489
-            $success = false;
490
-        }
491
-
492
-        if ($success) {
493
-            $success = $this->_update_espresso_configuration(
494
-                esc_html__('Your Organization Settings', 'event_espresso'),
495
-                EE_Registry::instance()->CFG,
496
-                __FILE__,
497
-                __FUNCTION__,
498
-                __LINE__
499
-            );
500
-        }
501
-
502
-        $this->_redirect_after_action($success, '', '', ['action' => 'default'], true);
503
-    }
504
-
505
-
506
-
507
-    /*************        Admin Options        *************/
508
-
509
-
510
-    /**
511
-     * _admin_option_settings
512
-     *
513
-     * @throws EE_Error
514
-     * @throws LogicException
515
-     */
516
-    protected function _admin_option_settings()
517
-    {
518
-        $this->_template_args['admin_page_content'] = '';
519
-        try {
520
-            $admin_options_settings_form = new AdminOptionsSettings(EE_Registry::instance());
521
-            // still need this for the old school form in Extend_General_Settings_Admin_Page
522
-            $this->_template_args['values'] = $this->_yes_no_values;
523
-            // also need to account for the do_action that was in the old template
524
-            $admin_options_settings_form->setTemplateArgs($this->_template_args);
525
-            $this->_template_args['admin_page_content'] = EEH_HTML::div(
526
-                $admin_options_settings_form->display(),
527
-                '',
528
-                'padding'
529
-            );
530
-        } catch (Exception $e) {
531
-            EE_Error::add_error($e->getMessage(), __FILE__, __FUNCTION__, __LINE__);
532
-        }
533
-        $this->_set_add_edit_form_tags('update_admin_option_settings');
534
-        $this->_set_publish_post_box_vars(null, false, false, null, false);
535
-        $this->display_admin_page_with_sidebar();
536
-    }
537
-
538
-
539
-    /**
540
-     * _update_admin_option_settings
541
-     *
542
-     * @throws EE_Error
543
-     * @throws InvalidDataTypeException
544
-     * @throws InvalidFormSubmissionException
545
-     * @throws InvalidArgumentException
546
-     * @throws LogicException
547
-     */
548
-    protected function _update_admin_option_settings()
549
-    {
550
-        try {
551
-            $admin_options_settings_form = new AdminOptionsSettings(EE_Registry::instance());
552
-            $admin_options_settings_form->process(
553
-                $this->request->getRequestParam(
554
-                    $admin_options_settings_form->slug(),
555
-                    [],
556
-                    DataType::STRING,
557
-                    true
558
-                )
559
-            );
560
-            EE_Registry::instance()->CFG->admin = apply_filters(
561
-                'FHEE__General_Settings_Admin_Page___update_admin_option_settings__CFG_admin',
562
-                EE_Registry::instance()->CFG->admin
563
-            );
564
-        } catch (Exception $e) {
565
-            EE_Error::add_error($e->getMessage(), __FILE__, __FUNCTION__, __LINE__);
566
-        }
567
-        $this->_redirect_after_action(
568
-            apply_filters(
569
-                'FHEE__General_Settings_Admin_Page___update_admin_option_settings__success',
570
-                $this->_update_espresso_configuration(
571
-                    esc_html__('Admin Options', 'event_espresso'),
572
-                    EE_Registry::instance()->CFG->admin,
573
-                    __FILE__,
574
-                    __FUNCTION__,
575
-                    __LINE__
576
-                )
577
-            ),
578
-            esc_html__('Admin Options', 'event_espresso'),
579
-            'updated',
580
-            ['action' => 'admin_option_settings']
581
-        );
582
-    }
583
-
584
-
585
-    /*************        Countries        *************/
586
-
587
-
588
-    /**
589
-     * @param string|null $default
590
-     * @return string
591
-     */
592
-    protected function getCountryISO(?string $default = null): string
593
-    {
594
-        $default = $default ?? $this->getCountryIsoForSite();
595
-        $CNT_ISO = $this->request->getRequestParam('country', $default);
596
-        $CNT_ISO = $this->request->getRequestParam('CNT_ISO', $CNT_ISO);
597
-        return strtoupper($CNT_ISO);
598
-    }
599
-
600
-
601
-    /**
602
-     * @return string
603
-     */
604
-    protected function getCountryIsoForSite(): string
605
-    {
606
-        return ! empty(EE_Registry::instance()->CFG->organization->CNT_ISO)
607
-            ? EE_Registry::instance()->CFG->organization->CNT_ISO
608
-            : 'US';
609
-    }
610
-
611
-
612
-    /**
613
-     * @param string          $CNT_ISO
614
-     * @param EE_Country|null $country
615
-     * @return EE_Base_Class|EE_Country
616
-     * @throws EE_Error
617
-     * @throws InvalidArgumentException
618
-     * @throws InvalidDataTypeException
619
-     * @throws InvalidInterfaceException
620
-     * @throws ReflectionException
621
-     */
622
-    protected function verifyOrGetCountryFromIso(string $CNT_ISO, ?EE_Country $country = null)
623
-    {
624
-        /** @var EE_Country $country */
625
-        return $country instanceof EE_Country && $country->ID() === $CNT_ISO
626
-            ? $country
627
-            : EEM_Country::instance()->get_one_by_ID($CNT_ISO);
628
-    }
629
-
630
-
631
-    /**
632
-     * Output Country Settings view.
633
-     *
634
-     * @throws DomainException
635
-     * @throws EE_Error
636
-     * @throws InvalidArgumentException
637
-     * @throws InvalidDataTypeException
638
-     * @throws InvalidInterfaceException
639
-     * @throws ReflectionException
640
-     */
641
-    protected function _country_settings()
642
-    {
643
-        $CNT_ISO = $this->getCountryISO();
644
-
645
-        $this->_template_args['values']    = $this->_yes_no_values;
646
-        $this->_template_args['countries'] = new EE_Question_Form_Input(
647
-            EE_Question::new_instance(
648
-                [
649
-                  'QST_ID'           => 0,
650
-                  'QST_display_text' => esc_html__('Select Country', 'event_espresso'),
651
-                  'QST_system'       => 'admin-country',
652
-                ]
653
-            ),
654
-            EE_Answer::new_instance(
655
-                [
656
-                    'ANS_ID'    => 0,
657
-                    'ANS_value' => $CNT_ISO,
658
-                ]
659
-            ),
660
-            [
661
-                'input_id'       => 'country',
662
-                'input_name'     => 'country',
663
-                'input_prefix'   => '',
664
-                'append_qstn_id' => false,
665
-            ]
666
-        );
667
-
668
-        $country = $this->verifyOrGetCountryFromIso($CNT_ISO);
669
-        add_filter('FHEE__EEH_Form_Fields__label_html', [$this, 'country_form_field_label_wrap'], 10);
670
-        add_filter('FHEE__EEH_Form_Fields__input_html', [$this, 'country_form_field_input__wrap'], 10);
671
-        $this->_template_args['country_details_settings'] = $this->display_country_settings(
672
-            $country->ID(),
673
-            $country
674
-        );
675
-        $this->_template_args['country_states_settings']  = $this->display_country_states(
676
-            $country->ID(),
677
-            $country
678
-        );
679
-        $this->_template_args['CNT_name_for_site']        = $country->name();
680
-
681
-        $this->_set_add_edit_form_tags('update_country_settings');
682
-        $this->_set_publish_post_box_vars(null, false, false, null, false);
683
-        $this->_template_args['admin_page_content'] = EEH_Template::display_template(
684
-            GEN_SET_TEMPLATE_PATH . 'countries_settings.template.php',
685
-            $this->_template_args,
686
-            true
687
-        );
688
-        $this->display_admin_page_with_no_sidebar();
689
-    }
690
-
691
-
692
-    /**
693
-     * @param string          $CNT_ISO
694
-     * @param EE_Country|null $country
695
-     * @return string
696
-     * @throws DomainException
697
-     * @throws EE_Error
698
-     * @throws InvalidArgumentException
699
-     * @throws InvalidDataTypeException
700
-     * @throws InvalidInterfaceException
701
-     * @throws ReflectionException
702
-     */
703
-    public function display_country_settings(string $CNT_ISO = '', ?EE_Country $country = null): string
704
-    {
705
-        $CNT_ISO          = $this->getCountryISO($CNT_ISO);
706
-        $CNT_ISO_for_site = $this->getCountryIsoForSite();
707
-
708
-        if (! $CNT_ISO) {
709
-            return '';
710
-        }
711
-
712
-        // for ajax
713
-        remove_all_filters('FHEE__EEH_Form_Fields__label_html');
714
-        remove_all_filters('FHEE__EEH_Form_Fields__input_html');
715
-        add_filter('FHEE__EEH_Form_Fields__label_html', [$this, 'country_form_field_label_wrap'], 10, 2);
716
-        add_filter('FHEE__EEH_Form_Fields__input_html', [$this, 'country_form_field_input__wrap'], 10, 2);
717
-        $country                                  = $this->verifyOrGetCountryFromIso($CNT_ISO, $country);
718
-        $CNT_cur_disabled                         = $CNT_ISO !== $CNT_ISO_for_site;
719
-        $this->_template_args['CNT_cur_disabled'] = $CNT_cur_disabled;
720
-
721
-        $country_input_types            = [
722
-            'CNT_active'      => [
723
-                'type'             => 'RADIO_BTN',
724
-                'input_name'       => "cntry[$CNT_ISO]",
725
-                'class'            => '',
726
-                'options'          => $this->_yes_no_values,
727
-                'use_desc_4_label' => true,
728
-            ],
729
-            'CNT_ISO'         => [
730
-                'type'       => 'TEXT',
731
-                'input_name' => "cntry[$CNT_ISO]",
732
-                'class'      => 'ee-input-width--small',
733
-            ],
734
-            'CNT_ISO3'        => [
735
-                'type'       => 'TEXT',
736
-                'input_name' => "cntry[$CNT_ISO]",
737
-                'class'      => 'ee-input-width--small',
738
-            ],
739
-            // 'RGN_ID'          => [
740
-            //     'type'       => 'TEXT',
741
-            //     'input_name' => "cntry[$CNT_ISO]",
742
-            //     'class'      => 'ee-input-width--small',
743
-            // ],
744
-            'CNT_name'        => [
745
-                'type'       => 'TEXT',
746
-                'input_name' => "cntry[$CNT_ISO]",
747
-                'class'      => 'ee-input-width--big',
748
-            ],
749
-            'CNT_cur_code'    => [
750
-                'type'       => 'TEXT',
751
-                'input_name' => "cntry[$CNT_ISO]",
752
-                'class'      => 'ee-input-width--small',
753
-                'disabled'   => $CNT_cur_disabled,
754
-            ],
755
-            'CNT_cur_single'  => [
756
-                'type'       => 'TEXT',
757
-                'input_name' => "cntry[$CNT_ISO]",
758
-                'class'      => 'ee-input-width--reg',
759
-                'disabled'   => $CNT_cur_disabled,
760
-            ],
761
-            'CNT_cur_plural'  => [
762
-                'type'       => 'TEXT',
763
-                'input_name' => "cntry[$CNT_ISO]",
764
-                'class'      => 'ee-input-width--reg',
765
-                'disabled'   => $CNT_cur_disabled,
766
-            ],
767
-            'CNT_cur_sign'    => [
768
-                'type'         => 'TEXT',
769
-                'input_name'   => "cntry[$CNT_ISO]",
770
-                'class'        => 'ee-input-width--small',
771
-                'htmlentities' => false,
772
-                'disabled'     => $CNT_cur_disabled,
773
-            ],
774
-            'CNT_cur_sign_b4' => [
775
-                'type'             => 'RADIO_BTN',
776
-                'input_name'       => "cntry[$CNT_ISO]",
777
-                'class'            => '',
778
-                'options'          => $this->_yes_no_values,
779
-                'use_desc_4_label' => true,
780
-                'disabled'         => $CNT_cur_disabled,
781
-            ],
782
-            'CNT_cur_dec_plc' => [
783
-                'type'       => 'RADIO_BTN',
784
-                'input_name' => "cntry[$CNT_ISO]",
785
-                'class'      => '',
786
-                'options'    => [
787
-                    ['id' => 0, 'text' => ''],
788
-                    ['id' => 1, 'text' => ''],
789
-                    ['id' => 2, 'text' => ''],
790
-                    ['id' => 3, 'text' => ''],
791
-                ],
792
-                'disabled'   => $CNT_cur_disabled,
793
-            ],
794
-            'CNT_cur_dec_mrk' => [
795
-                'type'             => 'RADIO_BTN',
796
-                'input_name'       => "cntry[$CNT_ISO]",
797
-                'class'            => '',
798
-                'options'          => [
799
-                    [
800
-                        'id'   => ',',
801
-                        'text' => esc_html__(', (comma)', 'event_espresso'),
802
-                    ],
803
-                    ['id' => '.', 'text' => esc_html__('. (decimal)', 'event_espresso')],
804
-                ],
805
-                'use_desc_4_label' => true,
806
-                'disabled'         => $CNT_cur_disabled,
807
-            ],
808
-            'CNT_cur_thsnds'  => [
809
-                'type'             => 'RADIO_BTN',
810
-                'input_name'       => "cntry[$CNT_ISO]",
811
-                'class'            => '',
812
-                'options'          => [
813
-                    [
814
-                        'id'   => ',',
815
-                        'text' => esc_html__(', (comma)', 'event_espresso'),
816
-                    ],
817
-                    [
818
-                        'id'   => '.',
819
-                        'text' => esc_html__('. (decimal)', 'event_espresso'),
820
-                    ],
821
-                    [
822
-                        'id'   => '&nbsp;',
823
-                        'text' => esc_html__('(space)', 'event_espresso'),
824
-                    ],
825
-                    [
826
-                        'id'   => '_',
827
-                        'text' => esc_html__('_ (underscore)', 'event_espresso'),
828
-                    ],
829
-                    [
830
-                        'id'   => "'",
831
-                        'text' => esc_html__("' (apostrophe)", 'event_espresso'),
832
-                    ],
833
-                ],
834
-                'use_desc_4_label' => true,
835
-                'disabled'         => $CNT_cur_disabled,
836
-            ],
837
-            'CNT_tel_code'    => [
838
-                'type'       => 'TEXT',
839
-                'input_name' => "cntry[$CNT_ISO]",
840
-                'class'      => 'ee-input-width--small',
841
-            ],
842
-            'CNT_is_EU'       => [
843
-                'type'             => 'RADIO_BTN',
844
-                'input_name'       => "cntry[$CNT_ISO]",
845
-                'class'            => '',
846
-                'options'          => $this->_yes_no_values,
847
-                'use_desc_4_label' => true,
848
-            ],
849
-        ];
850
-        $this->_template_args['inputs'] = EE_Question_Form_Input::generate_question_form_inputs_for_object(
851
-            $country,
852
-            $country_input_types
853
-        );
854
-        $country_details_settings       = EEH_Template::display_template(
855
-            GEN_SET_TEMPLATE_PATH . 'country_details_settings.template.php',
856
-            $this->_template_args,
857
-            true
858
-        );
859
-
860
-        if (defined('DOING_AJAX')) {
861
-            $notices = EE_Error::get_notices(false, false, false);
862
-            echo wp_json_encode(
863
-                [
864
-                    'return_data' => $country_details_settings,
865
-                    'success'     => $notices['success'],
866
-                    'errors'      => $notices['errors'],
867
-                ]
868
-            );
869
-            die();
870
-        }
871
-        return $country_details_settings;
872
-    }
873
-
874
-
875
-    /**
876
-     * @param string          $CNT_ISO
877
-     * @param EE_Country|null $country
878
-     * @return string
879
-     * @throws DomainException
880
-     * @throws EE_Error
881
-     * @throws InvalidArgumentException
882
-     * @throws InvalidDataTypeException
883
-     * @throws InvalidInterfaceException
884
-     * @throws ReflectionException
885
-     */
886
-    public function display_country_states(string $CNT_ISO = '', ?EE_Country $country = null): string
887
-    {
888
-        $CNT_ISO = $this->getCountryISO($CNT_ISO);
889
-        if (! $CNT_ISO) {
890
-            return '';
891
-        }
892
-        // for ajax
893
-        remove_all_filters('FHEE__EEH_Form_Fields__label_html');
894
-        remove_all_filters('FHEE__EEH_Form_Fields__input_html');
895
-        add_filter('FHEE__EEH_Form_Fields__label_html', [$this, 'state_form_field_label_wrap'], 10, 2);
896
-        add_filter('FHEE__EEH_Form_Fields__input_html', [$this, 'state_form_field_input__wrap'], 10);
897
-        $states = EEM_State::instance()->get_all_states_for_these_countries([$CNT_ISO => $CNT_ISO]);
898
-        if (empty($states)) {
899
-            /** @var EventEspresso\core\services\address\CountrySubRegionDao $countrySubRegionDao */
900
-            $countrySubRegionDao = $this->loader->getShared(
901
-                'EventEspresso\core\services\address\CountrySubRegionDao'
902
-            );
903
-            if ($countrySubRegionDao instanceof EventEspresso\core\services\address\CountrySubRegionDao) {
904
-                $country = $this->verifyOrGetCountryFromIso($CNT_ISO, $country);
905
-                if ($countrySubRegionDao->saveCountrySubRegions($country)) {
906
-                    $states = EEM_State::instance()->get_all_states_for_these_countries([$CNT_ISO => $CNT_ISO]);
907
-                }
908
-            }
909
-        }
910
-        if (is_array($states)) {
911
-            foreach ($states as $STA_ID => $state) {
912
-                if ($state instanceof EE_State) {
913
-                    $inputs = EE_Question_Form_Input::generate_question_form_inputs_for_object(
914
-                        $state,
915
-                        [
916
-                            'STA_abbrev' => [
917
-                                'type'             => 'TEXT',
918
-                                'label'            => esc_html__('Code', 'event_espresso'),
919
-                                'input_name'       => "states[$STA_ID]",
920
-                                'class'            => 'ee-input-width--tiny',
921
-                                'add_mobile_label' => true,
922
-                            ],
923
-                            'STA_name'   => [
924
-                                'type'             => 'TEXT',
925
-                                'label'            => esc_html__('Name', 'event_espresso'),
926
-                                'input_name'       => "states[$STA_ID]",
927
-                                'class'            => 'ee-input-width--big',
928
-                                'add_mobile_label' => true,
929
-                            ],
930
-                            'STA_active' => [
931
-                                'type'             => 'RADIO_BTN',
932
-                                'label'            => esc_html__(
933
-                                    'State Appears in Dropdown Select Lists',
934
-                                    'event_espresso'
935
-                                ),
936
-                                'input_name'       => "states[$STA_ID]",
937
-                                'options'          => $this->_yes_no_values,
938
-                                'use_desc_4_label' => true,
939
-                                'add_mobile_label' => true,
940
-                            ],
941
-                        ]
942
-                    );
943
-
944
-                    $delete_state_url = EE_Admin_Page::add_query_args_and_nonce(
945
-                        [
946
-                            'action'     => 'delete_state',
947
-                            'STA_ID'     => $STA_ID,
948
-                            'CNT_ISO'    => $CNT_ISO,
949
-                            'STA_abbrev' => $state->abbrev(),
950
-                        ],
951
-                        GEN_SET_ADMIN_URL
952
-                    );
953
-
954
-                    $this->_template_args['states'][ $STA_ID ]['inputs']           = $inputs;
955
-                    $this->_template_args['states'][ $STA_ID ]['delete_state_url'] = $delete_state_url;
956
-                }
957
-            }
958
-        } else {
959
-            $this->_template_args['states'] = false;
960
-        }
961
-
962
-        $this->_template_args['add_new_state_url'] = EE_Admin_Page::add_query_args_and_nonce(
963
-            ['action' => 'add_new_state'],
964
-            GEN_SET_ADMIN_URL
965
-        );
966
-
967
-        $state_details_settings = EEH_Template::display_template(
968
-            GEN_SET_TEMPLATE_PATH . 'state_details_settings.template.php',
969
-            $this->_template_args,
970
-            true
971
-        );
972
-
973
-        if (defined('DOING_AJAX')) {
974
-            $notices = EE_Error::get_notices(false, false, false);
975
-            echo wp_json_encode(
976
-                [
977
-                    'return_data' => $state_details_settings,
978
-                    'success'     => $notices['success'],
979
-                    'errors'      => $notices['errors'],
980
-                ]
981
-            );
982
-            die();
983
-        }
984
-        return $state_details_settings;
985
-    }
986
-
987
-
988
-    /**
989
-     * @return void
990
-     * @throws EE_Error
991
-     * @throws InvalidArgumentException
992
-     * @throws InvalidDataTypeException
993
-     * @throws InvalidInterfaceException
994
-     * @throws ReflectionException
995
-     */
996
-    public function add_new_state()
997
-    {
998
-        $success = true;
999
-        $CNT_ISO = $this->getCountryISO('');
1000
-        if (! $CNT_ISO) {
1001
-            EE_Error::add_error(
1002
-                esc_html__('No Country ISO code or an invalid Country ISO code was received.', 'event_espresso'),
1003
-                __FILE__,
1004
-                __FUNCTION__,
1005
-                __LINE__
1006
-            );
1007
-            $success = false;
1008
-        }
1009
-        $STA_abbrev = $this->request->getRequestParam('STA_abbrev');
1010
-        if (! $STA_abbrev) {
1011
-            EE_Error::add_error(
1012
-                esc_html__('No State ISO code or an invalid State ISO code was received.', 'event_espresso'),
1013
-                __FILE__,
1014
-                __FUNCTION__,
1015
-                __LINE__
1016
-            );
1017
-            $success = false;
1018
-        }
1019
-        $STA_name = $this->request->getRequestParam('STA_name');
1020
-        if (! $STA_name) {
1021
-            EE_Error::add_error(
1022
-                esc_html__('No State name or an invalid State name was received.', 'event_espresso'),
1023
-                __FILE__,
1024
-                __FUNCTION__,
1025
-                __LINE__
1026
-            );
1027
-            $success = false;
1028
-        }
1029
-
1030
-        if ($success) {
1031
-            $cols_n_values = [
1032
-                'CNT_ISO'    => $CNT_ISO,
1033
-                'STA_abbrev' => $STA_abbrev,
1034
-                'STA_name'   => $STA_name,
1035
-                'STA_active' => true,
1036
-            ];
1037
-            $success       = EEM_State::instance()->insert($cols_n_values);
1038
-            EE_Error::add_success(esc_html__('The State was added successfully.', 'event_espresso'));
1039
-        }
1040
-
1041
-        if (defined('DOING_AJAX')) {
1042
-            $notices = EE_Error::get_notices(false, false, false);
1043
-            echo wp_json_encode(array_merge($notices, ['return_data' => $CNT_ISO]));
1044
-            die();
1045
-        }
1046
-        $this->_redirect_after_action(
1047
-            $success,
1048
-            esc_html__('State', 'event_espresso'),
1049
-            'added',
1050
-            ['action' => 'country_settings']
1051
-        );
1052
-    }
1053
-
1054
-
1055
-    /**
1056
-     * @return void
1057
-     * @throws EE_Error
1058
-     * @throws InvalidArgumentException
1059
-     * @throws InvalidDataTypeException
1060
-     * @throws InvalidInterfaceException
1061
-     * @throws ReflectionException
1062
-     */
1063
-    public function delete_state()
1064
-    {
1065
-        $CNT_ISO    = $this->getCountryISO();
1066
-        $STA_ID     = $this->request->getRequestParam('STA_ID');
1067
-        $STA_abbrev = $this->request->getRequestParam('STA_abbrev');
1068
-
1069
-        if (! $STA_ID) {
1070
-            EE_Error::add_error(
1071
-                esc_html__('No State ID or an invalid State ID was received.', 'event_espresso'),
1072
-                __FILE__,
1073
-                __FUNCTION__,
1074
-                __LINE__
1075
-            );
1076
-            return;
1077
-        }
1078
-
1079
-        $success = EEM_State::instance()->delete_by_ID($STA_ID);
1080
-        if ($success !== false) {
1081
-            do_action(
1082
-                'AHEE__General_Settings_Admin_Page__delete_state__state_deleted',
1083
-                $CNT_ISO,
1084
-                $STA_ID,
1085
-                ['STA_abbrev' => $STA_abbrev]
1086
-            );
1087
-            EE_Error::add_success(esc_html__('The State was deleted successfully.', 'event_espresso'));
1088
-        }
1089
-        if (defined('DOING_AJAX')) {
1090
-            $notices                = EE_Error::get_notices(false);
1091
-            $notices['return_data'] = true;
1092
-            echo wp_json_encode($notices);
1093
-            die();
1094
-        }
1095
-        $this->_redirect_after_action(
1096
-            $success,
1097
-            esc_html__('State', 'event_espresso'),
1098
-            'deleted',
1099
-            ['action' => 'country_settings']
1100
-        );
1101
-    }
1102
-
1103
-
1104
-    /**
1105
-     * @return void
1106
-     * @throws EE_Error
1107
-     * @throws InvalidArgumentException
1108
-     * @throws InvalidDataTypeException
1109
-     * @throws InvalidInterfaceException
1110
-     * @throws ReflectionException
1111
-     */
1112
-    protected function _update_country_settings()
1113
-    {
1114
-        $CNT_ISO = $this->getCountryISO();
1115
-        if (! $CNT_ISO) {
1116
-            EE_Error::add_error(
1117
-                esc_html__('No Country ISO code or an invalid Country ISO code was received.', 'event_espresso'),
1118
-                __FILE__,
1119
-                __FUNCTION__,
1120
-                __LINE__
1121
-            );
1122
-            return;
1123
-        }
1124
-
1125
-        $country = $this->verifyOrGetCountryFromIso($CNT_ISO);
1126
-
1127
-        $cols_n_values                    = [];
1128
-        $cols_n_values['CNT_ISO3']        = strtoupper(
1129
-            $this->request->getRequestParam(
1130
-                "cntry[$CNT_ISO][CNT_ISO3]",
1131
-                $country->ISO3()
1132
-            )
1133
-        );
1134
-        $cols_n_values['CNT_name']        = $this->request->getRequestParam(
1135
-            "cntry[$CNT_ISO][CNT_name]",
1136
-            $country->name()
1137
-        );
1138
-        $cols_n_values['CNT_cur_code']    = strtoupper(
1139
-            $this->request->getRequestParam(
1140
-                "cntry[$CNT_ISO][CNT_cur_code]",
1141
-                $country->currency_code()
1142
-            )
1143
-        );
1144
-        $cols_n_values['CNT_cur_single']  = $this->request->getRequestParam(
1145
-            "cntry[$CNT_ISO][CNT_cur_single]",
1146
-            $country->currency_name_single()
1147
-        );
1148
-        $cols_n_values['CNT_cur_plural']  = $this->request->getRequestParam(
1149
-            "cntry[$CNT_ISO][CNT_cur_plural]",
1150
-            $country->currency_name_plural()
1151
-        );
1152
-        $cols_n_values['CNT_cur_sign']    = $this->request->getRequestParam(
1153
-            "cntry[$CNT_ISO][CNT_cur_sign]",
1154
-            $country->currency_sign()
1155
-        );
1156
-        $cols_n_values['CNT_cur_sign_b4'] = $this->request->getRequestParam(
1157
-            "cntry[$CNT_ISO][CNT_cur_sign_b4]",
1158
-            $country->currency_sign_before(),
1159
-            DataType::BOOL
1160
-        );
1161
-        $cols_n_values['CNT_cur_dec_plc'] = $this->request->getRequestParam(
1162
-            "cntry[$CNT_ISO][CNT_cur_dec_plc]",
1163
-            $country->currency_decimal_places()
1164
-        );
1165
-        $cols_n_values['CNT_cur_dec_mrk'] = $this->request->getRequestParam(
1166
-            "cntry[$CNT_ISO][CNT_cur_dec_mrk]",
1167
-            $country->currency_decimal_mark()
1168
-        );
1169
-        $cols_n_values['CNT_cur_thsnds']  = $this->request->getRequestParam(
1170
-            "cntry[$CNT_ISO][CNT_cur_thsnds]",
1171
-            $country->currency_thousands_separator()
1172
-        );
1173
-        $cols_n_values['CNT_tel_code']    = $this->request->getRequestParam(
1174
-            "cntry[$CNT_ISO][CNT_tel_code]",
1175
-            $country->telephoneCode()
1176
-        );
1177
-        $cols_n_values['CNT_active']      = $this->request->getRequestParam(
1178
-            "cntry[$CNT_ISO][CNT_active]",
1179
-            $country->isActive(),
1180
-            DataType::BOOL
1181
-        );
1182
-
1183
-        // allow filtering of country data
1184
-        $cols_n_values = apply_filters(
1185
-            'FHEE__General_Settings_Admin_Page___update_country_settings__cols_n_values',
1186
-            $cols_n_values
1187
-        );
1188
-
1189
-        // where values
1190
-        $where_cols_n_values = [['CNT_ISO' => $CNT_ISO]];
1191
-        // run the update
1192
-        $success = EEM_Country::instance()->update($cols_n_values, $where_cols_n_values);
1193
-
1194
-        // allow filtering of states data
1195
-        $states = apply_filters(
1196
-            'FHEE__General_Settings_Admin_Page___update_country_settings__states',
1197
-            $this->request->getRequestParam('states', [], DataType::STRING, true)
1198
-        );
1199
-
1200
-        if (! empty($states) && $success !== false) {
1201
-            // loop thru state data ( looks like : states[75][STA_name] )
1202
-            foreach ($states as $STA_ID => $state) {
1203
-                $cols_n_values = [
1204
-                    'CNT_ISO'    => $CNT_ISO,
1205
-                    'STA_abbrev' => sanitize_text_field($state['STA_abbrev']),
1206
-                    'STA_name'   => sanitize_text_field($state['STA_name']),
1207
-                    'STA_active' => filter_var($state['STA_active'], FILTER_VALIDATE_BOOLEAN),
1208
-                ];
1209
-                // where values
1210
-                $where_cols_n_values = [['STA_ID' => $STA_ID]];
1211
-                // run the update
1212
-                $success = EEM_State::instance()->update($cols_n_values, $where_cols_n_values);
1213
-                if ($success !== false) {
1214
-                    do_action(
1215
-                        'AHEE__General_Settings_Admin_Page__update_country_settings__state_saved',
1216
-                        $CNT_ISO,
1217
-                        $STA_ID,
1218
-                        $cols_n_values
1219
-                    );
1220
-                }
1221
-            }
1222
-        }
1223
-        // check if country being edited matches org option country, and if so, then  update EE_Config with new settings
1224
-        if (
1225
-            isset(EE_Registry::instance()->CFG->organization->CNT_ISO)
1226
-            && $CNT_ISO == EE_Registry::instance()->CFG->organization->CNT_ISO
1227
-        ) {
1228
-            EE_Registry::instance()->CFG->currency = new EE_Currency_Config($CNT_ISO);
1229
-            EE_Registry::instance()->CFG->update_espresso_config();
1230
-        }
1231
-
1232
-        if ($success !== false) {
1233
-            EE_Error::add_success(
1234
-                esc_html__('Country Settings updated successfully.', 'event_espresso')
1235
-            );
1236
-        }
1237
-        $this->_redirect_after_action(
1238
-            $success,
1239
-            '',
1240
-            '',
1241
-            ['action' => 'country_settings', 'country' => $CNT_ISO],
1242
-            true
1243
-        );
1244
-    }
1245
-
1246
-
1247
-    /**
1248
-     * form_form_field_label_wrap
1249
-     *
1250
-     * @param string $label
1251
-     * @return string
1252
-     */
1253
-    public function country_form_field_label_wrap(string $label): string
1254
-    {
1255
-        return '
22
+	/**
23
+	 * @var EE_Core_Config
24
+	 */
25
+	public $core_config;
26
+
27
+
28
+	/**
29
+	 * Initialize basic properties.
30
+	 */
31
+	protected function _init_page_props()
32
+	{
33
+		$this->page_slug        = GEN_SET_PG_SLUG;
34
+		$this->page_label       = GEN_SET_LABEL;
35
+		$this->_admin_base_url  = GEN_SET_ADMIN_URL;
36
+		$this->_admin_base_path = GEN_SET_ADMIN;
37
+
38
+		$this->core_config = EE_Registry::instance()->CFG->core;
39
+	}
40
+
41
+
42
+	/**
43
+	 * Set ajax hooks
44
+	 */
45
+	protected function _ajax_hooks()
46
+	{
47
+		add_action('wp_ajax_espresso_display_country_settings', [$this, 'display_country_settings']);
48
+		add_action('wp_ajax_espresso_display_country_states', [$this, 'display_country_states']);
49
+		add_action('wp_ajax_espresso_delete_state', [$this, 'delete_state'], 10, 3);
50
+		add_action('wp_ajax_espresso_add_new_state', [$this, 'add_new_state']);
51
+	}
52
+
53
+
54
+	/**
55
+	 * More page properties initialization.
56
+	 */
57
+	protected function _define_page_props()
58
+	{
59
+		$this->_admin_page_title = GEN_SET_LABEL;
60
+		$this->_labels           = ['publishbox' => esc_html__('Update Settings', 'event_espresso')];
61
+	}
62
+
63
+
64
+	/**
65
+	 * Set page routes property.
66
+	 */
67
+	protected function _set_page_routes()
68
+	{
69
+		$this->_page_routes = [
70
+			'critical_pages'                => [
71
+				'func'       => [$this, '_espresso_page_settings'],
72
+				'capability' => 'manage_options',
73
+			],
74
+			'update_espresso_page_settings' => [
75
+				'func'       => [$this, '_update_espresso_page_settings'],
76
+				'capability' => 'manage_options',
77
+				'noheader'   => true,
78
+			],
79
+			'default'                       => [
80
+				'func'       => [$this, '_your_organization_settings'],
81
+				'capability' => 'manage_options',
82
+			],
83
+
84
+			'update_your_organization_settings' => [
85
+				'func'       => [$this, '_update_your_organization_settings'],
86
+				'capability' => 'manage_options',
87
+				'noheader'   => true,
88
+			],
89
+
90
+			'admin_option_settings' => [
91
+				'func'       => [$this, '_admin_option_settings'],
92
+				'capability' => 'manage_options',
93
+			],
94
+
95
+			'update_admin_option_settings' => [
96
+				'func'       => [$this, '_update_admin_option_settings'],
97
+				'capability' => 'manage_options',
98
+				'noheader'   => true,
99
+			],
100
+
101
+			'country_settings' => [
102
+				'func'       => [$this, '_country_settings'],
103
+				'capability' => 'manage_options',
104
+			],
105
+
106
+			'update_country_settings' => [
107
+				'func'       => [$this, '_update_country_settings'],
108
+				'capability' => 'manage_options',
109
+				'noheader'   => true,
110
+			],
111
+
112
+			'display_country_settings' => [
113
+				'func'       => [$this, 'display_country_settings'],
114
+				'capability' => 'manage_options',
115
+				'noheader'   => true,
116
+			],
117
+
118
+			'add_new_state' => [
119
+				'func'       => [$this, 'add_new_state'],
120
+				'capability' => 'manage_options',
121
+				'noheader'   => true,
122
+			],
123
+
124
+			'delete_state'            => [
125
+				'func'       => [$this, 'delete_state'],
126
+				'capability' => 'manage_options',
127
+				'noheader'   => true,
128
+			],
129
+			'privacy_settings'        => [
130
+				'func'       => [$this, 'privacySettings'],
131
+				'capability' => 'manage_options',
132
+			],
133
+			'update_privacy_settings' => [
134
+				'func'               => [$this, 'updatePrivacySettings'],
135
+				'capability'         => 'manage_options',
136
+				'noheader'           => true,
137
+				'headers_sent_route' => 'privacy_settings',
138
+			],
139
+		];
140
+	}
141
+
142
+
143
+	/**
144
+	 * Set page configuration property
145
+	 */
146
+	protected function _set_page_config()
147
+	{
148
+		$this->_page_config = [
149
+			'critical_pages'        => [
150
+				'nav'           => [
151
+					'label' => esc_html__('Critical Pages', 'event_espresso'),
152
+					'icon' => 'dashicons-warning',
153
+					'order' => 50,
154
+				],
155
+				'metaboxes'     => array_merge($this->_default_espresso_metaboxes, ['_publish_post_box']),
156
+				'help_tabs'     => [
157
+					'general_settings_critical_pages_help_tab' => [
158
+						'title'    => esc_html__('Critical Pages', 'event_espresso'),
159
+						'filename' => 'general_settings_critical_pages',
160
+					],
161
+				],
162
+				'require_nonce' => false,
163
+			],
164
+			'default'               => [
165
+				'nav'           => [
166
+					'label' => esc_html__('Your Organization', 'event_espresso'),
167
+					'icon' => 'dashicons-admin-home',
168
+					'order' => 20,
169
+				],
170
+				'help_tabs'     => [
171
+					'general_settings_your_organization_help_tab' => [
172
+						'title'    => esc_html__('Your Organization', 'event_espresso'),
173
+						'filename' => 'general_settings_your_organization',
174
+					],
175
+				],
176
+				'metaboxes'     => array_merge($this->_default_espresso_metaboxes, ['_publish_post_box']),
177
+				'require_nonce' => false,
178
+			],
179
+			'admin_option_settings' => [
180
+				'nav'           => [
181
+					'label' => esc_html__('Admin Options', 'event_espresso'),
182
+					'icon' => 'dashicons-admin-settings',
183
+					'order' => 60,
184
+				],
185
+				'metaboxes'     => array_merge($this->_default_espresso_metaboxes, ['_publish_post_box']),
186
+				'help_tabs'     => [
187
+					'general_settings_admin_options_help_tab' => [
188
+						'title'    => esc_html__('Admin Options', 'event_espresso'),
189
+						'filename' => 'general_settings_admin_options',
190
+					],
191
+				],
192
+				'require_nonce' => false,
193
+			],
194
+			'country_settings'      => [
195
+				'nav'           => [
196
+					'label' => esc_html__('Countries', 'event_espresso'),
197
+					'icon' => 'dashicons-admin-site',
198
+					'order' => 70,
199
+				],
200
+				'help_tabs'     => [
201
+					'general_settings_countries_help_tab' => [
202
+						'title'    => esc_html__('Countries', 'event_espresso'),
203
+						'filename' => 'general_settings_countries',
204
+					],
205
+				],
206
+				'require_nonce' => false,
207
+			],
208
+			'privacy_settings'      => [
209
+				'nav'           => [
210
+					'label' => esc_html__('Privacy', 'event_espresso'),
211
+					'icon' => 'dashicons-privacy',
212
+					'order' => 80,
213
+				],
214
+				'metaboxes'     => array_merge($this->_default_espresso_metaboxes, ['_publish_post_box']),
215
+				'require_nonce' => false,
216
+			],
217
+		];
218
+	}
219
+
220
+
221
+	protected function _add_screen_options()
222
+	{
223
+	}
224
+
225
+
226
+	protected function _add_feature_pointers()
227
+	{
228
+	}
229
+
230
+
231
+	/**
232
+	 * Enqueue global scripts and styles for all routes in the General Settings Admin Pages.
233
+	 */
234
+	public function load_scripts_styles()
235
+	{
236
+		// styles
237
+		wp_enqueue_style('espresso-ui-theme');
238
+		// scripts
239
+		wp_enqueue_script('ee_admin_js');
240
+	}
241
+
242
+
243
+	/**
244
+	 * Execute logic running on `admin_init`
245
+	 */
246
+	public function admin_init()
247
+	{
248
+		EE_Registry::$i18n_js_strings['invalid_server_response'] = wp_strip_all_tags(
249
+			esc_html__(
250
+				'An error occurred! Your request may have been processed, but a valid response from the server was not received. Please refresh the page and try again.',
251
+				'event_espresso'
252
+			)
253
+		);
254
+		EE_Registry::$i18n_js_strings['error_occurred']          = wp_strip_all_tags(
255
+			esc_html__(
256
+				'An error occurred! Please refresh the page and try again.',
257
+				'event_espresso'
258
+			)
259
+		);
260
+		EE_Registry::$i18n_js_strings['confirm_delete_state']    = wp_strip_all_tags(
261
+			esc_html__(
262
+				'Are you sure you want to delete this State / Province?',
263
+				'event_espresso'
264
+			)
265
+		);
266
+		EE_Registry::$i18n_js_strings['ajax_url']                = admin_url(
267
+			'admin-ajax.php?page=espresso_general_settings',
268
+			is_ssl() ? 'https://' : 'http://'
269
+		);
270
+	}
271
+
272
+
273
+	public function admin_notices()
274
+	{
275
+	}
276
+
277
+
278
+	public function admin_footer_scripts()
279
+	{
280
+	}
281
+
282
+
283
+	/**
284
+	 * Enqueue scripts and styles for the default route.
285
+	 */
286
+	public function load_scripts_styles_default()
287
+	{
288
+		// styles
289
+		wp_enqueue_style('thickbox');
290
+		// scripts
291
+		wp_enqueue_script('media-upload');
292
+		wp_enqueue_script('thickbox');
293
+		wp_register_script(
294
+			'organization_settings',
295
+			GEN_SET_ASSETS_URL . 'your_organization_settings.js',
296
+			['jquery', 'media-upload', 'thickbox'],
297
+			EVENT_ESPRESSO_VERSION,
298
+			true
299
+		);
300
+		wp_register_style('organization-css', GEN_SET_ASSETS_URL . 'organization.css', [], EVENT_ESPRESSO_VERSION);
301
+		wp_enqueue_script('organization_settings');
302
+		wp_enqueue_style('organization-css');
303
+		$confirm_image_delete = [
304
+			'text' => wp_strip_all_tags(
305
+				esc_html__(
306
+					'Do you really want to delete this image? Please remember to save your settings to complete the removal.',
307
+					'event_espresso'
308
+				)
309
+			),
310
+		];
311
+		wp_localize_script('organization_settings', 'confirm_image_delete', $confirm_image_delete);
312
+	}
313
+
314
+
315
+	/**
316
+	 * Enqueue scripts and styles for the country settings route.
317
+	 */
318
+	public function load_scripts_styles_country_settings()
319
+	{
320
+		// scripts
321
+		wp_register_script(
322
+			'gen_settings_countries',
323
+			GEN_SET_ASSETS_URL . 'gen_settings_countries.js',
324
+			['ee_admin_js'],
325
+			EVENT_ESPRESSO_VERSION,
326
+			true
327
+		);
328
+		wp_register_style('organization-css', GEN_SET_ASSETS_URL . 'organization.css', [], EVENT_ESPRESSO_VERSION);
329
+		wp_enqueue_script('gen_settings_countries');
330
+		wp_enqueue_style('organization-css');
331
+	}
332
+
333
+
334
+	/*************        Espresso Pages        *************/
335
+	/**
336
+	 * _espresso_page_settings
337
+	 *
338
+	 * @throws EE_Error
339
+	 * @throws DomainException
340
+	 * @throws DomainException
341
+	 * @throws InvalidDataTypeException
342
+	 * @throws InvalidArgumentException
343
+	 */
344
+	protected function _espresso_page_settings()
345
+	{
346
+		// Check to make sure all of the main pages are set up properly,
347
+		// if not create the default pages and display an admin notice
348
+		EEH_Activation::verify_default_pages_exist();
349
+		$this->_transient_garbage_collection();
350
+
351
+		$this->_template_args['values'] = $this->_yes_no_values;
352
+
353
+		$this->_template_args['reg_page_id']  = $this->core_config->reg_page_id ?? null;
354
+		$this->_template_args['reg_page_obj'] = isset($this->core_config->reg_page_id)
355
+			? get_post($this->core_config->reg_page_id)
356
+			: false;
357
+
358
+		$this->_template_args['txn_page_id']  = $this->core_config->txn_page_id ?? null;
359
+		$this->_template_args['txn_page_obj'] = isset($this->core_config->txn_page_id)
360
+			? get_post($this->core_config->txn_page_id)
361
+			: false;
362
+
363
+		$this->_template_args['thank_you_page_id']  = $this->core_config->thank_you_page_id ?? null;
364
+		$this->_template_args['thank_you_page_obj'] = isset($this->core_config->thank_you_page_id)
365
+			? get_post($this->core_config->thank_you_page_id)
366
+			: false;
367
+
368
+		$this->_template_args['cancel_page_id']  = $this->core_config->cancel_page_id ?? null;
369
+		$this->_template_args['cancel_page_obj'] = isset($this->core_config->cancel_page_id)
370
+			? get_post($this->core_config->cancel_page_id)
371
+			: false;
372
+
373
+		$this->_set_add_edit_form_tags('update_espresso_page_settings');
374
+		$this->_set_publish_post_box_vars(null, false, false, null, false);
375
+		$this->_template_args['admin_page_content'] = EEH_Template::display_template(
376
+			GEN_SET_TEMPLATE_PATH . 'espresso_page_settings.template.php',
377
+			$this->_template_args,
378
+			true
379
+		);
380
+		$this->display_admin_page_with_sidebar();
381
+	}
382
+
383
+
384
+	/**
385
+	 * Handler for updating espresso page settings.
386
+	 *
387
+	 * @throws EE_Error
388
+	 */
389
+	protected function _update_espresso_page_settings()
390
+	{
391
+		$this->core_config = EE_Registry::instance()->CFG->core;
392
+		// capture incoming request data && set page IDs
393
+		$this->core_config->reg_page_id       = $this->request->getRequestParam(
394
+			'reg_page_id',
395
+			$this->core_config->reg_page_id,
396
+			DataType::INT
397
+		);
398
+		$this->core_config->txn_page_id       = $this->request->getRequestParam(
399
+			'txn_page_id',
400
+			$this->core_config->txn_page_id,
401
+			DataType::INT
402
+		);
403
+		$this->core_config->thank_you_page_id = $this->request->getRequestParam(
404
+			'thank_you_page_id',
405
+			$this->core_config->thank_you_page_id,
406
+			DataType::INT
407
+		);
408
+		$this->core_config->cancel_page_id    = $this->request->getRequestParam(
409
+			'cancel_page_id',
410
+			$this->core_config->cancel_page_id,
411
+			DataType::INT
412
+		);
413
+
414
+		$this->core_config = apply_filters(
415
+			'FHEE__General_Settings_Admin_Page___update_espresso_page_settings__CFG_core',
416
+			$this->core_config,
417
+			$this->request->requestParams()
418
+		);
419
+
420
+		$what = esc_html__('Critical Pages & Shortcodes', 'event_espresso');
421
+		$this->_redirect_after_action(
422
+			$this->_update_espresso_configuration(
423
+				$what,
424
+				$this->core_config,
425
+				__FILE__,
426
+				__FUNCTION__,
427
+				__LINE__
428
+			),
429
+			$what,
430
+			'',
431
+			[
432
+				'action' => 'critical_pages',
433
+			],
434
+			true
435
+		);
436
+	}
437
+
438
+
439
+	/*************        Your Organization        *************/
440
+
441
+
442
+	/**
443
+	 * @throws DomainException
444
+	 * @throws EE_Error
445
+	 * @throws InvalidArgumentException
446
+	 * @throws InvalidDataTypeException
447
+	 * @throws InvalidInterfaceException
448
+	 */
449
+	protected function _your_organization_settings()
450
+	{
451
+		$this->_template_args['admin_page_content'] = '';
452
+		try {
453
+			/** @var OrganizationSettings $organization_settings_form */
454
+			$organization_settings_form = $this->loader->getShared(OrganizationSettings::class);
455
+
456
+			$this->_template_args['admin_page_content'] = EEH_HTML::div(
457
+				$organization_settings_form->display(),
458
+				'',
459
+				'padding'
460
+			);
461
+		} catch (Exception $e) {
462
+			EE_Error::add_error($e->getMessage(), __FILE__, __FUNCTION__, __LINE__);
463
+		}
464
+		$this->_set_add_edit_form_tags('update_your_organization_settings');
465
+		$this->_set_publish_post_box_vars(null, false, false, null, false);
466
+		$this->display_admin_page_with_sidebar();
467
+	}
468
+
469
+
470
+	/**
471
+	 * Handler for updating organization settings.
472
+	 *
473
+	 * @throws EE_Error
474
+	 */
475
+	protected function _update_your_organization_settings()
476
+	{
477
+		try {
478
+			/** @var OrganizationSettings $organization_settings_form */
479
+			$organization_settings_form = $this->loader->getShared(OrganizationSettings::class);
480
+
481
+			$success = $organization_settings_form->process($this->request->requestParams());
482
+
483
+			EE_Registry::instance()->CFG = apply_filters(
484
+				'FHEE__General_Settings_Admin_Page___update_your_organization_settings__CFG',
485
+				EE_Registry::instance()->CFG
486
+			);
487
+		} catch (Exception $e) {
488
+			EE_Error::add_error($e->getMessage(), __FILE__, __FUNCTION__, __LINE__);
489
+			$success = false;
490
+		}
491
+
492
+		if ($success) {
493
+			$success = $this->_update_espresso_configuration(
494
+				esc_html__('Your Organization Settings', 'event_espresso'),
495
+				EE_Registry::instance()->CFG,
496
+				__FILE__,
497
+				__FUNCTION__,
498
+				__LINE__
499
+			);
500
+		}
501
+
502
+		$this->_redirect_after_action($success, '', '', ['action' => 'default'], true);
503
+	}
504
+
505
+
506
+
507
+	/*************        Admin Options        *************/
508
+
509
+
510
+	/**
511
+	 * _admin_option_settings
512
+	 *
513
+	 * @throws EE_Error
514
+	 * @throws LogicException
515
+	 */
516
+	protected function _admin_option_settings()
517
+	{
518
+		$this->_template_args['admin_page_content'] = '';
519
+		try {
520
+			$admin_options_settings_form = new AdminOptionsSettings(EE_Registry::instance());
521
+			// still need this for the old school form in Extend_General_Settings_Admin_Page
522
+			$this->_template_args['values'] = $this->_yes_no_values;
523
+			// also need to account for the do_action that was in the old template
524
+			$admin_options_settings_form->setTemplateArgs($this->_template_args);
525
+			$this->_template_args['admin_page_content'] = EEH_HTML::div(
526
+				$admin_options_settings_form->display(),
527
+				'',
528
+				'padding'
529
+			);
530
+		} catch (Exception $e) {
531
+			EE_Error::add_error($e->getMessage(), __FILE__, __FUNCTION__, __LINE__);
532
+		}
533
+		$this->_set_add_edit_form_tags('update_admin_option_settings');
534
+		$this->_set_publish_post_box_vars(null, false, false, null, false);
535
+		$this->display_admin_page_with_sidebar();
536
+	}
537
+
538
+
539
+	/**
540
+	 * _update_admin_option_settings
541
+	 *
542
+	 * @throws EE_Error
543
+	 * @throws InvalidDataTypeException
544
+	 * @throws InvalidFormSubmissionException
545
+	 * @throws InvalidArgumentException
546
+	 * @throws LogicException
547
+	 */
548
+	protected function _update_admin_option_settings()
549
+	{
550
+		try {
551
+			$admin_options_settings_form = new AdminOptionsSettings(EE_Registry::instance());
552
+			$admin_options_settings_form->process(
553
+				$this->request->getRequestParam(
554
+					$admin_options_settings_form->slug(),
555
+					[],
556
+					DataType::STRING,
557
+					true
558
+				)
559
+			);
560
+			EE_Registry::instance()->CFG->admin = apply_filters(
561
+				'FHEE__General_Settings_Admin_Page___update_admin_option_settings__CFG_admin',
562
+				EE_Registry::instance()->CFG->admin
563
+			);
564
+		} catch (Exception $e) {
565
+			EE_Error::add_error($e->getMessage(), __FILE__, __FUNCTION__, __LINE__);
566
+		}
567
+		$this->_redirect_after_action(
568
+			apply_filters(
569
+				'FHEE__General_Settings_Admin_Page___update_admin_option_settings__success',
570
+				$this->_update_espresso_configuration(
571
+					esc_html__('Admin Options', 'event_espresso'),
572
+					EE_Registry::instance()->CFG->admin,
573
+					__FILE__,
574
+					__FUNCTION__,
575
+					__LINE__
576
+				)
577
+			),
578
+			esc_html__('Admin Options', 'event_espresso'),
579
+			'updated',
580
+			['action' => 'admin_option_settings']
581
+		);
582
+	}
583
+
584
+
585
+	/*************        Countries        *************/
586
+
587
+
588
+	/**
589
+	 * @param string|null $default
590
+	 * @return string
591
+	 */
592
+	protected function getCountryISO(?string $default = null): string
593
+	{
594
+		$default = $default ?? $this->getCountryIsoForSite();
595
+		$CNT_ISO = $this->request->getRequestParam('country', $default);
596
+		$CNT_ISO = $this->request->getRequestParam('CNT_ISO', $CNT_ISO);
597
+		return strtoupper($CNT_ISO);
598
+	}
599
+
600
+
601
+	/**
602
+	 * @return string
603
+	 */
604
+	protected function getCountryIsoForSite(): string
605
+	{
606
+		return ! empty(EE_Registry::instance()->CFG->organization->CNT_ISO)
607
+			? EE_Registry::instance()->CFG->organization->CNT_ISO
608
+			: 'US';
609
+	}
610
+
611
+
612
+	/**
613
+	 * @param string          $CNT_ISO
614
+	 * @param EE_Country|null $country
615
+	 * @return EE_Base_Class|EE_Country
616
+	 * @throws EE_Error
617
+	 * @throws InvalidArgumentException
618
+	 * @throws InvalidDataTypeException
619
+	 * @throws InvalidInterfaceException
620
+	 * @throws ReflectionException
621
+	 */
622
+	protected function verifyOrGetCountryFromIso(string $CNT_ISO, ?EE_Country $country = null)
623
+	{
624
+		/** @var EE_Country $country */
625
+		return $country instanceof EE_Country && $country->ID() === $CNT_ISO
626
+			? $country
627
+			: EEM_Country::instance()->get_one_by_ID($CNT_ISO);
628
+	}
629
+
630
+
631
+	/**
632
+	 * Output Country Settings view.
633
+	 *
634
+	 * @throws DomainException
635
+	 * @throws EE_Error
636
+	 * @throws InvalidArgumentException
637
+	 * @throws InvalidDataTypeException
638
+	 * @throws InvalidInterfaceException
639
+	 * @throws ReflectionException
640
+	 */
641
+	protected function _country_settings()
642
+	{
643
+		$CNT_ISO = $this->getCountryISO();
644
+
645
+		$this->_template_args['values']    = $this->_yes_no_values;
646
+		$this->_template_args['countries'] = new EE_Question_Form_Input(
647
+			EE_Question::new_instance(
648
+				[
649
+				  'QST_ID'           => 0,
650
+				  'QST_display_text' => esc_html__('Select Country', 'event_espresso'),
651
+				  'QST_system'       => 'admin-country',
652
+				]
653
+			),
654
+			EE_Answer::new_instance(
655
+				[
656
+					'ANS_ID'    => 0,
657
+					'ANS_value' => $CNT_ISO,
658
+				]
659
+			),
660
+			[
661
+				'input_id'       => 'country',
662
+				'input_name'     => 'country',
663
+				'input_prefix'   => '',
664
+				'append_qstn_id' => false,
665
+			]
666
+		);
667
+
668
+		$country = $this->verifyOrGetCountryFromIso($CNT_ISO);
669
+		add_filter('FHEE__EEH_Form_Fields__label_html', [$this, 'country_form_field_label_wrap'], 10);
670
+		add_filter('FHEE__EEH_Form_Fields__input_html', [$this, 'country_form_field_input__wrap'], 10);
671
+		$this->_template_args['country_details_settings'] = $this->display_country_settings(
672
+			$country->ID(),
673
+			$country
674
+		);
675
+		$this->_template_args['country_states_settings']  = $this->display_country_states(
676
+			$country->ID(),
677
+			$country
678
+		);
679
+		$this->_template_args['CNT_name_for_site']        = $country->name();
680
+
681
+		$this->_set_add_edit_form_tags('update_country_settings');
682
+		$this->_set_publish_post_box_vars(null, false, false, null, false);
683
+		$this->_template_args['admin_page_content'] = EEH_Template::display_template(
684
+			GEN_SET_TEMPLATE_PATH . 'countries_settings.template.php',
685
+			$this->_template_args,
686
+			true
687
+		);
688
+		$this->display_admin_page_with_no_sidebar();
689
+	}
690
+
691
+
692
+	/**
693
+	 * @param string          $CNT_ISO
694
+	 * @param EE_Country|null $country
695
+	 * @return string
696
+	 * @throws DomainException
697
+	 * @throws EE_Error
698
+	 * @throws InvalidArgumentException
699
+	 * @throws InvalidDataTypeException
700
+	 * @throws InvalidInterfaceException
701
+	 * @throws ReflectionException
702
+	 */
703
+	public function display_country_settings(string $CNT_ISO = '', ?EE_Country $country = null): string
704
+	{
705
+		$CNT_ISO          = $this->getCountryISO($CNT_ISO);
706
+		$CNT_ISO_for_site = $this->getCountryIsoForSite();
707
+
708
+		if (! $CNT_ISO) {
709
+			return '';
710
+		}
711
+
712
+		// for ajax
713
+		remove_all_filters('FHEE__EEH_Form_Fields__label_html');
714
+		remove_all_filters('FHEE__EEH_Form_Fields__input_html');
715
+		add_filter('FHEE__EEH_Form_Fields__label_html', [$this, 'country_form_field_label_wrap'], 10, 2);
716
+		add_filter('FHEE__EEH_Form_Fields__input_html', [$this, 'country_form_field_input__wrap'], 10, 2);
717
+		$country                                  = $this->verifyOrGetCountryFromIso($CNT_ISO, $country);
718
+		$CNT_cur_disabled                         = $CNT_ISO !== $CNT_ISO_for_site;
719
+		$this->_template_args['CNT_cur_disabled'] = $CNT_cur_disabled;
720
+
721
+		$country_input_types            = [
722
+			'CNT_active'      => [
723
+				'type'             => 'RADIO_BTN',
724
+				'input_name'       => "cntry[$CNT_ISO]",
725
+				'class'            => '',
726
+				'options'          => $this->_yes_no_values,
727
+				'use_desc_4_label' => true,
728
+			],
729
+			'CNT_ISO'         => [
730
+				'type'       => 'TEXT',
731
+				'input_name' => "cntry[$CNT_ISO]",
732
+				'class'      => 'ee-input-width--small',
733
+			],
734
+			'CNT_ISO3'        => [
735
+				'type'       => 'TEXT',
736
+				'input_name' => "cntry[$CNT_ISO]",
737
+				'class'      => 'ee-input-width--small',
738
+			],
739
+			// 'RGN_ID'          => [
740
+			//     'type'       => 'TEXT',
741
+			//     'input_name' => "cntry[$CNT_ISO]",
742
+			//     'class'      => 'ee-input-width--small',
743
+			// ],
744
+			'CNT_name'        => [
745
+				'type'       => 'TEXT',
746
+				'input_name' => "cntry[$CNT_ISO]",
747
+				'class'      => 'ee-input-width--big',
748
+			],
749
+			'CNT_cur_code'    => [
750
+				'type'       => 'TEXT',
751
+				'input_name' => "cntry[$CNT_ISO]",
752
+				'class'      => 'ee-input-width--small',
753
+				'disabled'   => $CNT_cur_disabled,
754
+			],
755
+			'CNT_cur_single'  => [
756
+				'type'       => 'TEXT',
757
+				'input_name' => "cntry[$CNT_ISO]",
758
+				'class'      => 'ee-input-width--reg',
759
+				'disabled'   => $CNT_cur_disabled,
760
+			],
761
+			'CNT_cur_plural'  => [
762
+				'type'       => 'TEXT',
763
+				'input_name' => "cntry[$CNT_ISO]",
764
+				'class'      => 'ee-input-width--reg',
765
+				'disabled'   => $CNT_cur_disabled,
766
+			],
767
+			'CNT_cur_sign'    => [
768
+				'type'         => 'TEXT',
769
+				'input_name'   => "cntry[$CNT_ISO]",
770
+				'class'        => 'ee-input-width--small',
771
+				'htmlentities' => false,
772
+				'disabled'     => $CNT_cur_disabled,
773
+			],
774
+			'CNT_cur_sign_b4' => [
775
+				'type'             => 'RADIO_BTN',
776
+				'input_name'       => "cntry[$CNT_ISO]",
777
+				'class'            => '',
778
+				'options'          => $this->_yes_no_values,
779
+				'use_desc_4_label' => true,
780
+				'disabled'         => $CNT_cur_disabled,
781
+			],
782
+			'CNT_cur_dec_plc' => [
783
+				'type'       => 'RADIO_BTN',
784
+				'input_name' => "cntry[$CNT_ISO]",
785
+				'class'      => '',
786
+				'options'    => [
787
+					['id' => 0, 'text' => ''],
788
+					['id' => 1, 'text' => ''],
789
+					['id' => 2, 'text' => ''],
790
+					['id' => 3, 'text' => ''],
791
+				],
792
+				'disabled'   => $CNT_cur_disabled,
793
+			],
794
+			'CNT_cur_dec_mrk' => [
795
+				'type'             => 'RADIO_BTN',
796
+				'input_name'       => "cntry[$CNT_ISO]",
797
+				'class'            => '',
798
+				'options'          => [
799
+					[
800
+						'id'   => ',',
801
+						'text' => esc_html__(', (comma)', 'event_espresso'),
802
+					],
803
+					['id' => '.', 'text' => esc_html__('. (decimal)', 'event_espresso')],
804
+				],
805
+				'use_desc_4_label' => true,
806
+				'disabled'         => $CNT_cur_disabled,
807
+			],
808
+			'CNT_cur_thsnds'  => [
809
+				'type'             => 'RADIO_BTN',
810
+				'input_name'       => "cntry[$CNT_ISO]",
811
+				'class'            => '',
812
+				'options'          => [
813
+					[
814
+						'id'   => ',',
815
+						'text' => esc_html__(', (comma)', 'event_espresso'),
816
+					],
817
+					[
818
+						'id'   => '.',
819
+						'text' => esc_html__('. (decimal)', 'event_espresso'),
820
+					],
821
+					[
822
+						'id'   => '&nbsp;',
823
+						'text' => esc_html__('(space)', 'event_espresso'),
824
+					],
825
+					[
826
+						'id'   => '_',
827
+						'text' => esc_html__('_ (underscore)', 'event_espresso'),
828
+					],
829
+					[
830
+						'id'   => "'",
831
+						'text' => esc_html__("' (apostrophe)", 'event_espresso'),
832
+					],
833
+				],
834
+				'use_desc_4_label' => true,
835
+				'disabled'         => $CNT_cur_disabled,
836
+			],
837
+			'CNT_tel_code'    => [
838
+				'type'       => 'TEXT',
839
+				'input_name' => "cntry[$CNT_ISO]",
840
+				'class'      => 'ee-input-width--small',
841
+			],
842
+			'CNT_is_EU'       => [
843
+				'type'             => 'RADIO_BTN',
844
+				'input_name'       => "cntry[$CNT_ISO]",
845
+				'class'            => '',
846
+				'options'          => $this->_yes_no_values,
847
+				'use_desc_4_label' => true,
848
+			],
849
+		];
850
+		$this->_template_args['inputs'] = EE_Question_Form_Input::generate_question_form_inputs_for_object(
851
+			$country,
852
+			$country_input_types
853
+		);
854
+		$country_details_settings       = EEH_Template::display_template(
855
+			GEN_SET_TEMPLATE_PATH . 'country_details_settings.template.php',
856
+			$this->_template_args,
857
+			true
858
+		);
859
+
860
+		if (defined('DOING_AJAX')) {
861
+			$notices = EE_Error::get_notices(false, false, false);
862
+			echo wp_json_encode(
863
+				[
864
+					'return_data' => $country_details_settings,
865
+					'success'     => $notices['success'],
866
+					'errors'      => $notices['errors'],
867
+				]
868
+			);
869
+			die();
870
+		}
871
+		return $country_details_settings;
872
+	}
873
+
874
+
875
+	/**
876
+	 * @param string          $CNT_ISO
877
+	 * @param EE_Country|null $country
878
+	 * @return string
879
+	 * @throws DomainException
880
+	 * @throws EE_Error
881
+	 * @throws InvalidArgumentException
882
+	 * @throws InvalidDataTypeException
883
+	 * @throws InvalidInterfaceException
884
+	 * @throws ReflectionException
885
+	 */
886
+	public function display_country_states(string $CNT_ISO = '', ?EE_Country $country = null): string
887
+	{
888
+		$CNT_ISO = $this->getCountryISO($CNT_ISO);
889
+		if (! $CNT_ISO) {
890
+			return '';
891
+		}
892
+		// for ajax
893
+		remove_all_filters('FHEE__EEH_Form_Fields__label_html');
894
+		remove_all_filters('FHEE__EEH_Form_Fields__input_html');
895
+		add_filter('FHEE__EEH_Form_Fields__label_html', [$this, 'state_form_field_label_wrap'], 10, 2);
896
+		add_filter('FHEE__EEH_Form_Fields__input_html', [$this, 'state_form_field_input__wrap'], 10);
897
+		$states = EEM_State::instance()->get_all_states_for_these_countries([$CNT_ISO => $CNT_ISO]);
898
+		if (empty($states)) {
899
+			/** @var EventEspresso\core\services\address\CountrySubRegionDao $countrySubRegionDao */
900
+			$countrySubRegionDao = $this->loader->getShared(
901
+				'EventEspresso\core\services\address\CountrySubRegionDao'
902
+			);
903
+			if ($countrySubRegionDao instanceof EventEspresso\core\services\address\CountrySubRegionDao) {
904
+				$country = $this->verifyOrGetCountryFromIso($CNT_ISO, $country);
905
+				if ($countrySubRegionDao->saveCountrySubRegions($country)) {
906
+					$states = EEM_State::instance()->get_all_states_for_these_countries([$CNT_ISO => $CNT_ISO]);
907
+				}
908
+			}
909
+		}
910
+		if (is_array($states)) {
911
+			foreach ($states as $STA_ID => $state) {
912
+				if ($state instanceof EE_State) {
913
+					$inputs = EE_Question_Form_Input::generate_question_form_inputs_for_object(
914
+						$state,
915
+						[
916
+							'STA_abbrev' => [
917
+								'type'             => 'TEXT',
918
+								'label'            => esc_html__('Code', 'event_espresso'),
919
+								'input_name'       => "states[$STA_ID]",
920
+								'class'            => 'ee-input-width--tiny',
921
+								'add_mobile_label' => true,
922
+							],
923
+							'STA_name'   => [
924
+								'type'             => 'TEXT',
925
+								'label'            => esc_html__('Name', 'event_espresso'),
926
+								'input_name'       => "states[$STA_ID]",
927
+								'class'            => 'ee-input-width--big',
928
+								'add_mobile_label' => true,
929
+							],
930
+							'STA_active' => [
931
+								'type'             => 'RADIO_BTN',
932
+								'label'            => esc_html__(
933
+									'State Appears in Dropdown Select Lists',
934
+									'event_espresso'
935
+								),
936
+								'input_name'       => "states[$STA_ID]",
937
+								'options'          => $this->_yes_no_values,
938
+								'use_desc_4_label' => true,
939
+								'add_mobile_label' => true,
940
+							],
941
+						]
942
+					);
943
+
944
+					$delete_state_url = EE_Admin_Page::add_query_args_and_nonce(
945
+						[
946
+							'action'     => 'delete_state',
947
+							'STA_ID'     => $STA_ID,
948
+							'CNT_ISO'    => $CNT_ISO,
949
+							'STA_abbrev' => $state->abbrev(),
950
+						],
951
+						GEN_SET_ADMIN_URL
952
+					);
953
+
954
+					$this->_template_args['states'][ $STA_ID ]['inputs']           = $inputs;
955
+					$this->_template_args['states'][ $STA_ID ]['delete_state_url'] = $delete_state_url;
956
+				}
957
+			}
958
+		} else {
959
+			$this->_template_args['states'] = false;
960
+		}
961
+
962
+		$this->_template_args['add_new_state_url'] = EE_Admin_Page::add_query_args_and_nonce(
963
+			['action' => 'add_new_state'],
964
+			GEN_SET_ADMIN_URL
965
+		);
966
+
967
+		$state_details_settings = EEH_Template::display_template(
968
+			GEN_SET_TEMPLATE_PATH . 'state_details_settings.template.php',
969
+			$this->_template_args,
970
+			true
971
+		);
972
+
973
+		if (defined('DOING_AJAX')) {
974
+			$notices = EE_Error::get_notices(false, false, false);
975
+			echo wp_json_encode(
976
+				[
977
+					'return_data' => $state_details_settings,
978
+					'success'     => $notices['success'],
979
+					'errors'      => $notices['errors'],
980
+				]
981
+			);
982
+			die();
983
+		}
984
+		return $state_details_settings;
985
+	}
986
+
987
+
988
+	/**
989
+	 * @return void
990
+	 * @throws EE_Error
991
+	 * @throws InvalidArgumentException
992
+	 * @throws InvalidDataTypeException
993
+	 * @throws InvalidInterfaceException
994
+	 * @throws ReflectionException
995
+	 */
996
+	public function add_new_state()
997
+	{
998
+		$success = true;
999
+		$CNT_ISO = $this->getCountryISO('');
1000
+		if (! $CNT_ISO) {
1001
+			EE_Error::add_error(
1002
+				esc_html__('No Country ISO code or an invalid Country ISO code was received.', 'event_espresso'),
1003
+				__FILE__,
1004
+				__FUNCTION__,
1005
+				__LINE__
1006
+			);
1007
+			$success = false;
1008
+		}
1009
+		$STA_abbrev = $this->request->getRequestParam('STA_abbrev');
1010
+		if (! $STA_abbrev) {
1011
+			EE_Error::add_error(
1012
+				esc_html__('No State ISO code or an invalid State ISO code was received.', 'event_espresso'),
1013
+				__FILE__,
1014
+				__FUNCTION__,
1015
+				__LINE__
1016
+			);
1017
+			$success = false;
1018
+		}
1019
+		$STA_name = $this->request->getRequestParam('STA_name');
1020
+		if (! $STA_name) {
1021
+			EE_Error::add_error(
1022
+				esc_html__('No State name or an invalid State name was received.', 'event_espresso'),
1023
+				__FILE__,
1024
+				__FUNCTION__,
1025
+				__LINE__
1026
+			);
1027
+			$success = false;
1028
+		}
1029
+
1030
+		if ($success) {
1031
+			$cols_n_values = [
1032
+				'CNT_ISO'    => $CNT_ISO,
1033
+				'STA_abbrev' => $STA_abbrev,
1034
+				'STA_name'   => $STA_name,
1035
+				'STA_active' => true,
1036
+			];
1037
+			$success       = EEM_State::instance()->insert($cols_n_values);
1038
+			EE_Error::add_success(esc_html__('The State was added successfully.', 'event_espresso'));
1039
+		}
1040
+
1041
+		if (defined('DOING_AJAX')) {
1042
+			$notices = EE_Error::get_notices(false, false, false);
1043
+			echo wp_json_encode(array_merge($notices, ['return_data' => $CNT_ISO]));
1044
+			die();
1045
+		}
1046
+		$this->_redirect_after_action(
1047
+			$success,
1048
+			esc_html__('State', 'event_espresso'),
1049
+			'added',
1050
+			['action' => 'country_settings']
1051
+		);
1052
+	}
1053
+
1054
+
1055
+	/**
1056
+	 * @return void
1057
+	 * @throws EE_Error
1058
+	 * @throws InvalidArgumentException
1059
+	 * @throws InvalidDataTypeException
1060
+	 * @throws InvalidInterfaceException
1061
+	 * @throws ReflectionException
1062
+	 */
1063
+	public function delete_state()
1064
+	{
1065
+		$CNT_ISO    = $this->getCountryISO();
1066
+		$STA_ID     = $this->request->getRequestParam('STA_ID');
1067
+		$STA_abbrev = $this->request->getRequestParam('STA_abbrev');
1068
+
1069
+		if (! $STA_ID) {
1070
+			EE_Error::add_error(
1071
+				esc_html__('No State ID or an invalid State ID was received.', 'event_espresso'),
1072
+				__FILE__,
1073
+				__FUNCTION__,
1074
+				__LINE__
1075
+			);
1076
+			return;
1077
+		}
1078
+
1079
+		$success = EEM_State::instance()->delete_by_ID($STA_ID);
1080
+		if ($success !== false) {
1081
+			do_action(
1082
+				'AHEE__General_Settings_Admin_Page__delete_state__state_deleted',
1083
+				$CNT_ISO,
1084
+				$STA_ID,
1085
+				['STA_abbrev' => $STA_abbrev]
1086
+			);
1087
+			EE_Error::add_success(esc_html__('The State was deleted successfully.', 'event_espresso'));
1088
+		}
1089
+		if (defined('DOING_AJAX')) {
1090
+			$notices                = EE_Error::get_notices(false);
1091
+			$notices['return_data'] = true;
1092
+			echo wp_json_encode($notices);
1093
+			die();
1094
+		}
1095
+		$this->_redirect_after_action(
1096
+			$success,
1097
+			esc_html__('State', 'event_espresso'),
1098
+			'deleted',
1099
+			['action' => 'country_settings']
1100
+		);
1101
+	}
1102
+
1103
+
1104
+	/**
1105
+	 * @return void
1106
+	 * @throws EE_Error
1107
+	 * @throws InvalidArgumentException
1108
+	 * @throws InvalidDataTypeException
1109
+	 * @throws InvalidInterfaceException
1110
+	 * @throws ReflectionException
1111
+	 */
1112
+	protected function _update_country_settings()
1113
+	{
1114
+		$CNT_ISO = $this->getCountryISO();
1115
+		if (! $CNT_ISO) {
1116
+			EE_Error::add_error(
1117
+				esc_html__('No Country ISO code or an invalid Country ISO code was received.', 'event_espresso'),
1118
+				__FILE__,
1119
+				__FUNCTION__,
1120
+				__LINE__
1121
+			);
1122
+			return;
1123
+		}
1124
+
1125
+		$country = $this->verifyOrGetCountryFromIso($CNT_ISO);
1126
+
1127
+		$cols_n_values                    = [];
1128
+		$cols_n_values['CNT_ISO3']        = strtoupper(
1129
+			$this->request->getRequestParam(
1130
+				"cntry[$CNT_ISO][CNT_ISO3]",
1131
+				$country->ISO3()
1132
+			)
1133
+		);
1134
+		$cols_n_values['CNT_name']        = $this->request->getRequestParam(
1135
+			"cntry[$CNT_ISO][CNT_name]",
1136
+			$country->name()
1137
+		);
1138
+		$cols_n_values['CNT_cur_code']    = strtoupper(
1139
+			$this->request->getRequestParam(
1140
+				"cntry[$CNT_ISO][CNT_cur_code]",
1141
+				$country->currency_code()
1142
+			)
1143
+		);
1144
+		$cols_n_values['CNT_cur_single']  = $this->request->getRequestParam(
1145
+			"cntry[$CNT_ISO][CNT_cur_single]",
1146
+			$country->currency_name_single()
1147
+		);
1148
+		$cols_n_values['CNT_cur_plural']  = $this->request->getRequestParam(
1149
+			"cntry[$CNT_ISO][CNT_cur_plural]",
1150
+			$country->currency_name_plural()
1151
+		);
1152
+		$cols_n_values['CNT_cur_sign']    = $this->request->getRequestParam(
1153
+			"cntry[$CNT_ISO][CNT_cur_sign]",
1154
+			$country->currency_sign()
1155
+		);
1156
+		$cols_n_values['CNT_cur_sign_b4'] = $this->request->getRequestParam(
1157
+			"cntry[$CNT_ISO][CNT_cur_sign_b4]",
1158
+			$country->currency_sign_before(),
1159
+			DataType::BOOL
1160
+		);
1161
+		$cols_n_values['CNT_cur_dec_plc'] = $this->request->getRequestParam(
1162
+			"cntry[$CNT_ISO][CNT_cur_dec_plc]",
1163
+			$country->currency_decimal_places()
1164
+		);
1165
+		$cols_n_values['CNT_cur_dec_mrk'] = $this->request->getRequestParam(
1166
+			"cntry[$CNT_ISO][CNT_cur_dec_mrk]",
1167
+			$country->currency_decimal_mark()
1168
+		);
1169
+		$cols_n_values['CNT_cur_thsnds']  = $this->request->getRequestParam(
1170
+			"cntry[$CNT_ISO][CNT_cur_thsnds]",
1171
+			$country->currency_thousands_separator()
1172
+		);
1173
+		$cols_n_values['CNT_tel_code']    = $this->request->getRequestParam(
1174
+			"cntry[$CNT_ISO][CNT_tel_code]",
1175
+			$country->telephoneCode()
1176
+		);
1177
+		$cols_n_values['CNT_active']      = $this->request->getRequestParam(
1178
+			"cntry[$CNT_ISO][CNT_active]",
1179
+			$country->isActive(),
1180
+			DataType::BOOL
1181
+		);
1182
+
1183
+		// allow filtering of country data
1184
+		$cols_n_values = apply_filters(
1185
+			'FHEE__General_Settings_Admin_Page___update_country_settings__cols_n_values',
1186
+			$cols_n_values
1187
+		);
1188
+
1189
+		// where values
1190
+		$where_cols_n_values = [['CNT_ISO' => $CNT_ISO]];
1191
+		// run the update
1192
+		$success = EEM_Country::instance()->update($cols_n_values, $where_cols_n_values);
1193
+
1194
+		// allow filtering of states data
1195
+		$states = apply_filters(
1196
+			'FHEE__General_Settings_Admin_Page___update_country_settings__states',
1197
+			$this->request->getRequestParam('states', [], DataType::STRING, true)
1198
+		);
1199
+
1200
+		if (! empty($states) && $success !== false) {
1201
+			// loop thru state data ( looks like : states[75][STA_name] )
1202
+			foreach ($states as $STA_ID => $state) {
1203
+				$cols_n_values = [
1204
+					'CNT_ISO'    => $CNT_ISO,
1205
+					'STA_abbrev' => sanitize_text_field($state['STA_abbrev']),
1206
+					'STA_name'   => sanitize_text_field($state['STA_name']),
1207
+					'STA_active' => filter_var($state['STA_active'], FILTER_VALIDATE_BOOLEAN),
1208
+				];
1209
+				// where values
1210
+				$where_cols_n_values = [['STA_ID' => $STA_ID]];
1211
+				// run the update
1212
+				$success = EEM_State::instance()->update($cols_n_values, $where_cols_n_values);
1213
+				if ($success !== false) {
1214
+					do_action(
1215
+						'AHEE__General_Settings_Admin_Page__update_country_settings__state_saved',
1216
+						$CNT_ISO,
1217
+						$STA_ID,
1218
+						$cols_n_values
1219
+					);
1220
+				}
1221
+			}
1222
+		}
1223
+		// check if country being edited matches org option country, and if so, then  update EE_Config with new settings
1224
+		if (
1225
+			isset(EE_Registry::instance()->CFG->organization->CNT_ISO)
1226
+			&& $CNT_ISO == EE_Registry::instance()->CFG->organization->CNT_ISO
1227
+		) {
1228
+			EE_Registry::instance()->CFG->currency = new EE_Currency_Config($CNT_ISO);
1229
+			EE_Registry::instance()->CFG->update_espresso_config();
1230
+		}
1231
+
1232
+		if ($success !== false) {
1233
+			EE_Error::add_success(
1234
+				esc_html__('Country Settings updated successfully.', 'event_espresso')
1235
+			);
1236
+		}
1237
+		$this->_redirect_after_action(
1238
+			$success,
1239
+			'',
1240
+			'',
1241
+			['action' => 'country_settings', 'country' => $CNT_ISO],
1242
+			true
1243
+		);
1244
+	}
1245
+
1246
+
1247
+	/**
1248
+	 * form_form_field_label_wrap
1249
+	 *
1250
+	 * @param string $label
1251
+	 * @return string
1252
+	 */
1253
+	public function country_form_field_label_wrap(string $label): string
1254
+	{
1255
+		return '
1256 1256
 			<tr>
1257 1257
 				<th>
1258 1258
 					' . $label . '
1259 1259
 				</th>';
1260
-    }
1261
-
1262
-
1263
-    /**
1264
-     * form_form_field_input__wrap
1265
-     *
1266
-     * @param string $input
1267
-     * @return string
1268
-     */
1269
-    public function country_form_field_input__wrap(string $input): string
1270
-    {
1271
-        return '
1260
+	}
1261
+
1262
+
1263
+	/**
1264
+	 * form_form_field_input__wrap
1265
+	 *
1266
+	 * @param string $input
1267
+	 * @return string
1268
+	 */
1269
+	public function country_form_field_input__wrap(string $input): string
1270
+	{
1271
+		return '
1272 1272
 				<td class="general-settings-country-input-td">
1273 1273
 					' . $input . '
1274 1274
 				</td>
1275 1275
 			</tr>';
1276
-    }
1277
-
1278
-
1279
-    /**
1280
-     * form_form_field_label_wrap
1281
-     *
1282
-     * @param string $label
1283
-     * @param string $required_text
1284
-     * @return string
1285
-     */
1286
-    public function state_form_field_label_wrap(string $label, string $required_text): string
1287
-    {
1288
-        return $required_text;
1289
-    }
1290
-
1291
-
1292
-    /**
1293
-     * form_form_field_input__wrap
1294
-     *
1295
-     * @param string $input
1296
-     * @return string
1297
-     */
1298
-    public function state_form_field_input__wrap(string $input): string
1299
-    {
1300
-        return '
1276
+	}
1277
+
1278
+
1279
+	/**
1280
+	 * form_form_field_label_wrap
1281
+	 *
1282
+	 * @param string $label
1283
+	 * @param string $required_text
1284
+	 * @return string
1285
+	 */
1286
+	public function state_form_field_label_wrap(string $label, string $required_text): string
1287
+	{
1288
+		return $required_text;
1289
+	}
1290
+
1291
+
1292
+	/**
1293
+	 * form_form_field_input__wrap
1294
+	 *
1295
+	 * @param string $input
1296
+	 * @return string
1297
+	 */
1298
+	public function state_form_field_input__wrap(string $input): string
1299
+	{
1300
+		return '
1301 1301
 				<td class="general-settings-country-state-input-td">
1302 1302
 					' . $input . '
1303 1303
 				</td>';
1304
-    }
1305
-
1306
-
1307
-    /***********/
1308
-
1309
-
1310
-    /**
1311
-     * displays edit and view links for critical EE pages
1312
-     *
1313
-     * @param int $ee_page_id
1314
-     * @return string
1315
-     */
1316
-    public static function edit_view_links(int $ee_page_id): string
1317
-    {
1318
-        $edit_url = add_query_arg(
1319
-            ['post' => $ee_page_id, 'action' => 'edit'],
1320
-            admin_url('post.php')
1321
-        );
1322
-        $links    = '<a href="' . esc_url_raw($edit_url) . '" >' . esc_html__('Edit', 'event_espresso') . '</a>';
1323
-        $links    .= ' &nbsp;|&nbsp; ';
1324
-        $links    .= '<a href="' . get_permalink($ee_page_id) . '" >' . esc_html__('View', 'event_espresso') . '</a>';
1325
-
1326
-        return $links;
1327
-    }
1328
-
1329
-
1330
-    /**
1331
-     * displays page and shortcode status for critical EE pages
1332
-     *
1333
-     * @param WP_Post $ee_page
1334
-     * @param string  $shortcode
1335
-     * @return string
1336
-     */
1337
-    public static function page_and_shortcode_status(WP_Post $ee_page, string $shortcode): string
1338
-    {
1339
-        // page status
1340
-        if (isset($ee_page->post_status) && $ee_page->post_status == 'publish') {
1341
-            $pg_class  = 'ee-status-bg--success';
1342
-            $pg_status = sprintf(esc_html__('Page%sStatus%sOK', 'event_espresso'), '&nbsp;', '&nbsp;');
1343
-        } else {
1344
-            $pg_class  = 'ee-status-bg--error';
1345
-            $pg_status = sprintf(esc_html__('Page%sVisibility%sProblem', 'event_espresso'), '&nbsp;', '&nbsp;');
1346
-        }
1347
-
1348
-        // shortcode status
1349
-        if (isset($ee_page->post_content) && strpos($ee_page->post_content, $shortcode) !== false) {
1350
-            $sc_class  = 'ee-status-bg--success';
1351
-            $sc_status = sprintf(esc_html__('Shortcode%sOK', 'event_espresso'), '&nbsp;');
1352
-        } else {
1353
-            $sc_class  = 'ee-status-bg--error';
1354
-            $sc_status = sprintf(esc_html__('Shortcode%sProblem', 'event_espresso'), '&nbsp;');
1355
-        }
1356
-
1357
-        return '
1304
+	}
1305
+
1306
+
1307
+	/***********/
1308
+
1309
+
1310
+	/**
1311
+	 * displays edit and view links for critical EE pages
1312
+	 *
1313
+	 * @param int $ee_page_id
1314
+	 * @return string
1315
+	 */
1316
+	public static function edit_view_links(int $ee_page_id): string
1317
+	{
1318
+		$edit_url = add_query_arg(
1319
+			['post' => $ee_page_id, 'action' => 'edit'],
1320
+			admin_url('post.php')
1321
+		);
1322
+		$links    = '<a href="' . esc_url_raw($edit_url) . '" >' . esc_html__('Edit', 'event_espresso') . '</a>';
1323
+		$links    .= ' &nbsp;|&nbsp; ';
1324
+		$links    .= '<a href="' . get_permalink($ee_page_id) . '" >' . esc_html__('View', 'event_espresso') . '</a>';
1325
+
1326
+		return $links;
1327
+	}
1328
+
1329
+
1330
+	/**
1331
+	 * displays page and shortcode status for critical EE pages
1332
+	 *
1333
+	 * @param WP_Post $ee_page
1334
+	 * @param string  $shortcode
1335
+	 * @return string
1336
+	 */
1337
+	public static function page_and_shortcode_status(WP_Post $ee_page, string $shortcode): string
1338
+	{
1339
+		// page status
1340
+		if (isset($ee_page->post_status) && $ee_page->post_status == 'publish') {
1341
+			$pg_class  = 'ee-status-bg--success';
1342
+			$pg_status = sprintf(esc_html__('Page%sStatus%sOK', 'event_espresso'), '&nbsp;', '&nbsp;');
1343
+		} else {
1344
+			$pg_class  = 'ee-status-bg--error';
1345
+			$pg_status = sprintf(esc_html__('Page%sVisibility%sProblem', 'event_espresso'), '&nbsp;', '&nbsp;');
1346
+		}
1347
+
1348
+		// shortcode status
1349
+		if (isset($ee_page->post_content) && strpos($ee_page->post_content, $shortcode) !== false) {
1350
+			$sc_class  = 'ee-status-bg--success';
1351
+			$sc_status = sprintf(esc_html__('Shortcode%sOK', 'event_espresso'), '&nbsp;');
1352
+		} else {
1353
+			$sc_class  = 'ee-status-bg--error';
1354
+			$sc_status = sprintf(esc_html__('Shortcode%sProblem', 'event_espresso'), '&nbsp;');
1355
+		}
1356
+
1357
+		return '
1358 1358
         <span class="ee-page-status ' . $pg_class . '"><strong>' . $pg_status . '</strong></span>
1359 1359
         <span class="ee-page-status ' . $sc_class . '"><strong>' . $sc_status . '</strong></span>';
1360
-    }
1361
-
1362
-
1363
-    /**
1364
-     * generates a dropdown of all parent pages - copied from WP core
1365
-     *
1366
-     * @param int  $default
1367
-     * @param int  $parent
1368
-     * @param int  $level
1369
-     * @param bool $echo
1370
-     * @return string;
1371
-     */
1372
-    public static function page_settings_dropdown(
1373
-        int $default = 0,
1374
-        int $parent = 0,
1375
-        int $level = 0,
1376
-        bool $echo = true
1377
-    ): string {
1378
-        global $wpdb;
1379
-        $items  = $wpdb->get_results(
1380
-            $wpdb->prepare(
1381
-                "SELECT ID, post_parent, post_title FROM $wpdb->posts WHERE post_parent = %d AND post_type = 'page' AND post_status != 'trash' ORDER BY menu_order",
1382
-                $parent
1383
-            )
1384
-        );
1385
-        $output = '';
1386
-
1387
-        if ($items) {
1388
-            $level = absint($level);
1389
-            foreach ($items as $item) {
1390
-                $ID         = absint($item->ID);
1391
-                $post_title = wp_strip_all_tags($item->post_title);
1392
-                $pad        = str_repeat('&nbsp;', $level * 3);
1393
-                $option     = "\n\t";
1394
-                $option     .= '<option class="level-' . $level . '" ';
1395
-                $option     .= 'value="' . $ID . '" ';
1396
-                $option     .= $ID === absint($default) ? ' selected' : '';
1397
-                $option     .= '>';
1398
-                $option     .= "$pad {$post_title}";
1399
-                $option     .= '</option>';
1400
-                $output     .= $option;
1401
-                ob_start();
1402
-                parent_dropdown($default, $item->ID, $level + 1);
1403
-                $output .= ob_get_clean();
1404
-            }
1405
-        }
1406
-        if ($echo) {
1407
-            echo wp_kses($output, AllowedTags::getWithFormTags());
1408
-            return '';
1409
-        }
1410
-        return $output;
1411
-    }
1412
-
1413
-
1414
-    /**
1415
-     * Loads the scripts for the privacy settings form
1416
-     */
1417
-    public function load_scripts_styles_privacy_settings()
1418
-    {
1419
-        $form_handler = $this->loader->getShared(
1420
-            'EventEspresso\core\domain\services\admin\privacy\forms\PrivacySettingsFormHandler'
1421
-        );
1422
-        $form_handler->enqueueStylesAndScripts();
1423
-    }
1424
-
1425
-
1426
-    /**
1427
-     * display the privacy settings form
1428
-     *
1429
-     * @throws EE_Error
1430
-     */
1431
-    public function privacySettings()
1432
-    {
1433
-        $this->_set_add_edit_form_tags('update_privacy_settings');
1434
-        $this->_set_publish_post_box_vars(null, false, false, null, false);
1435
-        $form_handler                               = $this->loader->getShared(
1436
-            'EventEspresso\core\domain\services\admin\privacy\forms\PrivacySettingsFormHandler'
1437
-        );
1438
-        $this->_template_args['admin_page_content'] = EEH_HTML::div(
1439
-            $form_handler->display(),
1440
-            '',
1441
-            'padding'
1442
-        );
1443
-        $this->display_admin_page_with_sidebar();
1444
-    }
1445
-
1446
-
1447
-    /**
1448
-     * Update the privacy settings from form data
1449
-     *
1450
-     * @throws EE_Error
1451
-     */
1452
-    public function updatePrivacySettings()
1453
-    {
1454
-        $form_handler = $this->loader->getShared(
1455
-            'EventEspresso\core\domain\services\admin\privacy\forms\PrivacySettingsFormHandler'
1456
-        );
1457
-        $success      = $form_handler->process($this->get_request_data());
1458
-        $this->_redirect_after_action(
1459
-            $success,
1460
-            esc_html__('Registration Form Options', 'event_espresso'),
1461
-            'updated',
1462
-            ['action' => 'privacy_settings']
1463
-        );
1464
-    }
1360
+	}
1361
+
1362
+
1363
+	/**
1364
+	 * generates a dropdown of all parent pages - copied from WP core
1365
+	 *
1366
+	 * @param int  $default
1367
+	 * @param int  $parent
1368
+	 * @param int  $level
1369
+	 * @param bool $echo
1370
+	 * @return string;
1371
+	 */
1372
+	public static function page_settings_dropdown(
1373
+		int $default = 0,
1374
+		int $parent = 0,
1375
+		int $level = 0,
1376
+		bool $echo = true
1377
+	): string {
1378
+		global $wpdb;
1379
+		$items  = $wpdb->get_results(
1380
+			$wpdb->prepare(
1381
+				"SELECT ID, post_parent, post_title FROM $wpdb->posts WHERE post_parent = %d AND post_type = 'page' AND post_status != 'trash' ORDER BY menu_order",
1382
+				$parent
1383
+			)
1384
+		);
1385
+		$output = '';
1386
+
1387
+		if ($items) {
1388
+			$level = absint($level);
1389
+			foreach ($items as $item) {
1390
+				$ID         = absint($item->ID);
1391
+				$post_title = wp_strip_all_tags($item->post_title);
1392
+				$pad        = str_repeat('&nbsp;', $level * 3);
1393
+				$option     = "\n\t";
1394
+				$option     .= '<option class="level-' . $level . '" ';
1395
+				$option     .= 'value="' . $ID . '" ';
1396
+				$option     .= $ID === absint($default) ? ' selected' : '';
1397
+				$option     .= '>';
1398
+				$option     .= "$pad {$post_title}";
1399
+				$option     .= '</option>';
1400
+				$output     .= $option;
1401
+				ob_start();
1402
+				parent_dropdown($default, $item->ID, $level + 1);
1403
+				$output .= ob_get_clean();
1404
+			}
1405
+		}
1406
+		if ($echo) {
1407
+			echo wp_kses($output, AllowedTags::getWithFormTags());
1408
+			return '';
1409
+		}
1410
+		return $output;
1411
+	}
1412
+
1413
+
1414
+	/**
1415
+	 * Loads the scripts for the privacy settings form
1416
+	 */
1417
+	public function load_scripts_styles_privacy_settings()
1418
+	{
1419
+		$form_handler = $this->loader->getShared(
1420
+			'EventEspresso\core\domain\services\admin\privacy\forms\PrivacySettingsFormHandler'
1421
+		);
1422
+		$form_handler->enqueueStylesAndScripts();
1423
+	}
1424
+
1425
+
1426
+	/**
1427
+	 * display the privacy settings form
1428
+	 *
1429
+	 * @throws EE_Error
1430
+	 */
1431
+	public function privacySettings()
1432
+	{
1433
+		$this->_set_add_edit_form_tags('update_privacy_settings');
1434
+		$this->_set_publish_post_box_vars(null, false, false, null, false);
1435
+		$form_handler                               = $this->loader->getShared(
1436
+			'EventEspresso\core\domain\services\admin\privacy\forms\PrivacySettingsFormHandler'
1437
+		);
1438
+		$this->_template_args['admin_page_content'] = EEH_HTML::div(
1439
+			$form_handler->display(),
1440
+			'',
1441
+			'padding'
1442
+		);
1443
+		$this->display_admin_page_with_sidebar();
1444
+	}
1445
+
1446
+
1447
+	/**
1448
+	 * Update the privacy settings from form data
1449
+	 *
1450
+	 * @throws EE_Error
1451
+	 */
1452
+	public function updatePrivacySettings()
1453
+	{
1454
+		$form_handler = $this->loader->getShared(
1455
+			'EventEspresso\core\domain\services\admin\privacy\forms\PrivacySettingsFormHandler'
1456
+		);
1457
+		$success      = $form_handler->process($this->get_request_data());
1458
+		$this->_redirect_after_action(
1459
+			$success,
1460
+			esc_html__('Registration Form Options', 'event_espresso'),
1461
+			'updated',
1462
+			['action' => 'privacy_settings']
1463
+		);
1464
+	}
1465 1465
 }
Please login to merge, or discard this patch.
Spacing   +47 added lines, -47 removed lines patch added patch discarded remove patch
@@ -251,19 +251,19 @@  discard block
 block discarded – undo
251 251
                 'event_espresso'
252 252
             )
253 253
         );
254
-        EE_Registry::$i18n_js_strings['error_occurred']          = wp_strip_all_tags(
254
+        EE_Registry::$i18n_js_strings['error_occurred'] = wp_strip_all_tags(
255 255
             esc_html__(
256 256
                 'An error occurred! Please refresh the page and try again.',
257 257
                 'event_espresso'
258 258
             )
259 259
         );
260
-        EE_Registry::$i18n_js_strings['confirm_delete_state']    = wp_strip_all_tags(
260
+        EE_Registry::$i18n_js_strings['confirm_delete_state'] = wp_strip_all_tags(
261 261
             esc_html__(
262 262
                 'Are you sure you want to delete this State / Province?',
263 263
                 'event_espresso'
264 264
             )
265 265
         );
266
-        EE_Registry::$i18n_js_strings['ajax_url']                = admin_url(
266
+        EE_Registry::$i18n_js_strings['ajax_url'] = admin_url(
267 267
             'admin-ajax.php?page=espresso_general_settings',
268 268
             is_ssl() ? 'https://' : 'http://'
269 269
         );
@@ -292,12 +292,12 @@  discard block
 block discarded – undo
292 292
         wp_enqueue_script('thickbox');
293 293
         wp_register_script(
294 294
             'organization_settings',
295
-            GEN_SET_ASSETS_URL . 'your_organization_settings.js',
295
+            GEN_SET_ASSETS_URL.'your_organization_settings.js',
296 296
             ['jquery', 'media-upload', 'thickbox'],
297 297
             EVENT_ESPRESSO_VERSION,
298 298
             true
299 299
         );
300
-        wp_register_style('organization-css', GEN_SET_ASSETS_URL . 'organization.css', [], EVENT_ESPRESSO_VERSION);
300
+        wp_register_style('organization-css', GEN_SET_ASSETS_URL.'organization.css', [], EVENT_ESPRESSO_VERSION);
301 301
         wp_enqueue_script('organization_settings');
302 302
         wp_enqueue_style('organization-css');
303 303
         $confirm_image_delete = [
@@ -320,12 +320,12 @@  discard block
 block discarded – undo
320 320
         // scripts
321 321
         wp_register_script(
322 322
             'gen_settings_countries',
323
-            GEN_SET_ASSETS_URL . 'gen_settings_countries.js',
323
+            GEN_SET_ASSETS_URL.'gen_settings_countries.js',
324 324
             ['ee_admin_js'],
325 325
             EVENT_ESPRESSO_VERSION,
326 326
             true
327 327
         );
328
-        wp_register_style('organization-css', GEN_SET_ASSETS_URL . 'organization.css', [], EVENT_ESPRESSO_VERSION);
328
+        wp_register_style('organization-css', GEN_SET_ASSETS_URL.'organization.css', [], EVENT_ESPRESSO_VERSION);
329 329
         wp_enqueue_script('gen_settings_countries');
330 330
         wp_enqueue_style('organization-css');
331 331
     }
@@ -373,7 +373,7 @@  discard block
 block discarded – undo
373 373
         $this->_set_add_edit_form_tags('update_espresso_page_settings');
374 374
         $this->_set_publish_post_box_vars(null, false, false, null, false);
375 375
         $this->_template_args['admin_page_content'] = EEH_Template::display_template(
376
-            GEN_SET_TEMPLATE_PATH . 'espresso_page_settings.template.php',
376
+            GEN_SET_TEMPLATE_PATH.'espresso_page_settings.template.php',
377 377
             $this->_template_args,
378 378
             true
379 379
         );
@@ -390,12 +390,12 @@  discard block
 block discarded – undo
390 390
     {
391 391
         $this->core_config = EE_Registry::instance()->CFG->core;
392 392
         // capture incoming request data && set page IDs
393
-        $this->core_config->reg_page_id       = $this->request->getRequestParam(
393
+        $this->core_config->reg_page_id = $this->request->getRequestParam(
394 394
             'reg_page_id',
395 395
             $this->core_config->reg_page_id,
396 396
             DataType::INT
397 397
         );
398
-        $this->core_config->txn_page_id       = $this->request->getRequestParam(
398
+        $this->core_config->txn_page_id = $this->request->getRequestParam(
399 399
             'txn_page_id',
400 400
             $this->core_config->txn_page_id,
401 401
             DataType::INT
@@ -405,7 +405,7 @@  discard block
 block discarded – undo
405 405
             $this->core_config->thank_you_page_id,
406 406
             DataType::INT
407 407
         );
408
-        $this->core_config->cancel_page_id    = $this->request->getRequestParam(
408
+        $this->core_config->cancel_page_id = $this->request->getRequestParam(
409 409
             'cancel_page_id',
410 410
             $this->core_config->cancel_page_id,
411 411
             DataType::INT
@@ -672,16 +672,16 @@  discard block
 block discarded – undo
672 672
             $country->ID(),
673 673
             $country
674 674
         );
675
-        $this->_template_args['country_states_settings']  = $this->display_country_states(
675
+        $this->_template_args['country_states_settings'] = $this->display_country_states(
676 676
             $country->ID(),
677 677
             $country
678 678
         );
679
-        $this->_template_args['CNT_name_for_site']        = $country->name();
679
+        $this->_template_args['CNT_name_for_site'] = $country->name();
680 680
 
681 681
         $this->_set_add_edit_form_tags('update_country_settings');
682 682
         $this->_set_publish_post_box_vars(null, false, false, null, false);
683 683
         $this->_template_args['admin_page_content'] = EEH_Template::display_template(
684
-            GEN_SET_TEMPLATE_PATH . 'countries_settings.template.php',
684
+            GEN_SET_TEMPLATE_PATH.'countries_settings.template.php',
685 685
             $this->_template_args,
686 686
             true
687 687
         );
@@ -705,7 +705,7 @@  discard block
 block discarded – undo
705 705
         $CNT_ISO          = $this->getCountryISO($CNT_ISO);
706 706
         $CNT_ISO_for_site = $this->getCountryIsoForSite();
707 707
 
708
-        if (! $CNT_ISO) {
708
+        if ( ! $CNT_ISO) {
709 709
             return '';
710 710
         }
711 711
 
@@ -718,7 +718,7 @@  discard block
 block discarded – undo
718 718
         $CNT_cur_disabled                         = $CNT_ISO !== $CNT_ISO_for_site;
719 719
         $this->_template_args['CNT_cur_disabled'] = $CNT_cur_disabled;
720 720
 
721
-        $country_input_types            = [
721
+        $country_input_types = [
722 722
             'CNT_active'      => [
723 723
                 'type'             => 'RADIO_BTN',
724 724
                 'input_name'       => "cntry[$CNT_ISO]",
@@ -851,8 +851,8 @@  discard block
 block discarded – undo
851 851
             $country,
852 852
             $country_input_types
853 853
         );
854
-        $country_details_settings       = EEH_Template::display_template(
855
-            GEN_SET_TEMPLATE_PATH . 'country_details_settings.template.php',
854
+        $country_details_settings = EEH_Template::display_template(
855
+            GEN_SET_TEMPLATE_PATH.'country_details_settings.template.php',
856 856
             $this->_template_args,
857 857
             true
858 858
         );
@@ -886,7 +886,7 @@  discard block
 block discarded – undo
886 886
     public function display_country_states(string $CNT_ISO = '', ?EE_Country $country = null): string
887 887
     {
888 888
         $CNT_ISO = $this->getCountryISO($CNT_ISO);
889
-        if (! $CNT_ISO) {
889
+        if ( ! $CNT_ISO) {
890 890
             return '';
891 891
         }
892 892
         // for ajax
@@ -951,8 +951,8 @@  discard block
 block discarded – undo
951 951
                         GEN_SET_ADMIN_URL
952 952
                     );
953 953
 
954
-                    $this->_template_args['states'][ $STA_ID ]['inputs']           = $inputs;
955
-                    $this->_template_args['states'][ $STA_ID ]['delete_state_url'] = $delete_state_url;
954
+                    $this->_template_args['states'][$STA_ID]['inputs']           = $inputs;
955
+                    $this->_template_args['states'][$STA_ID]['delete_state_url'] = $delete_state_url;
956 956
                 }
957 957
             }
958 958
         } else {
@@ -965,7 +965,7 @@  discard block
 block discarded – undo
965 965
         );
966 966
 
967 967
         $state_details_settings = EEH_Template::display_template(
968
-            GEN_SET_TEMPLATE_PATH . 'state_details_settings.template.php',
968
+            GEN_SET_TEMPLATE_PATH.'state_details_settings.template.php',
969 969
             $this->_template_args,
970 970
             true
971 971
         );
@@ -997,7 +997,7 @@  discard block
 block discarded – undo
997 997
     {
998 998
         $success = true;
999 999
         $CNT_ISO = $this->getCountryISO('');
1000
-        if (! $CNT_ISO) {
1000
+        if ( ! $CNT_ISO) {
1001 1001
             EE_Error::add_error(
1002 1002
                 esc_html__('No Country ISO code or an invalid Country ISO code was received.', 'event_espresso'),
1003 1003
                 __FILE__,
@@ -1007,7 +1007,7 @@  discard block
 block discarded – undo
1007 1007
             $success = false;
1008 1008
         }
1009 1009
         $STA_abbrev = $this->request->getRequestParam('STA_abbrev');
1010
-        if (! $STA_abbrev) {
1010
+        if ( ! $STA_abbrev) {
1011 1011
             EE_Error::add_error(
1012 1012
                 esc_html__('No State ISO code or an invalid State ISO code was received.', 'event_espresso'),
1013 1013
                 __FILE__,
@@ -1017,7 +1017,7 @@  discard block
 block discarded – undo
1017 1017
             $success = false;
1018 1018
         }
1019 1019
         $STA_name = $this->request->getRequestParam('STA_name');
1020
-        if (! $STA_name) {
1020
+        if ( ! $STA_name) {
1021 1021
             EE_Error::add_error(
1022 1022
                 esc_html__('No State name or an invalid State name was received.', 'event_espresso'),
1023 1023
                 __FILE__,
@@ -1034,7 +1034,7 @@  discard block
 block discarded – undo
1034 1034
                 'STA_name'   => $STA_name,
1035 1035
                 'STA_active' => true,
1036 1036
             ];
1037
-            $success       = EEM_State::instance()->insert($cols_n_values);
1037
+            $success = EEM_State::instance()->insert($cols_n_values);
1038 1038
             EE_Error::add_success(esc_html__('The State was added successfully.', 'event_espresso'));
1039 1039
         }
1040 1040
 
@@ -1066,7 +1066,7 @@  discard block
 block discarded – undo
1066 1066
         $STA_ID     = $this->request->getRequestParam('STA_ID');
1067 1067
         $STA_abbrev = $this->request->getRequestParam('STA_abbrev');
1068 1068
 
1069
-        if (! $STA_ID) {
1069
+        if ( ! $STA_ID) {
1070 1070
             EE_Error::add_error(
1071 1071
                 esc_html__('No State ID or an invalid State ID was received.', 'event_espresso'),
1072 1072
                 __FILE__,
@@ -1112,7 +1112,7 @@  discard block
 block discarded – undo
1112 1112
     protected function _update_country_settings()
1113 1113
     {
1114 1114
         $CNT_ISO = $this->getCountryISO();
1115
-        if (! $CNT_ISO) {
1115
+        if ( ! $CNT_ISO) {
1116 1116
             EE_Error::add_error(
1117 1117
                 esc_html__('No Country ISO code or an invalid Country ISO code was received.', 'event_espresso'),
1118 1118
                 __FILE__,
@@ -1131,25 +1131,25 @@  discard block
 block discarded – undo
1131 1131
                 $country->ISO3()
1132 1132
             )
1133 1133
         );
1134
-        $cols_n_values['CNT_name']        = $this->request->getRequestParam(
1134
+        $cols_n_values['CNT_name'] = $this->request->getRequestParam(
1135 1135
             "cntry[$CNT_ISO][CNT_name]",
1136 1136
             $country->name()
1137 1137
         );
1138
-        $cols_n_values['CNT_cur_code']    = strtoupper(
1138
+        $cols_n_values['CNT_cur_code'] = strtoupper(
1139 1139
             $this->request->getRequestParam(
1140 1140
                 "cntry[$CNT_ISO][CNT_cur_code]",
1141 1141
                 $country->currency_code()
1142 1142
             )
1143 1143
         );
1144
-        $cols_n_values['CNT_cur_single']  = $this->request->getRequestParam(
1144
+        $cols_n_values['CNT_cur_single'] = $this->request->getRequestParam(
1145 1145
             "cntry[$CNT_ISO][CNT_cur_single]",
1146 1146
             $country->currency_name_single()
1147 1147
         );
1148
-        $cols_n_values['CNT_cur_plural']  = $this->request->getRequestParam(
1148
+        $cols_n_values['CNT_cur_plural'] = $this->request->getRequestParam(
1149 1149
             "cntry[$CNT_ISO][CNT_cur_plural]",
1150 1150
             $country->currency_name_plural()
1151 1151
         );
1152
-        $cols_n_values['CNT_cur_sign']    = $this->request->getRequestParam(
1152
+        $cols_n_values['CNT_cur_sign'] = $this->request->getRequestParam(
1153 1153
             "cntry[$CNT_ISO][CNT_cur_sign]",
1154 1154
             $country->currency_sign()
1155 1155
         );
@@ -1166,15 +1166,15 @@  discard block
 block discarded – undo
1166 1166
             "cntry[$CNT_ISO][CNT_cur_dec_mrk]",
1167 1167
             $country->currency_decimal_mark()
1168 1168
         );
1169
-        $cols_n_values['CNT_cur_thsnds']  = $this->request->getRequestParam(
1169
+        $cols_n_values['CNT_cur_thsnds'] = $this->request->getRequestParam(
1170 1170
             "cntry[$CNT_ISO][CNT_cur_thsnds]",
1171 1171
             $country->currency_thousands_separator()
1172 1172
         );
1173
-        $cols_n_values['CNT_tel_code']    = $this->request->getRequestParam(
1173
+        $cols_n_values['CNT_tel_code'] = $this->request->getRequestParam(
1174 1174
             "cntry[$CNT_ISO][CNT_tel_code]",
1175 1175
             $country->telephoneCode()
1176 1176
         );
1177
-        $cols_n_values['CNT_active']      = $this->request->getRequestParam(
1177
+        $cols_n_values['CNT_active'] = $this->request->getRequestParam(
1178 1178
             "cntry[$CNT_ISO][CNT_active]",
1179 1179
             $country->isActive(),
1180 1180
             DataType::BOOL
@@ -1197,7 +1197,7 @@  discard block
 block discarded – undo
1197 1197
             $this->request->getRequestParam('states', [], DataType::STRING, true)
1198 1198
         );
1199 1199
 
1200
-        if (! empty($states) && $success !== false) {
1200
+        if ( ! empty($states) && $success !== false) {
1201 1201
             // loop thru state data ( looks like : states[75][STA_name] )
1202 1202
             foreach ($states as $STA_ID => $state) {
1203 1203
                 $cols_n_values = [
@@ -1255,7 +1255,7 @@  discard block
 block discarded – undo
1255 1255
         return '
1256 1256
 			<tr>
1257 1257
 				<th>
1258
-					' . $label . '
1258
+					' . $label.'
1259 1259
 				</th>';
1260 1260
     }
1261 1261
 
@@ -1270,7 +1270,7 @@  discard block
 block discarded – undo
1270 1270
     {
1271 1271
         return '
1272 1272
 				<td class="general-settings-country-input-td">
1273
-					' . $input . '
1273
+					' . $input.'
1274 1274
 				</td>
1275 1275
 			</tr>';
1276 1276
     }
@@ -1299,7 +1299,7 @@  discard block
 block discarded – undo
1299 1299
     {
1300 1300
         return '
1301 1301
 				<td class="general-settings-country-state-input-td">
1302
-					' . $input . '
1302
+					' . $input.'
1303 1303
 				</td>';
1304 1304
     }
1305 1305
 
@@ -1319,9 +1319,9 @@  discard block
 block discarded – undo
1319 1319
             ['post' => $ee_page_id, 'action' => 'edit'],
1320 1320
             admin_url('post.php')
1321 1321
         );
1322
-        $links    = '<a href="' . esc_url_raw($edit_url) . '" >' . esc_html__('Edit', 'event_espresso') . '</a>';
1322
+        $links = '<a href="'.esc_url_raw($edit_url).'" >'.esc_html__('Edit', 'event_espresso').'</a>';
1323 1323
         $links    .= ' &nbsp;|&nbsp; ';
1324
-        $links    .= '<a href="' . get_permalink($ee_page_id) . '" >' . esc_html__('View', 'event_espresso') . '</a>';
1324
+        $links    .= '<a href="'.get_permalink($ee_page_id).'" >'.esc_html__('View', 'event_espresso').'</a>';
1325 1325
 
1326 1326
         return $links;
1327 1327
     }
@@ -1355,8 +1355,8 @@  discard block
 block discarded – undo
1355 1355
         }
1356 1356
 
1357 1357
         return '
1358
-        <span class="ee-page-status ' . $pg_class . '"><strong>' . $pg_status . '</strong></span>
1359
-        <span class="ee-page-status ' . $sc_class . '"><strong>' . $sc_status . '</strong></span>';
1358
+        <span class="ee-page-status ' . $pg_class.'"><strong>'.$pg_status.'</strong></span>
1359
+        <span class="ee-page-status ' . $sc_class.'"><strong>'.$sc_status.'</strong></span>';
1360 1360
     }
1361 1361
 
1362 1362
 
@@ -1376,7 +1376,7 @@  discard block
 block discarded – undo
1376 1376
         bool $echo = true
1377 1377
     ): string {
1378 1378
         global $wpdb;
1379
-        $items  = $wpdb->get_results(
1379
+        $items = $wpdb->get_results(
1380 1380
             $wpdb->prepare(
1381 1381
                 "SELECT ID, post_parent, post_title FROM $wpdb->posts WHERE post_parent = %d AND post_type = 'page' AND post_status != 'trash' ORDER BY menu_order",
1382 1382
                 $parent
@@ -1391,8 +1391,8 @@  discard block
 block discarded – undo
1391 1391
                 $post_title = wp_strip_all_tags($item->post_title);
1392 1392
                 $pad        = str_repeat('&nbsp;', $level * 3);
1393 1393
                 $option     = "\n\t";
1394
-                $option     .= '<option class="level-' . $level . '" ';
1395
-                $option     .= 'value="' . $ID . '" ';
1394
+                $option     .= '<option class="level-'.$level.'" ';
1395
+                $option     .= 'value="'.$ID.'" ';
1396 1396
                 $option     .= $ID === absint($default) ? ' selected' : '';
1397 1397
                 $option     .= '>';
1398 1398
                 $option     .= "$pad {$post_title}";
Please login to merge, or discard this patch.
admin_pages/events/Event_Categories_Admin_List_Table.class.php 2 patches
Indentation   +160 added lines, -160 removed lines patch added patch discarded remove patch
@@ -11,176 +11,176 @@
 block discarded – undo
11 11
  */
12 12
 class Event_Categories_Admin_List_Table extends EE_Admin_List_Table
13 13
 {
14
-    /**
15
-     * @var Events_Admin_Page $_admin_page
16
-     */
17
-    protected $_admin_page;
18
-
19
-
20
-    /**
21
-     * @throws EE_Error
22
-     * @throws ReflectionException
23
-     */
24
-    protected function _setup_data()
25
-    {
26
-        $this->_data           = $this->_admin_page->get_categories($this->_per_page, $this->_current_page);
27
-        $this->_all_data_count = EEM_Term_Taxonomy::instance()->count(
28
-            [['taxonomy' => 'espresso_event_categories']]
29
-        );
30
-    }
31
-
32
-
33
-    protected function _set_properties()
34
-    {
35
-        $this->_wp_list_args = [
36
-            'singular' => esc_html__('event category', 'event_espresso'),
37
-            'plural'   => esc_html__('event categories', 'event_espresso'),
38
-            'ajax'     => true, // for now,
39
-            'screen'   => $this->_admin_page->get_current_screen()->id,
40
-        ];
41
-
42
-        $this->_columns = [
43
-            'cb'        => '<input type="checkbox" />',
44
-            'id'        => esc_html__('ID', 'event_espresso'),
45
-            'name'      => esc_html__('Name', 'event_espresso'),
46
-            'shortcode' => esc_html__('Shortcode', 'event_espresso'),
47
-            'count'     => esc_html__('Events', 'event_espresso'),
48
-        ];
49
-
50
-        $this->_sortable_columns = [
51
-            'id'    => ['Term.term_id' => true],
52
-            'name'  => ['Term.slug' => false],
53
-            'count' => ['term_count' => false],
54
-        ];
55
-
56
-        $this->_primary_column = 'id';
57
-
58
-        $this->_hidden_columns = [];
59
-    }
60
-
61
-
62
-    protected function _get_table_filters(): array
63
-    {
64
-        return [];
65
-    }
66
-
67
-
68
-    protected function _add_view_counts()
69
-    {
70
-        $this->_views['all']['count'] = $this->_all_data_count;
71
-    }
72
-
73
-
74
-    public function column_cb($item): string
75
-    {
76
-        return sprintf('<input type="checkbox" name="EVT_CAT_ID[]" value="%s" />', $item->get('term_id'));
77
-    }
78
-
79
-
80
-    /**
81
-     * @param EE_Term_Taxonomy $item
82
-     * @return string
83
-     * @throws EE_Error
84
-     * @throws ReflectionException
85
-     */
86
-    public function column_id(EE_Term_Taxonomy $item): string
87
-    {
88
-        $content = '
14
+	/**
15
+	 * @var Events_Admin_Page $_admin_page
16
+	 */
17
+	protected $_admin_page;
18
+
19
+
20
+	/**
21
+	 * @throws EE_Error
22
+	 * @throws ReflectionException
23
+	 */
24
+	protected function _setup_data()
25
+	{
26
+		$this->_data           = $this->_admin_page->get_categories($this->_per_page, $this->_current_page);
27
+		$this->_all_data_count = EEM_Term_Taxonomy::instance()->count(
28
+			[['taxonomy' => 'espresso_event_categories']]
29
+		);
30
+	}
31
+
32
+
33
+	protected function _set_properties()
34
+	{
35
+		$this->_wp_list_args = [
36
+			'singular' => esc_html__('event category', 'event_espresso'),
37
+			'plural'   => esc_html__('event categories', 'event_espresso'),
38
+			'ajax'     => true, // for now,
39
+			'screen'   => $this->_admin_page->get_current_screen()->id,
40
+		];
41
+
42
+		$this->_columns = [
43
+			'cb'        => '<input type="checkbox" />',
44
+			'id'        => esc_html__('ID', 'event_espresso'),
45
+			'name'      => esc_html__('Name', 'event_espresso'),
46
+			'shortcode' => esc_html__('Shortcode', 'event_espresso'),
47
+			'count'     => esc_html__('Events', 'event_espresso'),
48
+		];
49
+
50
+		$this->_sortable_columns = [
51
+			'id'    => ['Term.term_id' => true],
52
+			'name'  => ['Term.slug' => false],
53
+			'count' => ['term_count' => false],
54
+		];
55
+
56
+		$this->_primary_column = 'id';
57
+
58
+		$this->_hidden_columns = [];
59
+	}
60
+
61
+
62
+	protected function _get_table_filters(): array
63
+	{
64
+		return [];
65
+	}
66
+
67
+
68
+	protected function _add_view_counts()
69
+	{
70
+		$this->_views['all']['count'] = $this->_all_data_count;
71
+	}
72
+
73
+
74
+	public function column_cb($item): string
75
+	{
76
+		return sprintf('<input type="checkbox" name="EVT_CAT_ID[]" value="%s" />', $item->get('term_id'));
77
+	}
78
+
79
+
80
+	/**
81
+	 * @param EE_Term_Taxonomy $item
82
+	 * @return string
83
+	 * @throws EE_Error
84
+	 * @throws ReflectionException
85
+	 */
86
+	public function column_id(EE_Term_Taxonomy $item): string
87
+	{
88
+		$content = '
89 89
         <span class="ee-entity-id">' . $item->get('term_id') . '</span>
90 90
         <span class="show-on-mobile-view-only">
91 91
             ' . $item->get_first_related('Term')->get('name') . '
92 92
         </span>';
93
-        return $this->columnContent('id', $content, 'end');
94
-    }
95
-
96
-
97
-    /**
98
-     * @param EE_Term_Taxonomy $item
99
-     * @return string
100
-     * @throws EE_Error
101
-     * @throws ReflectionException
102
-     */
103
-    public function column_name(EE_Term_Taxonomy $item): string
104
-    {
105
-        $edit_query_args = [
106
-            'action'     => 'edit_category',
107
-            'EVT_CAT_ID' => $item->get('term_id'),
108
-        ];
109
-
110
-        $delete_query_args = [
111
-            'action'     => 'delete_category',
112
-            'EVT_CAT_ID' => $item->get('term_id'),
113
-        ];
114
-
115
-        $edit_link   = EE_Admin_Page::add_query_args_and_nonce($edit_query_args, EVENTS_ADMIN_URL);
116
-        $delete_link = EE_Admin_Page::add_query_args_and_nonce($delete_query_args, EVENTS_ADMIN_URL);
117
-        $view_link   = get_term_link($item->get('term_id'));
118
-
119
-        $term_name = $item->get_first_related('Term')->get('name');
120
-
121
-        $edit_category_label = sprintf(
122
-        /* translators: The name of the event category */
123
-            esc_attr__(
124
-                'Edit Category (%s)',
125
-                'event_espresso'
126
-            ),
127
-            $term_name
128
-        );
129
-        $actions['edit']     = '
93
+		return $this->columnContent('id', $content, 'end');
94
+	}
95
+
96
+
97
+	/**
98
+	 * @param EE_Term_Taxonomy $item
99
+	 * @return string
100
+	 * @throws EE_Error
101
+	 * @throws ReflectionException
102
+	 */
103
+	public function column_name(EE_Term_Taxonomy $item): string
104
+	{
105
+		$edit_query_args = [
106
+			'action'     => 'edit_category',
107
+			'EVT_CAT_ID' => $item->get('term_id'),
108
+		];
109
+
110
+		$delete_query_args = [
111
+			'action'     => 'delete_category',
112
+			'EVT_CAT_ID' => $item->get('term_id'),
113
+		];
114
+
115
+		$edit_link   = EE_Admin_Page::add_query_args_and_nonce($edit_query_args, EVENTS_ADMIN_URL);
116
+		$delete_link = EE_Admin_Page::add_query_args_and_nonce($delete_query_args, EVENTS_ADMIN_URL);
117
+		$view_link   = get_term_link($item->get('term_id'));
118
+
119
+		$term_name = $item->get_first_related('Term')->get('name');
120
+
121
+		$edit_category_label = sprintf(
122
+		/* translators: The name of the event category */
123
+			esc_attr__(
124
+				'Edit Category (%s)',
125
+				'event_espresso'
126
+			),
127
+			$term_name
128
+		);
129
+		$actions['edit']     = '
130 130
             <a href="' . $edit_link . '" aria-label="' . $edit_category_label . '">
131 131
                 ' . esc_html__('Edit', 'event_espresso') . '
132 132
             </a>';
133 133
 
134
-        $actions['delete'] = '<a href="' . $delete_link . '" aria-label="' . esc_attr__(
135
-            'Delete Category',
136
-            'event_espresso'
137
-        ) . '">' . esc_html__('Delete', 'event_espresso') . '</a>';
138
-
139
-        $view_category_label = sprintf(
140
-        /* translators: %s: event category name */
141
-            esc_html__('View &#8220;%s&#8221; archive', 'event_espresso'),
142
-            $item->get_first_related('Term')->get('name')
143
-        );
144
-        $actions['view']     = '
134
+		$actions['delete'] = '<a href="' . $delete_link . '" aria-label="' . esc_attr__(
135
+			'Delete Category',
136
+			'event_espresso'
137
+		) . '">' . esc_html__('Delete', 'event_espresso') . '</a>';
138
+
139
+		$view_category_label = sprintf(
140
+		/* translators: %s: event category name */
141
+			esc_html__('View &#8220;%s&#8221; archive', 'event_espresso'),
142
+			$item->get_first_related('Term')->get('name')
143
+		);
144
+		$actions['view']     = '
145 145
             <a href="' . $view_link . '" aria-label="' . $view_category_label . '">
146 146
                 ' . esc_html__('View', 'event_espresso') . '
147 147
             </a>';
148 148
 
149
-        $content = '<strong><a class="row-title" href="' . $edit_link . '">' . $term_name . '</a></strong>';
150
-        $content .= $this->row_actions($actions);
151
-        return $this->columnContent('name', $content);
152
-    }
153
-
154
-
155
-    /**
156
-     * @param EE_Term_Taxonomy $item
157
-     * @return string
158
-     * @throws EE_Error
159
-     * @throws ReflectionException
160
-     */
161
-    public function column_shortcode(EE_Term_Taxonomy $item): string
162
-    {
163
-        $content = '[ESPRESSO_EVENTS category_slug=' . $item->get_first_related('Term')->get('slug') . ']';
164
-        return $this->columnContent('shortcode', $content);
165
-    }
166
-
167
-
168
-    /**
169
-     * @param EE_Term_Taxonomy $item
170
-     * @return string
171
-     * @throws EE_Error
172
-     * @throws ReflectionException
173
-     */
174
-    public function column_count(EE_Term_Taxonomy $item): string
175
-    {
176
-        $category_url = EE_Admin_Page::add_query_args_and_nonce(
177
-            [
178
-                'action'  => 'default',
179
-                'EVT_CAT' => $item->get_first_related('Term')->ID(),
180
-            ],
181
-            EVENTS_ADMIN_URL
182
-        );
183
-        $content      = '<a href="' . $category_url . '">' . $item->get('term_count') . '</a>';
184
-        return $this->columnContent('count', $content);
185
-    }
149
+		$content = '<strong><a class="row-title" href="' . $edit_link . '">' . $term_name . '</a></strong>';
150
+		$content .= $this->row_actions($actions);
151
+		return $this->columnContent('name', $content);
152
+	}
153
+
154
+
155
+	/**
156
+	 * @param EE_Term_Taxonomy $item
157
+	 * @return string
158
+	 * @throws EE_Error
159
+	 * @throws ReflectionException
160
+	 */
161
+	public function column_shortcode(EE_Term_Taxonomy $item): string
162
+	{
163
+		$content = '[ESPRESSO_EVENTS category_slug=' . $item->get_first_related('Term')->get('slug') . ']';
164
+		return $this->columnContent('shortcode', $content);
165
+	}
166
+
167
+
168
+	/**
169
+	 * @param EE_Term_Taxonomy $item
170
+	 * @return string
171
+	 * @throws EE_Error
172
+	 * @throws ReflectionException
173
+	 */
174
+	public function column_count(EE_Term_Taxonomy $item): string
175
+	{
176
+		$category_url = EE_Admin_Page::add_query_args_and_nonce(
177
+			[
178
+				'action'  => 'default',
179
+				'EVT_CAT' => $item->get_first_related('Term')->ID(),
180
+			],
181
+			EVENTS_ADMIN_URL
182
+		);
183
+		$content      = '<a href="' . $category_url . '">' . $item->get('term_count') . '</a>';
184
+		return $this->columnContent('count', $content);
185
+	}
186 186
 }
Please login to merge, or discard this patch.
Spacing   +13 added lines, -13 removed lines patch added patch discarded remove patch
@@ -86,9 +86,9 @@  discard block
 block discarded – undo
86 86
     public function column_id(EE_Term_Taxonomy $item): string
87 87
     {
88 88
         $content = '
89
-        <span class="ee-entity-id">' . $item->get('term_id') . '</span>
89
+        <span class="ee-entity-id">' . $item->get('term_id').'</span>
90 90
         <span class="show-on-mobile-view-only">
91
-            ' . $item->get_first_related('Term')->get('name') . '
91
+            ' . $item->get_first_related('Term')->get('name').'
92 92
         </span>';
93 93
         return $this->columnContent('id', $content, 'end');
94 94
     }
@@ -126,27 +126,27 @@  discard block
 block discarded – undo
126 126
             ),
127 127
             $term_name
128 128
         );
129
-        $actions['edit']     = '
130
-            <a href="' . $edit_link . '" aria-label="' . $edit_category_label . '">
131
-                ' . esc_html__('Edit', 'event_espresso') . '
129
+        $actions['edit'] = '
130
+            <a href="' . $edit_link.'" aria-label="'.$edit_category_label.'">
131
+                ' . esc_html__('Edit', 'event_espresso').'
132 132
             </a>';
133 133
 
134
-        $actions['delete'] = '<a href="' . $delete_link . '" aria-label="' . esc_attr__(
134
+        $actions['delete'] = '<a href="'.$delete_link.'" aria-label="'.esc_attr__(
135 135
             'Delete Category',
136 136
             'event_espresso'
137
-        ) . '">' . esc_html__('Delete', 'event_espresso') . '</a>';
137
+        ).'">'.esc_html__('Delete', 'event_espresso').'</a>';
138 138
 
139 139
         $view_category_label = sprintf(
140 140
         /* translators: %s: event category name */
141 141
             esc_html__('View &#8220;%s&#8221; archive', 'event_espresso'),
142 142
             $item->get_first_related('Term')->get('name')
143 143
         );
144
-        $actions['view']     = '
145
-            <a href="' . $view_link . '" aria-label="' . $view_category_label . '">
146
-                ' . esc_html__('View', 'event_espresso') . '
144
+        $actions['view'] = '
145
+            <a href="' . $view_link.'" aria-label="'.$view_category_label.'">
146
+                ' . esc_html__('View', 'event_espresso').'
147 147
             </a>';
148 148
 
149
-        $content = '<strong><a class="row-title" href="' . $edit_link . '">' . $term_name . '</a></strong>';
149
+        $content = '<strong><a class="row-title" href="'.$edit_link.'">'.$term_name.'</a></strong>';
150 150
         $content .= $this->row_actions($actions);
151 151
         return $this->columnContent('name', $content);
152 152
     }
@@ -160,7 +160,7 @@  discard block
 block discarded – undo
160 160
      */
161 161
     public function column_shortcode(EE_Term_Taxonomy $item): string
162 162
     {
163
-        $content = '[ESPRESSO_EVENTS category_slug=' . $item->get_first_related('Term')->get('slug') . ']';
163
+        $content = '[ESPRESSO_EVENTS category_slug='.$item->get_first_related('Term')->get('slug').']';
164 164
         return $this->columnContent('shortcode', $content);
165 165
     }
166 166
 
@@ -180,7 +180,7 @@  discard block
 block discarded – undo
180 180
             ],
181 181
             EVENTS_ADMIN_URL
182 182
         );
183
-        $content      = '<a href="' . $category_url . '">' . $item->get('term_count') . '</a>';
183
+        $content = '<a href="'.$category_url.'">'.$item->get('term_count').'</a>';
184 184
         return $this->columnContent('count', $content);
185 185
     }
186 186
 }
Please login to merge, or discard this patch.
admin_pages/venues/Venues_Admin_List_Table.class.php 2 patches
Indentation   +258 added lines, -258 removed lines patch added patch discarded remove patch
@@ -13,269 +13,269 @@
 block discarded – undo
13 13
  */
14 14
 class Venues_Admin_List_Table extends EE_Admin_List_Table
15 15
 {
16
-    /**
17
-     * @var Venues_Admin_Page $_admin_page
18
-     */
19
-    protected $_admin_page;
20
-
21
-
22
-    protected function _setup_data()
23
-    {
24
-        $this->_data           = $this->_admin_page->get_venues($this->_per_page);
25
-        $this->_all_data_count = $this->_admin_page->get_venues($this->_per_page, true);
26
-    }
27
-
28
-
29
-    protected function _set_properties()
30
-    {
31
-        $this->_wp_list_args = [
32
-            'singular' => esc_html__('Event Venue', 'event_espresso'),
33
-            'plural'   => esc_html__('Event Venues', 'event_espresso'),
34
-            'ajax'     => true, // for now,
35
-            'screen'   => $this->_admin_page->get_current_screen()->id,
36
-        ];
37
-
38
-        $this->_columns = [
39
-            'cb'       => '<input type="checkbox" />',
40
-            'id'       => esc_html__('ID', 'event_espresso'),
41
-            'name'     => esc_html__('Name', 'event_espresso'),
42
-            'address'  => esc_html__('Address', 'event_espresso'),
43
-            'city'     => esc_html__('City', 'event_espresso'),
44
-            'capacity' => esc_html__('Capacity', 'event_espresso'),
45
-            // 'shortcode' => esc_html__('Shortcode', 'event_espresso'),
46
-        ];
47
-
48
-        $this->_sortable_columns = [
49
-            'id'       => ['id' => true],
50
-            'name'     => ['name' => false],
51
-            'city'     => ['city' => false],
52
-            'capacity' => ['capacity' => false],
53
-        ];
54
-
55
-        $this->_hidden_columns = [];
56
-    }
57
-
58
-
59
-    // todo... add _venue_status in here (which we'll define a EE_Admin_CPT_List_Table for common properties)
60
-
61
-
62
-    /**
63
-     * @return array
64
-     */
65
-    protected function _get_table_filters(): array
66
-    {
67
-        return [];
68
-    }
69
-
70
-
71
-    /**
72
-     * @throws EE_Error
73
-     * @throws ReflectionException
74
-     */
75
-    protected function _add_view_counts()
76
-    {
77
-        $this->_views['all']['count'] = EEM_Venue::instance()->count();
78
-        if (EE_Registry::instance()->CAP->current_user_can('ee_delete_venues', 'espresso_venues_trash_venues')) {
79
-            $this->_views['trash']['count'] = EEM_Venue::instance()->count_deleted();
80
-        }
81
-    }
82
-
83
-
84
-    /**
85
-     * @param EE_Venue|null $item
86
-     * @return string
87
-     * @throws EE_Error
88
-     * @throws ReflectionException
89
-     */
90
-    public function column_cb($item): string
91
-    {
92
-        return $item->count_related('Event') > 0 && $item->get('status') === 'trash'
93
-            ? '<span class="dashicons dashicons-lock"></span>'
94
-            : sprintf(
95
-                '<input type="checkbox" name="venue_id[]" value="%s" />',
96
-                $item->ID()
97
-            );
98
-    }
99
-
100
-
101
-    /**
102
-     * @param EE_Venue $venue
103
-     * @return string
104
-     * @throws EE_Error
105
-     * @throws ReflectionException
106
-     */
107
-    public function column_id(EE_Venue $venue): string
108
-    {
109
-        $content = '
16
+	/**
17
+	 * @var Venues_Admin_Page $_admin_page
18
+	 */
19
+	protected $_admin_page;
20
+
21
+
22
+	protected function _setup_data()
23
+	{
24
+		$this->_data           = $this->_admin_page->get_venues($this->_per_page);
25
+		$this->_all_data_count = $this->_admin_page->get_venues($this->_per_page, true);
26
+	}
27
+
28
+
29
+	protected function _set_properties()
30
+	{
31
+		$this->_wp_list_args = [
32
+			'singular' => esc_html__('Event Venue', 'event_espresso'),
33
+			'plural'   => esc_html__('Event Venues', 'event_espresso'),
34
+			'ajax'     => true, // for now,
35
+			'screen'   => $this->_admin_page->get_current_screen()->id,
36
+		];
37
+
38
+		$this->_columns = [
39
+			'cb'       => '<input type="checkbox" />',
40
+			'id'       => esc_html__('ID', 'event_espresso'),
41
+			'name'     => esc_html__('Name', 'event_espresso'),
42
+			'address'  => esc_html__('Address', 'event_espresso'),
43
+			'city'     => esc_html__('City', 'event_espresso'),
44
+			'capacity' => esc_html__('Capacity', 'event_espresso'),
45
+			// 'shortcode' => esc_html__('Shortcode', 'event_espresso'),
46
+		];
47
+
48
+		$this->_sortable_columns = [
49
+			'id'       => ['id' => true],
50
+			'name'     => ['name' => false],
51
+			'city'     => ['city' => false],
52
+			'capacity' => ['capacity' => false],
53
+		];
54
+
55
+		$this->_hidden_columns = [];
56
+	}
57
+
58
+
59
+	// todo... add _venue_status in here (which we'll define a EE_Admin_CPT_List_Table for common properties)
60
+
61
+
62
+	/**
63
+	 * @return array
64
+	 */
65
+	protected function _get_table_filters(): array
66
+	{
67
+		return [];
68
+	}
69
+
70
+
71
+	/**
72
+	 * @throws EE_Error
73
+	 * @throws ReflectionException
74
+	 */
75
+	protected function _add_view_counts()
76
+	{
77
+		$this->_views['all']['count'] = EEM_Venue::instance()->count();
78
+		if (EE_Registry::instance()->CAP->current_user_can('ee_delete_venues', 'espresso_venues_trash_venues')) {
79
+			$this->_views['trash']['count'] = EEM_Venue::instance()->count_deleted();
80
+		}
81
+	}
82
+
83
+
84
+	/**
85
+	 * @param EE_Venue|null $item
86
+	 * @return string
87
+	 * @throws EE_Error
88
+	 * @throws ReflectionException
89
+	 */
90
+	public function column_cb($item): string
91
+	{
92
+		return $item->count_related('Event') > 0 && $item->get('status') === 'trash'
93
+			? '<span class="dashicons dashicons-lock"></span>'
94
+			: sprintf(
95
+				'<input type="checkbox" name="venue_id[]" value="%s" />',
96
+				$item->ID()
97
+			);
98
+	}
99
+
100
+
101
+	/**
102
+	 * @param EE_Venue $venue
103
+	 * @return string
104
+	 * @throws EE_Error
105
+	 * @throws ReflectionException
106
+	 */
107
+	public function column_id(EE_Venue $venue): string
108
+	{
109
+		$content = '
110 110
             <span class="ee-entity-id">' . $venue->ID() . '</span>
111 111
             <span class="show-on-mobile-view-only">' . $this->column_name($venue, false) . '</span>';
112
-        return $this->columnContent('id', $content, 'end');
113
-    }
114
-
115
-
116
-    /**
117
-     * @param EE_Venue $venue
118
-     * @param bool     $prep_content
119
-     * @return string
120
-     * @throws EE_Error
121
-     * @throws ReflectionException
122
-     */
123
-    public function column_name(EE_Venue $venue, bool $prep_content = true): string
124
-    {
125
-        $edit_link = EE_Admin_Page::add_query_args_and_nonce(
126
-            [
127
-                'action' => 'edit',
128
-                'post'   => $venue->ID(),
129
-            ],
130
-            EE_VENUES_ADMIN_URL
131
-        );
132
-
133
-        $statuses = EEM_Venue::instance()->get_status_array();
134
-        $actions  = $prep_content ? $this->_column_name_action_setup($venue) : [];
135
-        $content  =
136
-            EE_Registry::instance()->CAP->current_user_can('ee_edit_venue', 'espresso_venues_edit', $venue->ID())
137
-                ? '<strong><a class="row-title" href="' . $edit_link . '">' . stripslashes_deep(
138
-                    $venue->name()
139
-                ) . '</a></strong>' : $venue->name();
140
-        $content  .= $venue->status() == 'draft' ? ' - <span class="post-state">' . $statuses['draft'] . '</span>' : '';
141
-
142
-        $content .= $prep_content ? $this->row_actions($actions) : '';
143
-        return $prep_content ? $this->columnContent('name', $content) : $content;
144
-    }
145
-
146
-
147
-    /**
148
-     * Used to setup the actions for the Venue name column
149
-     *
150
-     * @param EE_Venue $venue
151
-     * @return array()
152
-     * @throws EE_Error
153
-     * @throws ReflectionException
154
-     * @throws EE_Error
155
-     * @throws ReflectionException
156
-     */
157
-    protected function _column_name_action_setup(EE_Venue $venue): array
158
-    {
159
-        $actions = [];
160
-
161
-        if (EE_Registry::instance()->CAP->current_user_can('ee_edit_venue', 'espresso_venues_edit', $venue->ID())) {
162
-            $edit_query_args = [
163
-                'action' => 'edit',
164
-                'post'   => $venue->ID(),
165
-            ];
166
-            $edit_link       = EE_Admin_Page::add_query_args_and_nonce($edit_query_args, EE_VENUES_ADMIN_URL);
167
-            $actions['edit'] = '<a href="' . $edit_link . '" title="' . esc_attr__(
168
-                'Edit Venue',
169
-                'event_espresso'
170
-            ) . '">' . esc_html__('Edit', 'event_espresso') . '</a>';
171
-        }
172
-
173
-
174
-        switch ($venue->get('status')) {
175
-            case 'trash':
176
-                if (
177
-                    EE_Registry::instance()->CAP->current_user_can(
178
-                        'ee_delete_venue',
179
-                        'espresso_venues_restore_venue',
180
-                        $venue->ID()
181
-                    )
182
-                ) {
183
-                    $restore_venue_link = EE_Admin_Page::add_query_args_and_nonce(
184
-                        [
185
-                            'action' => 'restore_venue',
186
-                            'VNU_ID' => $venue->ID(),
187
-                        ],
188
-                        EE_VENUES_ADMIN_URL
189
-                    );
190
-
191
-                    $actions['restore_from_trash'] = '<a href="' . $restore_venue_link . '" title="' . esc_attr__(
192
-                        'Restore from Trash',
193
-                        'event_espresso'
194
-                    ) . '">' . esc_html__('Restore from Trash', 'event_espresso') . '</a>';
195
-                }
196
-                if (
197
-                    $venue->count_related('Event') === 0
198
-                    && EE_Registry::instance()->CAP->current_user_can(
199
-                        'ee_delete_venue',
200
-                        'espresso_venues_delete_venue',
201
-                        $venue->ID()
202
-                    )
203
-                ) {
204
-                    $delete_venue_link = EE_Admin_Page::add_query_args_and_nonce(
205
-                        [
206
-                            'action' => 'delete_venue',
207
-                            'VNU_ID' => $venue->ID(),
208
-                        ],
209
-                        EE_VENUES_ADMIN_URL
210
-                    );
211
-
212
-                    $actions['delete permanently'] = '<a href="' . $delete_venue_link . '" title="' . esc_attr__(
213
-                        'Delete Permanently',
214
-                        'event_espresso'
215
-                    ) . '">' . esc_html__('Delete Permanently', 'event_espresso') . '</a>';
216
-                }
217
-                break;
218
-            default:
219
-                $actions['view'] = '
112
+		return $this->columnContent('id', $content, 'end');
113
+	}
114
+
115
+
116
+	/**
117
+	 * @param EE_Venue $venue
118
+	 * @param bool     $prep_content
119
+	 * @return string
120
+	 * @throws EE_Error
121
+	 * @throws ReflectionException
122
+	 */
123
+	public function column_name(EE_Venue $venue, bool $prep_content = true): string
124
+	{
125
+		$edit_link = EE_Admin_Page::add_query_args_and_nonce(
126
+			[
127
+				'action' => 'edit',
128
+				'post'   => $venue->ID(),
129
+			],
130
+			EE_VENUES_ADMIN_URL
131
+		);
132
+
133
+		$statuses = EEM_Venue::instance()->get_status_array();
134
+		$actions  = $prep_content ? $this->_column_name_action_setup($venue) : [];
135
+		$content  =
136
+			EE_Registry::instance()->CAP->current_user_can('ee_edit_venue', 'espresso_venues_edit', $venue->ID())
137
+				? '<strong><a class="row-title" href="' . $edit_link . '">' . stripslashes_deep(
138
+					$venue->name()
139
+				) . '</a></strong>' : $venue->name();
140
+		$content  .= $venue->status() == 'draft' ? ' - <span class="post-state">' . $statuses['draft'] . '</span>' : '';
141
+
142
+		$content .= $prep_content ? $this->row_actions($actions) : '';
143
+		return $prep_content ? $this->columnContent('name', $content) : $content;
144
+	}
145
+
146
+
147
+	/**
148
+	 * Used to setup the actions for the Venue name column
149
+	 *
150
+	 * @param EE_Venue $venue
151
+	 * @return array()
152
+	 * @throws EE_Error
153
+	 * @throws ReflectionException
154
+	 * @throws EE_Error
155
+	 * @throws ReflectionException
156
+	 */
157
+	protected function _column_name_action_setup(EE_Venue $venue): array
158
+	{
159
+		$actions = [];
160
+
161
+		if (EE_Registry::instance()->CAP->current_user_can('ee_edit_venue', 'espresso_venues_edit', $venue->ID())) {
162
+			$edit_query_args = [
163
+				'action' => 'edit',
164
+				'post'   => $venue->ID(),
165
+			];
166
+			$edit_link       = EE_Admin_Page::add_query_args_and_nonce($edit_query_args, EE_VENUES_ADMIN_URL);
167
+			$actions['edit'] = '<a href="' . $edit_link . '" title="' . esc_attr__(
168
+				'Edit Venue',
169
+				'event_espresso'
170
+			) . '">' . esc_html__('Edit', 'event_espresso') . '</a>';
171
+		}
172
+
173
+
174
+		switch ($venue->get('status')) {
175
+			case 'trash':
176
+				if (
177
+					EE_Registry::instance()->CAP->current_user_can(
178
+						'ee_delete_venue',
179
+						'espresso_venues_restore_venue',
180
+						$venue->ID()
181
+					)
182
+				) {
183
+					$restore_venue_link = EE_Admin_Page::add_query_args_and_nonce(
184
+						[
185
+							'action' => 'restore_venue',
186
+							'VNU_ID' => $venue->ID(),
187
+						],
188
+						EE_VENUES_ADMIN_URL
189
+					);
190
+
191
+					$actions['restore_from_trash'] = '<a href="' . $restore_venue_link . '" title="' . esc_attr__(
192
+						'Restore from Trash',
193
+						'event_espresso'
194
+					) . '">' . esc_html__('Restore from Trash', 'event_espresso') . '</a>';
195
+				}
196
+				if (
197
+					$venue->count_related('Event') === 0
198
+					&& EE_Registry::instance()->CAP->current_user_can(
199
+						'ee_delete_venue',
200
+						'espresso_venues_delete_venue',
201
+						$venue->ID()
202
+					)
203
+				) {
204
+					$delete_venue_link = EE_Admin_Page::add_query_args_and_nonce(
205
+						[
206
+							'action' => 'delete_venue',
207
+							'VNU_ID' => $venue->ID(),
208
+						],
209
+						EE_VENUES_ADMIN_URL
210
+					);
211
+
212
+					$actions['delete permanently'] = '<a href="' . $delete_venue_link . '" title="' . esc_attr__(
213
+						'Delete Permanently',
214
+						'event_espresso'
215
+					) . '">' . esc_html__('Delete Permanently', 'event_espresso') . '</a>';
216
+				}
217
+				break;
218
+			default:
219
+				$actions['view'] = '
220 220
                     <a  href="' . get_permalink($venue->ID()) . '"
221 221
                         title="' . esc_attr__('View Venue', 'event_espresso') . '"
222 222
                     >
223 223
                         ' . esc_html__('View', 'event_espresso') . '
224 224
                     </a>';
225
-                if (
226
-                    EE_Registry::instance()->CAP->current_user_can(
227
-                        'ee_delete_venue',
228
-                        'espresso_venues_trash_venue',
229
-                        $venue->ID()
230
-                    )
231
-                ) {
232
-                    $trash_venue_link = EE_Admin_Page::add_query_args_and_nonce(
233
-                        [
234
-                            'action' => 'trash_venue',
235
-                            'VNU_ID' => $venue->ID(),
236
-                        ],
237
-                        EE_VENUES_ADMIN_URL
238
-                    );
239
-
240
-                    $actions['move to trash'] = '<a href="' . $trash_venue_link . '" title="' . esc_attr__(
241
-                        'Trash Event',
242
-                        'event_espresso'
243
-                    ) . '">' . esc_html__('Trash', 'event_espresso') . '</a>';
244
-                }
245
-        }
246
-        return $actions;
247
-    }
248
-
249
-
250
-    public function column_address(EE_Venue $venue): string
251
-    {
252
-        return $this->columnContent('address', $venue->address());
253
-    }
254
-
255
-
256
-    public function column_city(EE_Venue $venue): string
257
-    {
258
-        return $this->columnContent('city', $venue->city());
259
-    }
260
-
261
-
262
-    /**
263
-     * @throws EE_Error
264
-     * @throws ReflectionException
265
-     */
266
-    public function column_capacity(EE_Venue $venue): string
267
-    {
268
-        return $this->columnContent('capacity', $venue->capacity());
269
-    }
270
-
271
-
272
-    /**
273
-     * @throws ReflectionException
274
-     * @throws EE_Error
275
-     */
276
-    public function column_shortcode(EE_Venue $venue): string
277
-    {
278
-        $content = '[ESPRESSO_VENUE id=' . $venue->ID() . ']';
279
-        return $this->columnContent('shortcode', $content);
280
-    }
225
+				if (
226
+					EE_Registry::instance()->CAP->current_user_can(
227
+						'ee_delete_venue',
228
+						'espresso_venues_trash_venue',
229
+						$venue->ID()
230
+					)
231
+				) {
232
+					$trash_venue_link = EE_Admin_Page::add_query_args_and_nonce(
233
+						[
234
+							'action' => 'trash_venue',
235
+							'VNU_ID' => $venue->ID(),
236
+						],
237
+						EE_VENUES_ADMIN_URL
238
+					);
239
+
240
+					$actions['move to trash'] = '<a href="' . $trash_venue_link . '" title="' . esc_attr__(
241
+						'Trash Event',
242
+						'event_espresso'
243
+					) . '">' . esc_html__('Trash', 'event_espresso') . '</a>';
244
+				}
245
+		}
246
+		return $actions;
247
+	}
248
+
249
+
250
+	public function column_address(EE_Venue $venue): string
251
+	{
252
+		return $this->columnContent('address', $venue->address());
253
+	}
254
+
255
+
256
+	public function column_city(EE_Venue $venue): string
257
+	{
258
+		return $this->columnContent('city', $venue->city());
259
+	}
260
+
261
+
262
+	/**
263
+	 * @throws EE_Error
264
+	 * @throws ReflectionException
265
+	 */
266
+	public function column_capacity(EE_Venue $venue): string
267
+	{
268
+		return $this->columnContent('capacity', $venue->capacity());
269
+	}
270
+
271
+
272
+	/**
273
+	 * @throws ReflectionException
274
+	 * @throws EE_Error
275
+	 */
276
+	public function column_shortcode(EE_Venue $venue): string
277
+	{
278
+		$content = '[ESPRESSO_VENUE id=' . $venue->ID() . ']';
279
+		return $this->columnContent('shortcode', $content);
280
+	}
281 281
 }
Please login to merge, or discard this patch.
Spacing   +17 added lines, -17 removed lines patch added patch discarded remove patch
@@ -107,8 +107,8 @@  discard block
 block discarded – undo
107 107
     public function column_id(EE_Venue $venue): string
108 108
     {
109 109
         $content = '
110
-            <span class="ee-entity-id">' . $venue->ID() . '</span>
111
-            <span class="show-on-mobile-view-only">' . $this->column_name($venue, false) . '</span>';
110
+            <span class="ee-entity-id">' . $venue->ID().'</span>
111
+            <span class="show-on-mobile-view-only">' . $this->column_name($venue, false).'</span>';
112 112
         return $this->columnContent('id', $content, 'end');
113 113
     }
114 114
 
@@ -134,10 +134,10 @@  discard block
 block discarded – undo
134 134
         $actions  = $prep_content ? $this->_column_name_action_setup($venue) : [];
135 135
         $content  =
136 136
             EE_Registry::instance()->CAP->current_user_can('ee_edit_venue', 'espresso_venues_edit', $venue->ID())
137
-                ? '<strong><a class="row-title" href="' . $edit_link . '">' . stripslashes_deep(
137
+                ? '<strong><a class="row-title" href="'.$edit_link.'">'.stripslashes_deep(
138 138
                     $venue->name()
139
-                ) . '</a></strong>' : $venue->name();
140
-        $content  .= $venue->status() == 'draft' ? ' - <span class="post-state">' . $statuses['draft'] . '</span>' : '';
139
+                ).'</a></strong>' : $venue->name();
140
+        $content .= $venue->status() == 'draft' ? ' - <span class="post-state">'.$statuses['draft'].'</span>' : '';
141 141
 
142 142
         $content .= $prep_content ? $this->row_actions($actions) : '';
143 143
         return $prep_content ? $this->columnContent('name', $content) : $content;
@@ -164,10 +164,10 @@  discard block
 block discarded – undo
164 164
                 'post'   => $venue->ID(),
165 165
             ];
166 166
             $edit_link       = EE_Admin_Page::add_query_args_and_nonce($edit_query_args, EE_VENUES_ADMIN_URL);
167
-            $actions['edit'] = '<a href="' . $edit_link . '" title="' . esc_attr__(
167
+            $actions['edit'] = '<a href="'.$edit_link.'" title="'.esc_attr__(
168 168
                 'Edit Venue',
169 169
                 'event_espresso'
170
-            ) . '">' . esc_html__('Edit', 'event_espresso') . '</a>';
170
+            ).'">'.esc_html__('Edit', 'event_espresso').'</a>';
171 171
         }
172 172
 
173 173
 
@@ -188,10 +188,10 @@  discard block
 block discarded – undo
188 188
                         EE_VENUES_ADMIN_URL
189 189
                     );
190 190
 
191
-                    $actions['restore_from_trash'] = '<a href="' . $restore_venue_link . '" title="' . esc_attr__(
191
+                    $actions['restore_from_trash'] = '<a href="'.$restore_venue_link.'" title="'.esc_attr__(
192 192
                         'Restore from Trash',
193 193
                         'event_espresso'
194
-                    ) . '">' . esc_html__('Restore from Trash', 'event_espresso') . '</a>';
194
+                    ).'">'.esc_html__('Restore from Trash', 'event_espresso').'</a>';
195 195
                 }
196 196
                 if (
197 197
                     $venue->count_related('Event') === 0
@@ -209,18 +209,18 @@  discard block
 block discarded – undo
209 209
                         EE_VENUES_ADMIN_URL
210 210
                     );
211 211
 
212
-                    $actions['delete permanently'] = '<a href="' . $delete_venue_link . '" title="' . esc_attr__(
212
+                    $actions['delete permanently'] = '<a href="'.$delete_venue_link.'" title="'.esc_attr__(
213 213
                         'Delete Permanently',
214 214
                         'event_espresso'
215
-                    ) . '">' . esc_html__('Delete Permanently', 'event_espresso') . '</a>';
215
+                    ).'">'.esc_html__('Delete Permanently', 'event_espresso').'</a>';
216 216
                 }
217 217
                 break;
218 218
             default:
219 219
                 $actions['view'] = '
220
-                    <a  href="' . get_permalink($venue->ID()) . '"
221
-                        title="' . esc_attr__('View Venue', 'event_espresso') . '"
220
+                    <a  href="' . get_permalink($venue->ID()).'"
221
+                        title="' . esc_attr__('View Venue', 'event_espresso').'"
222 222
                     >
223
-                        ' . esc_html__('View', 'event_espresso') . '
223
+                        ' . esc_html__('View', 'event_espresso').'
224 224
                     </a>';
225 225
                 if (
226 226
                     EE_Registry::instance()->CAP->current_user_can(
@@ -237,10 +237,10 @@  discard block
 block discarded – undo
237 237
                         EE_VENUES_ADMIN_URL
238 238
                     );
239 239
 
240
-                    $actions['move to trash'] = '<a href="' . $trash_venue_link . '" title="' . esc_attr__(
240
+                    $actions['move to trash'] = '<a href="'.$trash_venue_link.'" title="'.esc_attr__(
241 241
                         'Trash Event',
242 242
                         'event_espresso'
243
-                    ) . '">' . esc_html__('Trash', 'event_espresso') . '</a>';
243
+                    ).'">'.esc_html__('Trash', 'event_espresso').'</a>';
244 244
                 }
245 245
         }
246 246
         return $actions;
@@ -275,7 +275,7 @@  discard block
 block discarded – undo
275 275
      */
276 276
     public function column_shortcode(EE_Venue $venue): string
277 277
     {
278
-        $content = '[ESPRESSO_VENUE id=' . $venue->ID() . ']';
278
+        $content = '[ESPRESSO_VENUE id='.$venue->ID().']';
279 279
         return $this->columnContent('shortcode', $content);
280 280
     }
281 281
 }
Please login to merge, or discard this patch.